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/

    欢迎访问、评论、留言!

  • 相关阅读:
    在thinkphp中批量生成Word并压缩打包下载
    使用phpExcel实现Excel数据的导入导出(完全步骤)
    HTML的几种触发
    IOS上iframe的滚动条失效的解决办法。
    VC6下安装与配置OpenCV1.0
    VS2010+Opencv2.4.0的配置攻略
    could not find the main class
    Makefile 教程
    第一个小程序——显示图像
    自建CDib类库
  • 原文地址:https://www.cnblogs.com/guanjianzhuo/p/10949474.html
Copyright © 2011-2022 走看看