師長來了,關注師長不迷路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