• 简单直白的去理解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的注解可以非常容易的定义一个切面,不需要实现任何的接口

  • 相关阅读:
    hdu 1524
    hdu 1536
    转载一篇博弈博弈入门的好文章
    nim 博弈
    WPF 从Main函数启动
    C# map network drive sample
    Predicate 学习
    WPF 绑定到附加属性 绑定到只读属性
    WPF 带有watermark的文本输入框
    使用Windows服务发布WCF服务
  • 原文地址:https://www.cnblogs.com/captains/p/3628240.html
Copyright © 2020-2023  润新知