zoukankan      html  css  js  c++  java
  • Spring

    摘要

    AOP是通过对程序结构的另一种思考,补充了OOP。在OOP中,最关键的模块是类,而在AOP中最关键的模块是切面。Spring AOP是基于代理的原理实现的。Spring AOP使用JDK的动态代理或者CGLIB来创建代理实例。

    一、什么是AOP

    AOP(Aspect-oriented Programming,面向切面编程)是通过对程序结构的另一种思考,补充了OOP(Object-oriented Programing,面向对象编程)。在OOP中,最关键的模块是类,而在AOP中最关键的模块是切面。AOP关注的是横跨不同类的逻辑的封装。

    AOP是Spring框架的一个核心组件,Spring AOP的使用有两种风格,分别是:Schema(XML)和@AspectJ

    AOP在Spring中的应用有:

    • 提供了声明式的企业级服务(如最重要的声明式事务管理)
    • 让用户自定义切面对OOP设计补充。

    AOP的一些概念

    • Aspect(切面):横跨不同类的关注部分的封装抽象
    • Join Point(连接点):程序运行中的一些节点,比如方法的执行、异常。在Spring中,Join Point 通常代表一个方法的执行
    • Advice(增强):在Join Point上的处理,类型可以有"around"、"before"、"after"。Spring将Advice构建成拦截器,围绕着Join Point维护着一条拦截链
    • Pointcut(切点):和Join Point相对应起来,Pointcut是一个匹配规则,匹配上Pointcut的Join Point会在上面执行Advice
    • Introduction(引入):为一个类型添加方法或字段,Spring AOP 允许引入新的接口(和对应实现)到目标对象上
    • Target Object(目标对象,Advised Object):织入Advice后的对象,因为Spring AOP使用动态代理实现后,目标对象通常是一个代理对象
    • Weaving(织入):链接Aspect和其它对象并生成Advice Object的工程。

    二、AOP使用

    以下是分别使用@AspectJ和Schema风格定义一个AOP的例子,此例子用于在服务执行出错时,重试执行服务。(高并发下的乐观锁异常处理)

    要注意的是下面所执行的服务是幂等的才可以重试的。后面会展示如何通过定义一个幂等注解,并对幂等注解进行过滤配置。

    1、@AspectJ风格

    @AspectJ注解风格是从AspectJ项目中引入的,但是AOP还是只使用Spring AOP,而不是依赖AspectJ的编译器和织入器。

    使用Java代码启用Aspect:

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    }
    

    或者使用XML配置启用Aspect:

    <!-- 声明自动为spring容器中那些配置@Aspect切面的bean创建代理 -->
    <aop:aspectj-autoproxy/>
    

    定义Aspect、Pointcut和Advice:

    // 注解Aspect
    @Aspect
    public class ConcurrentOperationExecutor implements Ordered {
    
        private static final int DEFAULT_MAX_RETRIES = 2;
    
        private int maxRetries = DEFAULT_MAX_RETRIES;
        private int order = 1;
    
        public void setMaxRetries(int maxRetries) {
            this.maxRetries = maxRetries;
        }
    
        public int getOrder() {
            return this.order;
        }
    
        public void setOrder(int order) {
            this.order = order;
        }
    
        // 定义Pointcut和Advice
        @Around("com.xyz.myapp.SystemArchitecture.businessService()")
        public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
            int numAttempts = 0;
            PessimisticLockingFailureException lockFailureException;
            do {
                numAttempts++;
                try {
                    return pjp.proceed();
                }
                catch(PessimisticLockingFailureException ex) {
                    lockFailureException = ex;
                }
            } while(numAttempts <= this.maxRetries);
            throw lockFailureException;
        }
    
    }
    

    配置Aspect对应的Bean:

    <!-- 配置Bean -->
    <bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
    </bean>
    

    定义幂等注解:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Idempotent {
        // marker annotation
    }
    

    为前面的Aspect添加幂等注解过滤:

    @Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
            "@annotation(com.xyz.myapp.service.Idempotent)")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        ...
    }
    

    2、Schema(XML)支持

    定义Aspect、Pointcut和Advice:

    public class ConcurrentOperationExecutor implements Ordered {
    
        private static final int DEFAULT_MAX_RETRIES = 2;
    
        private int maxRetries = DEFAULT_MAX_RETRIES;
        private int order = 1;
    
        public void setMaxRetries(int maxRetries) {
            this.maxRetries = maxRetries;
        }
    
        public int getOrder() {
            return this.order;
        }
    
        public void setOrder(int order) {
            this.order = order;
        }
    
        // 环绕增强,用于失败重试
        public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
            int numAttempts = 0;
            PessimisticLockingFailureException lockFailureException;
            do {
                numAttempts++;
                try {
                    return pjp.proceed();
                }
                catch(PessimisticLockingFailureException ex) {
                    lockFailureException = ex;
                }
            } while(numAttempts <= this.maxRetries);
            throw lockFailureException;
        }
    
    }
    

    配置Aspect、Pointcut和Advice:

    <aop:config>
        <!-- 定义切面 -->
        <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
            <!-- 定义切入点 -->
            <aop:pointcut id="idempotentOperation"
                expression="execution(* com.xyz.myapp.service.*.*(..))"/>
            <!-- 定义环绕增强 -->
            <aop:around
                pointcut-ref="idempotentOperation"
                method="doConcurrentOperation"/>
        </aop:aspect>
    </aop:config>
    
    <!-- 定义Bean -->
    <bean id="concurrentOperationExecutor"
        class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
            <property name="maxRetries" value="3"/>
            <property name="order" value="100"/>
    </bean>
    

    定义幂等注解:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Idempotent {
        // marker annotation
    }
    

    为前面的Aspect添加幂等注解过滤:

    <aop:pointcut id="idempotentOperation"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and
            @annotation(com.xyz.myapp.service.Idempotent)"/>
    

    3、两种AOP声明风格的对比

    Aspect同样是一个面向切面的框架,Spring通过Aspect无缝地将AOP和IoC整合在一起。

    使用Spring AOP还是AspectJ

    Spring AOP 在开发中不需要引入AspectJ的编译器和织入器,如果想在Spring的Bean引入增强。如果不是管理Spring容器,则需要AspectJ了。

    在Spring AOP中使用@AspectJ还是XML

    • 所有切面、切点、增强都写在一个或几个配置文件里便于维护,且不需要修改Java代码;
    • XML风格AOP仅支持"singleton"切面实例模型,而采用AspectJ风格的AOP则没有这个限制;
    • XML风格的AOP不支持命名切入点的的声明,而AspectJ没有这个限制(@Pointcut);

    参考:简单说说SpringAOP与Aspectj的不同,以及使用SpringAOP所需的最小jar依赖

    三、AOP的代理机制

    Spring AOP是基于代理的原理实现的。Spring AOP使用JDK的动态代理或者CGLIB来创建代理实例。JDK动态代理是JDK的内置代理,而CGLIB则是一个开源的类定义库(重新打包到spring-core中)。

    关于代理的概念,参考:27--静态代理模式和JDK、CGLIB动态代理

    Spring AOP使用ProxyFactory来创建代理,类似代码如下:

    public class Main {
        public static void main(String[] args) {
            ProxyFactory factory = new ProxyFactory(new SimplePojo());
            factory.adddInterface(Pojo.class);
            factory.addAdvice(new RetryAdvice());
            factory.setExposeProxy(true);
    
            Pojo pojo = (Pojo) factory.getProxy(); // 返回jdk或者cglib生成的代理对象
            // this is a method call on the proxy!
            pojo.foo();
        }
    }
    

    如果代理对象实现了至少一个接口,则会使用JDK动态代理。如果代理对象没有实现任何接口,则会使用CGLIB进行代理。

    使用CGLIB的一些限制:

    • final类和方法不能使用增强(子类不能不能重写final方法)
    • 从Spring 4.0开始,代理对象的构造函数不再被调用两次,因为CGLIB代理实例通过Objenesis创建。只有JVM不允许构造函数绕过时,才可能看到来自Spring AOP支持的双重调用和相应的调用日志。

    参考:Objenesis,另一种实例化对象的方式

    强制使用CGLIB:

    <aop:config proxy-target-class="true">
        <!-- other beans defined here... -->
    </aop:config>
    
    <!-- 或者 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    

    参考资料

    作者:Emile

    个人主页:http://www.guanjianzhuo.com/

    欢迎访问、评论、留言!

  • 相关阅读:
    366. Find Leaves of Binary Tree输出层数相同的叶子节点
    716. Max Stack实现一个最大stack
    515. Find Largest Value in Each Tree Row查找一行中的最大值
    364. Nested List Weight Sum II 大小反向的括号加权求和
    156. Binary Tree Upside Down反转二叉树
    698. Partition to K Equal Sum Subsets 数组分成和相同的k组
    244. Shortest Word Distance II 实现数组中的最短距离单词
    187. Repeated DNA Sequences重复的DNA子串序列
    java之hibernate之基于主键的双向一对一关联映射
    java之hibernate之基于主键的单向一对一关联映射
  • 原文地址:https://www.cnblogs.com/guanjianzhuo/p/10949474.html
Copyright © 2011-2022 走看看