# hill-spring-ext
**Repository Path**: hilltool/hill-spring-ext
## Basic Information
- **Project Name**: hill-spring-ext
- **Description**: 基于spring的基础组件扩展包
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 2
- **Created**: 2022-08-16
- **Last Updated**: 2025-04-10
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# hill-spring-ext
#### 介绍
hill-spring-ext基于spring全家桶对各类开发场景进行抽象封装的spring扩展组件包。该组件包主要有以下基础组件:
* 基于package扫描的自定义beandefination注册器
* beandefination属性重置器
* 面向同一集团公司夸业务线间不同注册中心的二方api支持的重写feign url组件
* [对sping bean通过注解形式定义的别名alias组件](#sping别名alias组件)
* 面向外部对接的mock组件
* 将传统通过工具类来实现的消息发送、发送外部事件等进行面向接口编程的iop组件
* 通过注解来定义业务处理分发器的Dispatcher组件
* 通过注解实现的对spring mvc的请求参数解密和响应结果加密的mvc扩展组件
* [mybatisplus多数据源优化组件](#mybatisplus多数据源优化组件):
* 通过函数式编程实现的集合转换器和树行结构数据转换器
#### 软件架构
spring cloud:Greenwich.SR3
spring boot:2.2.9.RELEASE
hutool:5.1.0
jdk:8.0
#### 使用说明
1. 添加maven依赖(jar包已发布到maven中央仓库,最新版本为0.0.2)
```xml
cn.hill4j.tool
hill4j-spring-ext
0.0.3-RELEASE
```
2. 参考demo工程:https://gitee.com/hilltool/hill-spring-std
3. 参考具体各个组件使用手册
#### 组件使用手册
##### sping别名alias组件
**解决问题:**
在业务开发过程中,我们经常会碰到根据不同的业务类型来选择不同的业务策略,如在同一业务模块下不同的业务类型审批流程审批通过或需要不同的审批处理器,如积分系统中不同的积分事件需要通过不同的积分计算策略来进行计算等场景。
我以往的实现方式是通过定义一个简单工厂模式将策略与业务的映射关系注册到简单工厂中,如果用spring 的InitializingBean接口的afterPropertiesSet方法来注册时,会存在aop切面不生效,事务也不会执行,而alias组件思路是通过spring bean容器的别名机制来替代这个简单工厂,在通过spring 获取bean工具通过别名来获取具体策略bean。
**代码实现:**
定义别名组注解```@Aliases```和别名注解```@Alias```,直接加载spring bean的实现类上,在通过```BeanAliasPostProcessor```对spring bean 初始化后置操作扩展点进行扩展,给spring bean注册好对应的别名,最后使用别名bean获取工具类根据别名获取对应的bean来执行具体的业务操作
```java
public class BeanAliasPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class beanClazz = AopUtils.getTargetClass(bean);
Aliases aliases = AnnotationUtils.findAnnotation(beanClazz, Aliases.class);
Set aliasNames = new HashSet<>();
if (Objects.nonNull(aliases) && aliases.value().length > 0){
for (Alias alias:aliases.value()){
if (alias.value().length > 0){
Arrays.stream(alias.value()).forEach(a -> aliasNames.add(BeansAliasUtils.createAliasBeanName(alias.groupName(),a)));
}
}
}else {
Alias alias = AnnotationUtils.findAnnotation(beanClazz, Alias.class);
if (Objects.nonNull(alias) && alias.value().length > 0){
Arrays.stream(alias.value()).forEach(a -> aliasNames.add(BeansAliasUtils.createAliasBeanName(alias.groupName(),a)));
}
}
if (!aliasNames.isEmpty()){
for (String aliasName:aliasNames){
((AliasRegistry) applicationContext).registerAlias(beanName,aliasName);
}
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
...
}
```
**使用步骤**
* 给bean添加别名组件(```@Alias```被```@Component```修饰过,只要加上```@Alias```不需要额外加上```@Component```注解也是会装载到spring容器中)
```java
@Alias("t1")
@Component
public class AliasTest1 implements AliasTest{
@Override
public String out(String str) {
return "AliasTest1:" + str;
}
}
...
@Alias("t2")
public class AliasTest2 implements AliasTest{
@Override
public String out(String str) {
return "AliasTest2:" + str;
}
}
```
* 业务代码中根据别名通过工具类获取对应的bean
```java
@RestController
@RequestMapping("/alias")
public class AliasController {
@GetMapping("/test/{biz}/{str}")
public String aliasTest(@PathVariable("biz") String biz,@PathVariable("str") String str) throws DispatchInvokerException {
return BeansAliasUtils.getBean(biz, AliasTest.class).out(str);
}
}
```
该示例代码中会通过请求参数biz的值为t1/t2来选择具体要执行那个业务逻辑
##### mybatisplus多数据源优化组件:
springjdbc本地事务ThreadLocal中是通过org.springframework.transaction.reactive.TransactionSynchronizationUtils#unwrapResourceIfNecessary方法返回的数据源对象作为key值来存放对应数据源的事务状态,而mybatis-plus中的动态数据源通过该方法一直返回的是动态数据源对象本身,则当存在事务时切换数据源来执行查询数据时存在会导致切换源不存在,而通过实现InfrastructureProxy接口,将需要切换的目标数据源返回,就能解决该问题,核心代码如下:
**org.springframework.transaction.reactive.TransactionSynchronizationUtils#unwrapResourceIfNecessary方法**
```java
static Object unwrapResourceIfNecessary(Object resource) {
Assert.notNull(resource, "Resource must not be null");
Object resourceRef = resource;
// unwrap infrastructure proxy
if (resourceRef instanceof InfrastructureProxy) {
resourceRef = ((InfrastructureProxy) resourceRef).getWrappedObject();
}
if (aopAvailable) {
// now unwrap scoped proxy
resourceRef = ScopedProxyUnwrapper.unwrapIfNecessary(resourceRef);
}
return resourceRef;
}
```
获取事务对象方法:**org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary**
```java
// 创建或获取事务
@SuppressWarnings("serial")
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
// 创建事务
protected Object doGetTransaction() {
DataSourceTransactionManager.DataSourceTransactionObject txObject = new DataSourceTransactionManager.DataSourceTransactionObject();
txObject.setSavepointAllowed(this.isNestedTransactionAllowed());
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
// 获取数据源
@Nullable
public Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to context [" +
this.transactionContext.getName() + "]");
}
return value;
}
// 从threadLocal中获取数据源
@Nullable
private Object doGetResource(Object actualKey) {
return this.transactionContext.getResources().get(actualKey);
}
```
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)