序言
上一节我们学习了异步查询转同步的 7 种实现方式,今天我们就来学习一下,如何对其进行封装,使其成为一个更加便于使用的工具。
思维导图如下:
本节主要介绍注解框架如何整合 spring 和 springboot。
让我们的框架使用更加便利。
拓展阅读
java 手写并发框架(一)异步查询转同步的 7 种实现方式
整合思路
spring 整合
其实整体的实现类似于 spring 的 @Retry
注解。
(1)在锁同步方法上使用对应的注解 @Sync
(2)在异步回调方法上使用注解 @SyncCallback
(3)通过定义 aop 切面,实现拦截调用
springboot 整合
通过 spring-boot-starter 特性,实现与 springboot 的整合
spring 整合实现
注解定义
我们定义 @EnableSync
注解,用于声明启用 Sync 同步功能。
这个用过 spring 框架的,应该见过很多类似的注解,比如 @EnableAsync
@EnableRetry
等等。
package com.github.houbb.sync.spring.annotation;
import com.github.houbb.sync.spring.config.SyncAopConfig;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* 启用自动日志注解
* @author binbin.hou
* @since 0.0.2
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SyncAopConfig.class)
@EnableAspectJAutoProxy
public @interface EnableSync {
}
这里导入了一个 SyncAopConfig 配置。
实现如下:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 自动 sync aop 配置
*
* @author binbin.hou
* @since 0.0.2
*/
@Configuration
@ComponentScan(basePackages = "com.github.houbb.sync.spring")
public class SyncAopConfig {
}
主要用于指定需要扫描的包。
aop 核心实现
package com.github.houbb.sync.spring.aop;
import com.github.houbb.aop.spring.util.SpringAopUtil;
import com.github.houbb.sync.api.annotation.Sync;
import com.github.houbb.sync.api.annotation.SyncCallback;
import com.github.houbb.sync.api.api.ISyncContext;
import com.github.houbb.sync.core.bs.SyncBs;
import com.github.houbb.sync.spring.context.SpringAopSyncContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 这是一种写法
* @author binbin.hou
* @since 0.0.2
*/
@Aspect
@Component
@EnableAspectJAutoProxy
public class SyncAop {
/**
*
* 切面方法:
*
* (1)扫描所有的共有方法
* <pre>
* execution(public * *(..))
* </pre>
*
* 问题:切面太大,废弃。
* 使用扫描注解的方式替代。
*
* (2)扫描指定注解的方式
*
* 其实可以在 aop 中直接获取到注解信息,暂时先不调整。
* 暂时先不添加 public 的限定
*
* (3)直接改成注解的优缺点:
* 优点:减少了 aop 的切面访问
* 缺点:弱化了注解的特性,本来是只要指定的注解即可,
*
* 不过考虑到使用者的熟练度,如果用户知道了自定义注解,自定义 aop 应该也不是问题。
*/
@Pointcut("@within(com.github.houbb.sync.api.annotation.Sync)" +
"|| @within(com.github.houbb.sync.api.annotation.SyncCallback)" +
"|| @annotation(com.github.houbb.sync.api.annotation.Sync)" +
"|| @annotation(com.github.houbb.sync.api.annotation.SyncCallback)")
public void syncPointcut() {
}
/**
* 执行核心方法
*
* 相当于 MethodInterceptor
*
* @param point 切点
* @return 结果
* @throws Throwable 异常信息
* @since 0.0.2
*/
@Around("syncPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = SpringAopUtil.getCurrentMethod(point);
Sync sync = AnnotationUtils.getAnnotation(method, Sync.class);
SyncCallback callback = AnnotationUtils.getAnnotation(method, SyncCallback.class);
ISyncContext context = SpringAopSyncContext.newInstance()
.method(method)
.sync(sync)
.callback(callback)
.point(point);
return SyncBs.newInstance()
.syncContext(context)
.execute();
}
}
这里统一拦截指定了 @Sync
和 @SyncCallback
注解的方法。
复用了原来的代理的实现,当然你可以重新实现。
spring 整合验证
在有了字节码增强的基础上,我们实现 spring 整合实际上非常简单。
下面让我们验证一下实现是否符合我们的预期。
maven 引入
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sync-spring</artifactId>
<version>${project.version}</version>
</dependency>
入门测试
配置类
@EnableSync
注解启用 sync 特性。
@Configurable
@ComponentScan(basePackages = "com.github.houbb.sync.test.service")
@EnableSync
public class SpringConfig {
}
业务代码
和以前一样
@Service
public class UserService {
String id = "";
@Sync
public String queryId() {
System.out.println("开始查询");
return id;
}
@SyncCallback(value = "queryId")
public void queryIdCallback() {
System.out.println("回调函数执行");
id = "123";
}
}
测试代码
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringUserServiceTest {
@Autowired
private UserService userService;
@Test
public void queryIdTest() {
// 异步执行回调
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("开始异步执行回调");
TimeUnit.SECONDS.sleep(2);
userService.queryIdCallback();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
String id = userService.queryId();
System.out.println("id: " + id);
}
}
测试结果符合我们的预期,日志和上一节类似,此处就不再贴出。
springboot 整合实现
整合原理
利用 spring-boot-starter,在 spring.factories
文件中指定如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.github.houbb.sync.springboot.starter.config.SyncAutoConfig
这样就可以在 springboot 启动的时候自动加载我们定义的配置类。
SyncAutoConfig 配置
package com.github.houbb.sync.springboot.starter.config;
import com.github.houbb.sync.spring.annotation.EnableSync;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
/**
* 异步转同步自动配置
* @author binbin.hou
* @since 0.0.3
*/
@Configuration
@ConditionalOnClass(EnableSync.class)
@EnableSync
public class SyncAutoConfig {
}
实现非常的简单,直接启用 @EnableSync
注解。
这样使用的时候只需要引入 jar,就可以自定生效了。
当然也可以和 springboot 的配置文件配合,提供更加灵活的配置。
验证
maven 引入
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sync-springboot-starter</artifactId>
<version>${最新版本}</version>
</dependency>
配置
@SpringBootApplication
public class SyncApplication {
public static void main(String[] args) {
SpringApplication.run(SyncApplication.class, args);
}
}
只需要引入 jar 即可,业务类的定义和 spring 整合保持一致,然后就可以开始使用了。
小结
到这里我们就把同步框架整合 spring 和 springboot 基本完成了。
回顾我们整个历程,从最开始的异步查询转同步的 7 种方式,到对其中几种常见的封装,然后是字节码结合注解的代理使用,最后是本节的 spring 的整合。
这一路走来,基本上同步工具到了一个勉强可用的阶段。
但是依然存在很多问题,比如并发时的问题,性能问题等等,这个后续有机会我们一起去解决。
觉得本文对你有帮助的话,欢迎点赞评论收藏转发一波。你的鼓励,是我最大的动力~
不知道你有哪些收获呢?或者有其他更多的想法,欢迎留言区和我一起讨论,期待与你的思考相遇。
文中如果链接失效,可以点击 {阅读原文}。