师长来了,关注师长不迷路web
该趟专车是开往基于Spring Boot事务思想实战的专车,在上一篇 搭上SpringBoot事务源码分析专车[1]中咱们详细介绍了Spring Boot事务实现的原理,这一篇是基于上一篇的实战。spring
在实战以前,咱们再次回顾下上篇文章讲解的重点:bash
后置处理器:对Bean进行拦截并处理app
切面:由切点和通知组成ide
切点:用于匹配符合的类和方法spring-boot
通知:用于代理处理源码分析
如何利用后置处理器对Bean进行拦截并处理?ui
如何定义切面?this
如何定义切点?spa
如何定义通知?
如何实现自动配置?
实现是以Spring Boot为基础,须要添加以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
复制代码
按照如上提到的问题依次定义
定义bean后置处理器,特别注意,若是项目中使用到了事务特性,就不须要重复定义
/**
* 必定要声明InfrastructureAdvisorAutoProxyCreator,用于实现bean的后置处理
*
* @return
*/
@Bean
public InfrastructureAdvisorAutoProxyCreator infrastructureAdvisorAutoProxyCreator() {
return new InfrastructureAdvisorAutoProxyCreator();
}
复制代码
定义切面
public class BeanFactorySystemLogAdvisor extends AbstractBeanFactoryPointcutAdvisor {
/**
* 定义切点
*/
private final SystemLogPointcut point = new SystemLogPointcut();
@Override
public Pointcut getPointcut() {
return this.point;
}
}
复制代码
定义切点
public class SystemLogPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 查找类上@SystemLog注解属性
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
targetClass, SystemLog.class, false, false);
if (Objects.nonNull(attributes)) {
return true;
}
// 查找方法上@SystemLog注解属性
attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
method, SystemLog.class, false, false);
return Objects.nonNull(attributes);
}
}
复制代码
定义通知
@Slf4j
public class SystemLogInterceptor implements MethodInterceptor, Serializable {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
String className = method.getDeclaringClass().getSimpleName();
String methodName = method.getName();
log.info("======[" + className + "#" + methodName + " method begin execute]======");
Arrays.stream(invocation.getArguments()).forEach(argument -> log.info("======[execute method argument:" + argument + "]======"));
Long time1 = Clock.systemDefaultZone().millis();
Object result = invocation.proceed();
Long time2 = Clock.systemDefaultZone().millis();
log.info("======[method execute time:" + (time2 - time1) + "]======");
return result;
}
}
复制代码
自动配置
@Configuration
public class ProxySystemLogConfiguration {
/**
* 定义切面
* 此处必定要指定@Role注解
*
* @return
*/
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
public BeanFactorySystemLogAdvisor beanFactorySystemLogAdvisor() {
BeanFactorySystemLogAdvisor advisor = new BeanFactorySystemLogAdvisor();
advisor.setAdvice(systemLogInterceptor());
return advisor;
}
/**
* 定义通知
*
* @return
*/
@Bean
public SystemLogInterceptor systemLogInterceptor() {
return new SystemLogInterceptor();
}
/**
* 必定要声明InfrastructureAdvisorAutoProxyCreator,用于实现bean的后置处理
*
* @return
*/
@Bean
public InfrastructureAdvisorAutoProxyCreator infrastructureAdvisorAutoProxyCreator() {
return new InfrastructureAdvisorAutoProxyCreator();
}
}
复制代码
定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
}
复制代码
定义控制器
@RestController
public class SystemLogController {
@Autowired
private SystemLogService systemLogService;
@GetMapping("/log")
public String hello(@RequestParam("name") String name) throws InterruptedException {
return systemLogService.log(name);
}
}
复制代码
定义业务方法
@Slf4j
@Service
public class SystemLogService {
@SystemLog
public String log(String name) throws InterruptedException {
log.info("执行业务方法");
TimeUnit.SECONDS.sleep(1);
return "hello " + name;
}
}
复制代码
定义启动类
@SpringBootApplication
public class TransactionImitateApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionImitateApplication.class, args);
}
}
复制代码
访问http://localhost:8080/log?name=advisor
查看控制台
2019-08-23 11:13:36.029 INFO 23227 --- [nio-8080-exec-1] c.b.example.config.SystemLogInterceptor : ======[SystemLogService#log method begin execute]======
2019-08-23 11:13:36.030 INFO 23227 --- [nio-8080-exec-1] c.b.example.config.SystemLogInterceptor : ======[execute method argument:advisor]======
2019-08-23 11:13:36.038 INFO 23227 --- [nio-8080-exec-1] c.boot.example.service.SystemLogService : 执行业务方法
2019-08-23 11:13:37.038 INFO 23227 --- [nio-8080-exec-1] c.b.example.config.SystemLogInterceptor : ======[method execute time:1004]======
复制代码
能够看到经过模拟@Transaction注解的实现方式,完成了日志切面功能。
首先咱们须要定义一个Bean后置处理器,用于拦截处理Bean
而后定义切面,在切面中定义切点
切点中实现切入的逻辑,好比此处咱们的实现逻辑就是查找类或方法上是否含有@SystemLog注解
定义通知,完成代理工做
自动装配,将咱们的切面、通知、Bean后置处理器声明在配置类中
集成业务
回顾下开头的五个问题:
如何利用后置处理器对Bean进行拦截并处理?直接在配置类中声明后置处理器
如何定义切面?继承AbstractBeanFactoryPointcutAdvisor,并在配置类中中声明
如何定义切点?继承StaticMethodMatcherPointcut,实现matches方法
如何定义通知?实现MethodInterceptor接口,实现invoke方法
如何实现自动配置?自定义配置类,声明全部须要加入容器的Bean