【原創】003 | 搭上基於SpringBoot事務思想實戰專車

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

相關文章
相關標籤/搜索