• Spring 实践 -AOP


    Spring 实践

    标签: Java与设计模式


    AOP引介

    AOP(Aspect Oriented Programing)面向切面编程採用横向抽取机制,以代替传统的纵向继承体系的反复性代码(如性能监控/事务管理/安全检查/缓存实现等).

    横向抽代替码复用: 基于代理技术,在不改动原来代码的前提下,对原有方法进行增强.


    Spring AOP 历史

    • 1.2開始, Spring開始支持AOP技术(Spring AOP)
      Spring AOP使用纯Java实现,不须要专门的编译过程和类载入器,在运行期通过代理方式向目标类织入增强代码.
    • 2.0之后, 为了简化AOP开发, Spring開始支持AspectJ(一个基于Java的AOP框架)框架.

    AOP相关术语

    术语 中文 描写叙述
    Joinpoint 连接点 指那些被拦截到的点.在Spring中,这些点指方法(由于Spring仅仅支持方法类型的连接点).
    Pointcut 切入点 指须要(配置)被增强的Joinpoint.
    Advice 通知/增强 指拦截到Joinpoint后要做的操作.通知分为前置通知/后置通知/异常通知/终于通知/围绕通知等.
    Aspect 切面 切入点和通知的结合.
    Target 目标对象 须要被代理(增强)的对象.
    Proxy 代理对象 目标对象被AOP 织入 增强/通知后,产生的对象.
    Weaving 织入 指把增强/通知应用到目标对象来创建代理对象过程(Spring採用动态代理织入,AspectJ採用编译期织入和类装载期织入).
    Introduction 引介 一种特殊通知,在不改动类代码的前提下,能够在运行期为类动态地加入一些Method/Field(不经常使用).

    其它关于AOP理论知识可參考AOP技术研究.


    AOP实现

    Spring AOP代理实现有两种:JDK动态代理Cglib框架动态代理, JDK动态代理能够參考博客代理模式的动态代理部分, 在这里仅介绍CGLib框架实现.

    cglib 动态代理

    cglib(Code Generation Library)是一个开源/高性能/高质量的Code生成类库,能够在运行期动态扩展Java类与实现Java接口.
    cglib比java.lang.reflect.Proxy更强的在于它不仅能够接管接口类的方法,还能够接管普通类的方法(cglib项目).从3.2開始, spring-core包中内置cglib类,因此能够不用加入额外依赖.

    • UserDAO(并没有实现接口)
    /**
     * @author jifang
     * @since 16/3/3 上午11:16.
     */
    public class UserDAO {
    
        public void add(Object o) {
            System.out.println("UserDAO -> Add: " + o.toString());
        }
    
        public void get(Object o) {
            System.out.println("UserDAO -> Get: " + o.toString());
        }
    }
    • CGLibProxyFactory
    public class CGLibProxyFactory {
    
        private Object target;
    
        public CGLibProxyFactory(Object target) {
            this.target = target;
        }
    
    
        private Callback callback = new MethodInterceptor() {
    
            /**
             *
             * @param obj   代理对象
             * @param method    当期调用方法
             * @param args  方法參数
             * @param proxy 被调用方法的代理对象(用于运行父类的方法)
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
                // 前置增强
                System.out.println("+ Before Advice ...");
    
                // 运行目标方法
                //Object result = method.invoke(target, args);
                Object result = proxy.invoke(target, args);
    
                // 后置增强
                System.out.println("+ After Advice ...");
    
                return result;
            }
        };
    
        public Object createProxy() {
    
            // 1. 创建Enhancer对象
            Enhancer enhancer = new Enhancer();
    
            // 2. cglib创建代理, 对目标对象创建子对象
            enhancer.setSuperclass(target.getClass());
    
            // 3. 传入回调接口, 对目标增强
            enhancer.setCallback(callback);
    
            return enhancer.create();
        }
    
        public static void main(String[] args) {
            UserDAO proxy = (UserDAO) new CGLibProxyFactory(new UserDAO()).createProxy();
            proxy.get("hello");
            proxy.add("world");
        }
    }

    AOP小结

    • Spring AOP的底层通过JDK/cglib动态代理为目标对象进行横向织入:
      1) 若目标对象实现了接口,则Spring使用JDK的java.lang.reflect.Proxy代理.
      2) 若目标对象没有实现接口,则Spring使用cglib库生成目标对象的子类.
    • Spring仅仅支持方法连接点,不提供属性连接.
    • 标记为final的方法不能被代理,由于无法进行覆盖.
    • 程序应优先对针对接口代理,这样便于程序解耦/维护.

    Spring AOP

    AOP联盟为通知Advice定义了org.aopalliance.aop.Advice接口, Spring在Advice的基础上,依据通知在目标方法的连接点位置,扩充为下面五类:

    通知 接口 描写叙述
    前置通知 MethodBeforeAdvice 在目标方法运行实施增强
    后置通知 AfterReturningAdvice …运行实施增强
    围绕通知 MethodInterceptor ..运行前后实施增强
    异常抛出通知 ThrowsAdvice …抛出异常后实施增强
    引介通知 IntroductionInterceptor 在目标类中加入新的方法和属性(少用)
    • 加入Spring的AOP依赖
      使用Spring的AOP和AspectJ须要在pom.xml中加入例如以下依赖:
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    • 定义Target
    /**
     * @author jifang
     * @since 16/3/3 下午2:50.
     */
    public interface OrderService {
    
        void save();
    
        Integer delete(Integer param);
    }
    public class OrderServiceImpl implements OrderService {
    
        @Override
        public void save() {
            System.out.println("加入...");
        }
    
        @Override
        public Integer delete(Integer param) {
            System.out.println("删除...");
            return param;
        }
    }
    • 定义Advice
    /**
     * 实现MethodInterceptor接口定义围绕通知
     *
     * @author jifang
     * @since 16/3/6 下午2:54.
     */
    public class ConcreteInterceptor implements MethodInterceptor {
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("前置通知 -> ");
    
            Object result = invocation.proceed();
    
            System.out.println("<- 后置通知");
    
            return result;
        }
    }

    Spring手动代理

    • 配置代理
      Spring最原始的AOP支持, 手动指定目标对象与通知(没有使用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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- target -->
        <bean id="service" class="com.fq.service.impl.OrderServiceImpl"/>
        <!-- advice -->
        <bean id="advice" class="com.fq.advice.ConcreteInterceptor"/>
    
        <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="service"/>
            <property name="interceptorNames" value="advice"/>
            <property name="proxyTargetClass" value="false"/>
        </bean>
    </beans>
    • Client
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
    public class AOPClient {
    
        @Autowired
        // 必须指定使用代理对象名称, 否则不予代理
        @Qualifier("serviceProxy")
        private OrderService service;
    
        @Test
        public void client() {
            service.save();
            service.delete(88);
        }
    }

    这样的方式的缺陷在于每一个Target都必须手动指定ProxyFactoryBean对其代理(不能批量指定),并且这样的方式会在Spring容器中存在两份Target对象(代理前/代理后),浪费资源,且easy出错(比方没有指定@Qualifier).


    Spring自己主动代理 - 引入AspectJ

    通过AspectJ引入Pointcut切点定义

    • Target/Advice同前
    • 定义切面表达式
      通过execution函数定义切点表达式(定义切点的方法切入)
      execution(<訪问修饰符> <返回类型><方法名>(<參数>)<异常>)
      如:
      1) execution(public * *(..)) # 匹配全部public方法.
      2) execution(* com.fq.dao.*(..)) # 匹配指定包下全部类方法(不包括子包)
      3) execution(* com.fq.dao..*(..)) # 匹配指定包下全部类方法(包括子包)
      4) execution(* com.fq.service.impl.OrderServiceImple.*(..)) # 匹配指定类全部方法
      5) execution(* com.fq.service.OrderService+.*(..)) # 匹配实现特定接口全部类方法
      6) execution(* save*(..)) # 匹配全部save开头的方法
    <?

    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"> <!-- target --> <bean id="service" class="com.fq.service.impl.OrderServiceImpl"/> <!-- advice --> <bean id="advice" class="com.fq.advice.ConcreteInterceptor"/> <!-- 配置切面 : proxy-target-class确定是否使用CGLIB --> <aop:config proxy-target-class="true"> <!-- aop:pointcut : 切点定义 aop:advisor: 定义Spring传统AOP的切面,仅仅支持一个pointcut/一个advice aop:aspect : 定义AspectJ切面的,能够包括多个pointcut/多个advice --> <aop:pointcut id="pointcut" expression="execution(* com.fq.service.impl.OrderServiceImpl.*(..))"/> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> </aop:config> </beans>

    • Client同前

    AspectJ AOP

    AspectJ是一个基于Java的AOP框架,提供了强大的AOP功能,其它非常多AOP框架都借鉴或採纳了AspectJ的一些思想,Spring2.0以后添加了对AspectJ切点表达式支持(如上),并在Spring3.0之后与AspectJ进行了非常好的集成.
    在Java领域,AspectJ中的非常多语法结构基本上已成为AOP领域的标准, 他定义了例如以下几类通知类型:

    通知 接口 描写叙述
    前置通知 @Before 相当于BeforeAdvice
    后置通知 @AfterReturning 相当于AfterReturningAdvice
    围绕通知 @Around 相当于MethodInterceptor
    抛出通知 @AfterThrowing 相当于ThrowAdvice
    引介通知 @DeclareParents 相当于IntroductionInterceptor
    终于final通知 @After 不管是否异常,该通知都会运行

    新版本号Spring,建议使用AspectJ方式开发以简化AOP配置.


    AspectJ-XML-AOP

    使用AspectJ编写Advice无需实现不论什么接口,并且能够将多个通知写入一个切面类.

    前置通知

    • 定义通知
    /**
     * @author jifang
     * @since 16/3/3 下午5:38.
     */
    public class Aspect {
    
        /**
         * 无返回值
         */
        public void before1() {
            System.out.println("前置增强before1");
        }
    
        /**
         * 还能够传入连接点參数 JoinPoint
         *
         * @param point
         */
        public void before2(JoinPoint point) {
            System.out.printf("前置增强before2 %s%n", point.getKind());
        }
    }
    • 装配
    <?

    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" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.fq.service"/> <!-- 配置切面通知 --> <bean id="advice" class="com.fq.advice.Aspect"/> <!-- AOP切面配置 --> <aop:config> <aop:aspect ref="advice"> <aop:pointcut id="pointcut" expression="execution(* com.fq.service.impl.OrderServiceImpl.*(..))"/> <aop:before method="before1" pointcut-ref="pointcut"/> <aop:before method="before2" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>

    • 前置通知小结
      • 前置通知会保证在目标方法运行前运行;
      • 前置通知默认不能阻止目标方法运行(但假设通知抛出异常,则目标方法无法运行);
      • 能够通过JoinPoint參数获得当前拦截对象和方法等信息.

    后置通知

    • 定义通知
    public void afterReturning(JoinPoint point, Object result) {
        System.out.printf("后置增强, 结果为 %s%n", result);
    }
    • 装配
    <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/>

    后置通知能够获得方法返回值,但在配置文件定义返回值參数名必须与后置通知方法參数名一致(如result).


    围绕通知

    • 定义通知
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.printf("围绕前置增强 method: %s, args: %s%n", point.toShortString(), Arrays.toString(point.getArgs()));
    
        Object result = point.proceed(point.getArgs());
    
        System.out.printf("围绕后置增强 result: %s%n", result);
    
        return result;
    }
    • 装配
    <aop:around method="around" arg-names="point" pointcut-ref="pointcut"/>

    围绕通知能够实现不论什么通知的效果, 甚至能够阻止目标方法的运行.


    抛出通知

    • 定义通知
    private static final Logger LOGGER = LoggerFactory.getLogger(Aspect.class);
    
    public void afterThrowing(JoinPoint point, Throwable ex) {
        String message = new StringBuilder("method ").append(point.getSignature().getName()).append(" error").toString();
        System.out.println(message);
    
        LOGGER.error("{},", message, ex);
    }
    • 装配
    <aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>

    throwing属性指定异常对象名, 该名称应和方法定义參数名一致.


    终于通知

    • 定义通知
    public void after(JoinPoint point) {
        System.out.println("终于通知, 释放资源");
    }
    • 装配
    <aop:after method="after" pointcut-ref="pointcut"/>

    不管目标方法是否出现异常,该通知都会运行(相似finally代码块, 应用场景为释放资源).


    AspectJ-Annotation-AOP

    @AspectJ是AspectJ 1.5新增功能,能够通过JDK注解技术,直接在Bean类中定义切面.
    AspectJ提前定义的注解有:@Before/@AfterReturning/@Around/@AfterThrowing/@DeclareParents/@After.描写叙述同前.
    使用AspectJ注解AOP须要在applicationContext.xml文件里开启注解自己主动代理功能:

    <?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" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 批量扫描@Component --> <context:component-scan base-package="com.fq"/> <!-- 启用注解自己主动代理@Aspect--> <aop:aspectj-autoproxy/> </beans>

    • OrderService/Client同前

    @Before

    • Aspect
    /**
     * @Aspect: 指定是一个切面
     * @Component: 指定能够被Spring容器扫描到
     */
    @Aspect
    @Component
    public class CustomAspect {
    
        @Before("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
        public void before(JoinPoint point) {
            System.out.printf("前置增强before2 %s%n", point.getKind());
        }
    }

    @AfterReturning

    @AfterReturning(value = "execution(* com.fq.service.impl.OrderServiceImpl.d*(..))", returning = "result")
    public void afterReturning(JoinPoint point, Object result) {
        System.out.printf("后置增强, 结果为 %s%n", result);
    }

    @Around

    @Around("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = point.proceed(point.getArgs());
        long time = System.currentTimeMillis() - start;
    
        System.out.printf("method %s invoke consuming %d ms%n", point.toLongString(), time);
    
        return result;
    }

    假设不调用ProceedingJoinPointproceed方法,那么目标方法就不运行了.


    @AfterThrowing

    @AfterThrowing(value = "execution(* com.fq.service.impl.OrderServiceImpl.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint point, Throwable ex) {
        String message = new StringBuilder("method ").append(point.getSignature().getName()).append(" error").toString();
        System.out.println(message);
    
        LOGGER.error("{},", message, ex);
    }

    @After

    @After("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
    public void after(JoinPoint point) {
        System.out.println("终于通知, 释放资源");
    }

    @Pointcut定义切点

    对于反复的切点,能够使用@Pointcut进行定义, 然后在通知注解内引用.

    • 定义切点方法
      无參/无返回值/方法名为切点名:
    /**
     * @author jifang
     * @since 16/3/4 上午11:47.
     */
    public class OrderServicePointcut {
    
        @Pointcut("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
        public void pointcut() {
        }
    }
    • 引用切点
      在Advice上像调用方法一样引用切点:
    @After("OrderServicePointcut.pointcut()")
    public void after(JoinPoint point) {
        System.out.println("终于通知, 释放资源");
    }

    1) 假设切点与切面在同一个类内, 可省去类名前缀;
    2) 当须要通知多个切点时,能够使用||/&&进行连接.


    小结

    通知 描写叙述
    前置通知 权限控制(少用)
    后置通知 少用
    围绕通知 权限控制/性能监控/缓存实现/事务管理
    异常通知 发生异常后,记录错误日志
    终于通知 释放资源

  • 相关阅读:
    P3391 【模板】文艺平衡树(Splay)
    P4198 楼房重建
    P1491 集合位置
    P3957 跳房子
    P4016 负载平衡问题
    bzoj1077: [SCOI2008]天平 差分约束
    bzoj1151: [CTSC2007]动物园zoo 状压dp
    bzoj1076: [SCOI2008]奖励关 状压dp
    bzoj1226: [SDOI2009]学校食堂Dining 状压dp
    bzoj1879: [Sdoi2009]Bill的挑战 状压dp
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/7101406.html
Copyright © 2020-2023  润新知