zoukankan      html  css  js  c++  java
  • 简单直白的去理解AOP,了解Spring AOP,使用 @AspectJ

    AOP = Aspect Oriental Programing  面向切面编程

    文章里不讲AOP术语,什么连接点、切点、切面什么的,这玩意太绕,记不住也罢。旨在以简单、直白的方式理解AOP,理解Spring AOP, 应用 @AspectJ。

    1. 什么是AOP?
    2. Spring AOP 实现机制
    3. 使用Spring AOP,并通过xml配置(这个稍微看看就行了,你不一定用它)
    4. 使用@AspectJ (未完成)

    1、什么是AOP?

    方法1 方法2 方法3
    A A A
    代码x 代码y 代码z
    B B B

    从纵向看,方法1、2、3 都执行了相同的A、B代码,这样重复代码是很无聊的。

    一个典型的场景就是:开启事务,更新表里数据,提交事务;  开启事务,删除表里数据,提交事务。

    所以我们从横向来,把重复代码抽取出来,变为

    A A A
    方法1(代码x) 方法2(代码y) 方法3(代码z)
    B B B

    AOP希望将A、B 这些分散在各个业务逻辑中的相同代码,通过横向切割的方式抽取到一个独立的模块中,还业务逻辑类一个清新的世界。

    当然,将这些重复性的横切逻辑独立出来很容易,但是如何将独立的横切逻辑 融合到 业务逻辑中 来完成和原来一样的业务操作,这是事情的关键,也是AOP要解决的主要问题。

    2.Spring AOP 实现机制

    Spring AOP使用动态代理技术在运行期织入增强的代码,使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理

    织入、增强 是AOP的两个术语,织入增强的代码简单点就是在你的代码上插入另一段代码。

    JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy 和 InvocationHandler(接口)。

    直接上代码

    package test;
    
    public interface CalcService {
        public void add(int x, int y);
    }
    CalcService 接口
    package test;
    
    public class CalcServiceImpl implements CalcService{
        public void add(int x, int y) {
            System.out.println("结果为" + (x + y));
        }
    }
    CalcServiceImpl 实现类
    package test;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class CalcHandler implements InvocationHandler {
        
        public Object target;
        
        
        public CalcHandler(Object target){
            this.target = target;
        }
        
        /** 
         * 实现接口的方法
         * @param proxy 最终生成的代理实例 
         * @param method 被代理目标(也就是target)的某个具体方法
         * @param args   某个具体方法的入参参数
         * @return Object 方法返回的值*/
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("*******调用方法前执行代码******");
            Object obj = method.invoke(this.target, args);
            System.out.println("*******调用方法后执行代码******");
            return  obj;
        }
    
    }
    CalcHandler 实现InvocationHandler
    package test;
    
    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args){
            long start = System.nanoTime();
            
            CalcService target = new CalcServiceImpl();
            CalcHandler handler = new CalcHandler(target);
            CalcService calcProxy = (CalcService)Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), 
                    handler);
            System.out.println("创建时间:" + (System.nanoTime()-start));
            
            start = System.nanoTime();
            calcProxy.add(2, 3);
            System.out.println("执行时间:" + (System.nanoTime()-start));
    
        }
    }
    Test 测试

    执行结果为

    *******调用方法前执行代码******
    结果为2
    *******调用方法后执行代码******

    但是JDK动态代理有一个限制,即它只能为接口创建代理实例*******************************

    看Proxy的方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

    interfaces 是需要代理实例实现的接口列表

    那么对于一个没有通过接口定义业务方法的类,怎么创建代理实例?

    CGLib

    CGLib采用非常底层的字节码技术,可以在运行时为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。

    package test;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CalcProxy implements MethodInterceptor {
        private Enhancer enhancer = new Enhancer();
        
        public Object getProxy(Class clazz){
            enhancer.setSuperclass(clazz); // 设置需要被代理的类 target
            enhancer.setCallback(this);
            return enhancer.create(); // 通过字节码技术动态创建子类
        }
        
        // 拦截父类所有方法的调用
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("*******调用方法前执行代码******");
            Object result = proxy.invokeSuper(obj, args); // 通过代理类调用父类中的方法
            System.out.println("*******调用方法后执行代码******");
            return result;
        }
    
    }
    CalcProxy 实现MethodInterceptor接口
    package com.ycp.framework.test.proxyPattern.sample2;
    
    public class Test2 {
        public static void main(String[] args) {
            CalcProxy proxy = new CalcProxy();
            CalcServiceImpl calcImpl = (CalcServiceImpl)proxy.getProxy(CalcServiceImpl.class);
            calcImpl.add(2, 3);
        }
    }
    Test2 测试类

    之后对两者做了一个效率对比

    我在自己本机上通过System.nanoTime()对两者做了记录,结果如下

                            JDK动态代理                  CGLiib
    创建代理对象时间         720 1394                    1 3473 7007       (时间单位为纳秒)
    
    代理对象执行方法时间      97 7322                     15 2080

    一个创建花费时间长,一个执行时间长。

    3.使用 Spring AOP,并通过XML配置

    在Spring中,定义了 AopProxy接口,并提供了两个final类型的实现类

    Cglib2AopProxy          JdkDynamicAopProxy

    以一个前置增强为例,也就是说在目标方法执行前执行的代码

    package test;
    
    import java.lang.reflect.Method;
    
    
    import org.springframework.aop.MethodBeforeAdvice;
    import org.springframework.aop.framework.ProxyFactory;
    
    public class CalcBeforeAdvice implements MethodBeforeAdvice {
    
        public void before(Method method, Object[] args, Object obj) throws Throwable {
            System.out.println("*******调用目标方法前执行代码******");
    
        }
        
        public static void main (String [] args){
            CalcService target = new CalcServiceImpl();
            
            CalcBeforeAdvice advice = new CalcBeforeAdvice();
            
            // 1 spring 提供的代理工厂
            ProxyFactory pf = new ProxyFactory();
            // 2 设置代理目标
            pf.setInterfaces(target.getClass().getInterfaces());// 指定对接口进行代理,将使用JdkDynamicAopProxy
            // 下面两行操作,有任意一行,都将使用Cglib2AopProxy
            pf.setOptimize(true);// 启用代理优化,将使用Cglib2AopProxy
            pf.setProxyTargetClass(true); // true表示对类进行代理,将使用Cglib2AopProxy
            
            pf.setTarget(target);
            // 3 为代理目标添加增强
            pf.addAdvice(advice);
            // 4 生成代理实例
            
            CalcService proxy  = (CalcService) pf.getProxy();
            System.out.println(proxy.getClass().getName());
            proxy.add(2, 3);
        }
    }
    通过Spring实现前置增强

    以上通过ProxyFactory创建代理,下面我们通过Spring配置来声明代理

    <bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
    <bean id="calcTarget" class="test.CalcServiceImpl"/>
    <bean id="calcProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:proxyInterfaces="test.CalcService" //指定代理接口,如果有多个接口,可用,隔开
      p:interceptorNames="calcBeforAdvice"//指定使用的增强,引用第一行,如果有多个可用,隔开
      p:target-ref="calcTarget"//指定对那个Bean进行代理,引用第二行
      p:proxyTargetClass="true" //指定是否对类进行代理
      p:singleton="true"//指定返回的代理是否单实例,默认为true
    />

     除了前置增强BeforeAdvice,还有后置增强AfterReturningAdvice、环绕增强MethodInterceptor、异常抛出增强

    ThrowsAdvice、及引介增强IntroductionInterceptor,均为接口。

    其中引介增强稍微强调一下,它会在目标类中增加一些新的方法和属性。

    到了这里,可能对AOP稍有些了解了,那我们简单说一下AOP的几个名词

    连接点Joinpoint:类初始化前、初始化后, 方法调用前、调用后,方法抛出异常后,这些特定的点,叫连接点。
    
    切点Pointcut:想想数据库查询,切点就是通过其所设定的条件找到对应的连接点。
    
    增强Advice:就是把代码加到某个连接点上。
    
    引介Introduction:一种特殊的增强,它为类增加一些属性和方法,假设某个业务类没有实现A接口,我们给它添加方法,让其成为A的实现类。
    
    织入Weaving:就是怎么将增强添加到连接点上。
    
             三种织入方式:1、编译期织入,要求使用特殊的JAVA编译器
    
            2.类装载期织入,要求使用特殊的类装载器
    
            3.动态代理织入,在运行期为目标类添加增强
    
            Spring采用动态代理,而AspectJ采用编译期织入和类装载期织入。
    
    目标对象Target:也就是你自己的业务类,AOP就是对这个类做增强、引介。
    
    代理Proxy: 目标对象被织入增强后产生的结果类。
    
    切面:由切点和增强(引介)组成,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑(也就是代码)织入到切面所指定的连接点(也就是代码往哪加)中。

    看完了名词,再看完之前的代码,我们发现增强被织入到了目标类的所有方法中(XX的,都木有选择的余地....)

    现在我们要对某些类的某些方法织入增强,那这时候就涉及到切点概念了

    以如下为例:我只想针对所有的以add开头的方法做处理

    静态普通方法名匹配

    package com.ycp.framework.test.proxyPattern.sample2;
    
    import java.lang.reflect.Method;
    
    import org.springframework.aop.ClassFilter;
    import org.springframework.aop.IntroductionInterceptor;
    import org.springframework.aop.ThrowsAdvice;
    import org.springframework.aop.framework.ProxyFactory;
    import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
    
    public class AddAdvisor extends StaticMethodMatcherPointcutAdvisor {
        
        //StaticMethodMatcherPointcutAdvisor 抽象类
        
        // 实现父类 matches方法
        public boolean matches(Method method, Class clazz) {
            //只匹配add方法
            return  0 == "add".indexOf(method.getName());
        }
        
    //    //切点类匹配规则为 CalcServiceImpl的类或子类,
    //    @Override
    //    public ClassFilter getClassFilter(){
    //        return new ClassFilter(){
    //            public boolean matches(Class clazz){
    //                return CalcServiceImpl.class.isAssignableFrom(clazz);
    //            }
    //        };
    //    }
        
        public static void main (String [] args){
            CalcService target = new CalcServiceImpl();
            CalcBeforeAdvice advice = new CalcBeforeAdvice();
            
            // 1 spring 提供的代理工厂
            ProxyFactory pf = new ProxyFactory();
            
            // 2 设置代理目标
            pf.setInterfaces(target.getClass().getInterfaces());// 指定对接口进行代理,将使用JdkDynamicAopProxy
            // 下面两行操作,有任意一行,都将使用Cglib2AopProxy
            pf.setOptimize(true);// 启用代理优化,将使用Cglib2AopProxy
            pf.setProxyTargetClass(true); // true表示对类进行代理,将使用Cglib2AopProxy
            
            pf.setTarget(target);
            
            // 3 为代理目标添加增强
            AddAdvisor advisor = new AddAdvisor();
            advisor.setAdvice(advice);
            
            pf.addAdvisor(advisor);
            
            // 4 生成代理实例
            CalcService proxy  = (CalcService) pf.getProxy();
            
            System.out.println(proxy.getClass().getName());
            proxy.add(2, 3);
        }
    
    }
    AddAdvisor 继承StaticMethodMatcherPointcutAdvisor

    通过Spring配置来定义切面

    <bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
    <bean id="calcTarget" class="test.CalcServiceImpl"/>
    <bean id="addAdvisor" class="test.AddAdvisor" 
       p:advice-ref="calcBeforAdvice"//向切面注入一个前置增强
     />
    
    <bean id="parent" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:interceptorNames="addAdvisor"
      p:proxyTargetClass="true" 
    />
    
    <bean id="calc" parent="parent" p:target-ref="calcTarget"/> //CalcServiceImpl的代理

    上面的忒麻烦,我们通过静态正则表达式来匹配

    <bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
    <bean id="calcTarget" class="test.CalcServiceImpl"/>
    <bean id="addRegexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" 
       p:advice-ref="calcBeforAdvice"//向切面注入一个前置增强 >
        <property name="patterns">
         <list>
          <value> .add*</value>//匹配模式串
        </list>
      </property> 
    </bean>
    <bean id="calc" class="org.springframework.aop.framework.ProxyFactoryBean"
       p:interceptorNames="addRegexpAdvisor"
        p:target-ref="calcTarget"
      p:proxyTargetClass="true" 
    />

    Spring提供了6种类型的切点,静态方法切点、动态方法切点、注解切点、表达式切点、流程切点,我能力有限,没有研究下去,仅以静态切点 StaticMethodMatcherPointcut 做个例子就算完事,啥时项目用到了啥时再研究吧。

    4.使用AspectJ

    Spring AOP应用是比较麻烦的,要实现这个那个接口,写这个那个XML描述,你头疼不?

    使用@AspectJ的注解可以非常容易的定义一个切面,不需要实现任何的接口

  • 相关阅读:
    SpringMVC中静态获取request对象 Spring中获取 HttpServletRequest对象【转载】
    springcloud 的loadbalancer 轮询算法切换方法 2021.4.3
    springboot项目启动增加图标
    rabbitmq 端口作用以及修改方法
    centos8 安装rabbitmq
    springcloud config client Value获取不到信息的问题的处理方法
    springcloud config配置git作为数据源然后启动报错 If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    Sublime Text的列模式如何操作
    centos8 安装redis
    jQuery简单的Ajax调用
  • 原文地址:https://www.cnblogs.com/captains/p/3628240.html
Copyright © 2011-2022 走看看