Spring自定义注解定义AOP配置去xml

原理参考ImportBeanDefinitionRegistrar+SPI简化Spring开发

spring中AOP使用非常广泛,引入方式一般分为两种,注解方式或xml方式。直接方式使用@AspectJ这样的注解,其缺点是需要手写切面实现业务逻辑,不太方便用第三方包做切面。xml方式打破了注解方式的局限,配置起来较为灵活,但xml毕竟偏向于配置,有一定的臃肿性。换句话说,在去xml的大趋势下,如何消除用来配置AOP的xml呢?

本文提出一种解决方案,自定义配置AOP的注解,使用ImportBeanDefinitionRegistrar机制,动态解析注解,基于xml的AOP配置模板生成xml配置,借助Spring解析xml配置的工具解析aop bean定义加载到context中。

首先定义配置AOP定义的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@EnableAutoRegistrar
@Repeatable(AopDefinitions.class)
public @interface AopDefinition {

    boolean proxyTargetClass() default false;

    String refBeanName();

    int order() default 0;

    String advice();

    String adviceMethod();

    /**
     * 切入点,最终会以空格拼接
     */
    String[] pointcut();

}

以及批量注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@EnableAutoRegistrar
public @interface AopDefinitions {

    AopDefinition[] value();

}

再定义xml方式的AOP配置模板:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
	    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config proxy-target-class="${enable-proxy-target-class}">

        <aop:aspect ref="${aop-ref}" order="${aop-order}">
            <aop:${aop-advice} method="${aop-advice-method}"
                        pointcut="${aop-pointcut}"/>
        </aop:aspect>

    </aop:config>

</beans>

可见注解的参数就是为了匹配AOP xml模板中参数。下面是利用模板生成xml配置并解析bean以及注册到context的工具类:

@Data
@Accessors(chain = true)
public class AspectJParams {

    private boolean proxyTargetClass;
    private String aopRefBean;
    private int aopOrder;
    private String aopAdvice;
    private String aopAdviceMethod;
    private String aopPointcut;

    public Map<String, Object> build() {
        Map<String, Object> map = new HashMap<>();
        map.put("enable-proxy-target-class", proxyTargetClass);
        map.put("aop-ref", aopRefBean);
        map.put("aop-order", aopOrder);
        map.put("aop-advice", aopAdvice);
        map.put("aop-advice-method", aopAdviceMethod);
        map.put("aop-pointcut", aopPointcut);
        return map;
    }
}
@Slf4j
public class AopBeanDefinitionRegistry {

    public static int loadBeanDefinitions(BeanDefinitionRegistry registry, AspectJParams params)
            throws BeanDefinitionStoreException, IOException {
        final ClassPathResource classPathResource = new ClassPathResource("/resource/aop-definition-template.tpl");
        String template = null;
        try (InputStream inputStream = classPathResource.getInputStream()) {
            template = IOUtils.readFromInputStream(inputStream, Charset.forName("UTF-8"), true);
        }
        if (template == null || template.isEmpty()) {
            throw new RuntimeException("AOP template empty");
        }
        String defineText = new StringSubstitutor(params.build()).replace(template);
        if (log.isTraceEnabled()) {
            log.debug("AOP define: \n{}", defineText);
        }
        ByteArrayResource byteArrayResource = new ByteArrayResource(defineText.getBytes());
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
        return reader.loadBeanDefinitions(byteArrayResource);
    }

}

主要是利用XmlBeanDefinitionReader来加载bean定义。

对于我们自定义注解的处理我们定义对应的Handler:

@Slf4j
public class AopDefinitionHandler implements ConfigurationRegisterHandler {

    @Override
    public void registerBeanDefinitions(RegisterBeanDefinitionContext context) {
        Set<AnnotationAttributes> annotationAttributes = SpringAnnotationConfigUtils.attributesForRepeatable(
                context.getImportingClassMetadata(), AopDefinitions.class, AopDefinition.class);
        if (CollectionUtils.isEmpty(annotationAttributes)) {
            return;
        }
        for (AnnotationAttributes attributes : annotationAttributes) {
            final AspectJParams params = new AspectJParams()
                    .setProxyTargetClass(attributes.getBoolean("proxyTargetClass"))
                    .setAopRefBean(attributes.getString("refBeanName"))
                    .setAopOrder(attributes.getNumber("order").intValue())
                    .setAopAdvice(attributes.getString("advice"))
                    .setAopAdviceMethod(attributes.getString("adviceMethod"))
                    .setAopPointcut(Arrays.stream(attributes.getStringArray("pointcut"))
                            .filter(StringUtils::isNotEmpty)
                            .reduce((x, y) -> x + " " + y).get());
            try {
                AopBeanDefinitionRegistry.loadBeanDefinitions(context.getRegistry(), params);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

    @Override
    public int getOrder() {
        return 0;
    }
}

这样我们在@Configuration的class上使用@AopDefinition或@AopDefinitions注解,即可向容器中定义AOP。示例如下:

@Configuration
@AopDefinition(proxyTargetClass = true, refBeanName = "logEnvAspect",
        order = -10, advice = "around", adviceMethod = "doAspect",
        pointcut = {
                "@within(org.springframework.stereotype.Controller)",
                "|| @annotation(org.springframework.stereotype.Controller)",
                "|| @within(org.springframework.web.bind.annotation.RestController)",
                "|| @annotation(org.springframework.web.bind.annotation.RestController)"
        })
public class AopDefinitionConfiguration {

    @Bean("logEnvAspect")
    public LogEnvAspect logEnvAspect() {
        return new LogEnvAspect().setKeys(Arrays.asList("system", "host", "port", "appName", "module"));
    }

}

我们在@Controller或@RestController的bean方法上引入了around通知,切点逻辑是LogEnvAspect这个bean的doAspect方法。效果配下面的xml配置是一样的:

    <aop:config proxy-target-class="true">

        <!-- 基于注解的RedisCache -->
        <aop:aspect ref="logEnvAspect" order="-10">
            <aop:around method="doAspect"
                        pointcut="@within(org.springframework.stereotype.Controller)
                        || @annotation(org.springframework.stereotype.Controller)
                        || @within(org.springframework.web.bind.annotation.RestController)
                        || @annotation(org.springframework.web.bind.annotation.RestController)"/>
        </aop:aspect>

    </aop:config>

完整代码参考github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值