程序员

Spring AOP基础

2018-05-13  本文已影响0人  小螺钉12138

1.AOP概述

1.1.AOP到底是什么

AOP只适合那些具有横切面逻辑的应用场合,如性能监测,访问控制,事务管理及日志记录

Aop实例的github地址

1.2.AOP术语
1.3.AOP的实现者

2.基础知识

Spring AOP使用动态代理技术在运行时织入增强代码,Spring AOP使用两种代理机制:一种
基于JDK的动态代理,另一种是基于CGLib的动态代理。之所以需要两种代理机制,是因为很大程度上JDK本身只提供接口的代理,而不支持类的代理。

所有实例的代码地址
Aop实例的github地址

2.1.带有横切面逻辑的实例

以下是通过代码实现,当某个方法需要进行性能监视时,必须要调整方法代码,在方法体前后分别添加开启性能监视和结束性能监视的代码。

public interface ForumService {
    public void removeTopic(int topicID);


    public void removeForum(int topicID);

}

package com.aop.impl;

public class ForumServiceImpl implements ForumService {
    public void removeTopic(int topicID){
        //开始对该方法进行性能监视
        PerformanceMonitor.begin("removeTopic");
        System.out.println("模拟删除论坛topic记录:"+topicID);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //结束对该方法的性能监视
        PerformanceMonitor.end();
    }

    public void removeForum(int topicID){
        //开始对该方法进行性能监视
        PerformanceMonitor.begin("removeForum");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("模拟删除Forum记录:"+topicID);
        //结束对该方法的性能监视
        PerformanceMonitor.end();
    }
}

public class MethodPerformance {

    private long begin;
    private long end;
    private String serviceMethod;

    public MethodPerformance(String serviceMethod) {
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();//记录目标类方法开始执行点的开始时间

    }

    public void printPerformance() {
        end = System.currentTimeMillis();//获取目标类方法执行完成后的系统时间,进而计算出目标类方法的执行时间
        long elapse = end - begin;
        System.out.println(serviceMethod+"花费"+elapse+"毫秒");
    }

}

public class PerformanceMonitor {
    //通过一个ThreadLocal保存与调用线程相关的性能监视信息
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<>();

    //启动对某一目标方法的性能监视
    public static void begin(String method){
        System.out.println("begin monitor....");
        MethodPerformance mp=new MethodPerformance(method);
        performanceRecord.set(mp);
    }

    public static void end(){
        System.out.println("end monitor");
        MethodPerformance mp=performanceRecord.get();
        //打印出方法性能监视的结果信息
        mp.printPerformance();
    }
}

public class TestForumService {
    public static void main(String[] args) {
        ForumService forumService=new ForumServiceImpl();
        forumService.removeForum(10);
        forumService.removeTopic(20);
    }
}



2.2.JDK动态代理

public class PerformanceHandler implements InvocationHandler {

    private Object target;



    public  PerformanceHandler(Object target) {//target为目标业务类
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName());
        Object obj=method.invoke(target,args);//通过反射调用业务类的目标方法
        PerformanceMonitor.end();
        return obj;
    }

}

public class TestForumService {
    public static void main(String[] args) {
        ForumService target = new ForumServiceImpl();

        PerformanceHandler handler = new PerformanceHandler(target);

        ForumService proxy= (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);

        proxy.removeForum(10);
        proxy.removeTopic(1020);
    }
}

2.3.CGLib动态代理

由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final或private方法进行代理

public class CGLibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);//设置需要创建子类的类
        enhancer.setCallback(this);
        return enhancer.create();//通过字节码技术动态创建子类实例
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {//拦截父类所有方法
        PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
        Object result=methodProxy.invokeSuper(obj,objects);//通过反射调用业务类的目标方法
        PerformanceMonitor.end();
        return result;
    }
}

public class TestForumService {

    public static void main(String[] args) {
        CGLibProxy proxy=new CGLibProxy();
        ForumServiceImpl forumService= (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);//通过动态生成子类的方式创建 代理类
        forumService.removeTopic(10);
        forumService.removeForum(1024);

    }
}
2.4.AOP联盟
2.5.代理知识小结

虽然通过上面的PerformanceHandler或CGLibProxy实现了性能监视横切逻辑的动态织入,但是这种实现方式存在3个明显需要改进的地方

JDK动态代理所创建的代理对象,在Java 1.3下,性能差强人意,研究表明CGLib所创
建的动态代理对象的性能依旧比JDK所创建的动态代理对象的性能高不少(大概10倍),但是CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍)。对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理技术,反之则适合采用JDK动态代理技术

3.创建增强类

Spring使用增强类定义了横切面逻辑,同时由于Spring只支持方法连接点,增强还包括方法在哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,又包含部分连接点的信息

3.1.增强类型
3.2.前置增强

通过实现MethodBeforeAdvice接口


public interface Waiter {

     public void greetTo(String name);
     public void serveTo(String name);
}

public class NativeWaiter implements Waiter {
    @Override
    public void greetTo(String name) {
        System.out.println("greet to "+name+"...");
    }

    @Override
    public void serveTo(String name) {
        System.out.println("serving to "+name+"...");
    }
}

public class GreetingBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object o) throws Throwable {//在目标方法调用前使用
        String clientName= (String) args[0];
        System.out.println("How are you! Mr."+clientName);

    }
}

public class BeforeAdviceTest {

    @Test
    public void before() {
        System.out.println("测试代码");
        Waiter target = new NativeWaiter();
        BeforeAdvice advice=new GreetingBeforeAdvice();

        //spring提供代理工厂
        ProxyFactory pf=new ProxyFactory();
        //设置代理目标
        pf.setTarget(target);
        //为代理目标添加增强
        pf.addAdvice(advice);
        //生成代理实例
        Waiter proxy= (Waiter) pf.getProxy();
        proxy.greetTo("John");
        proxy.serveTo("Tom");
    }

}

上面代码中使用ProxyFactory代理,ProxyFactory内部使用的就是JDK或者是CGLib动态代理技术将增强应用到目标类中的。如果通过ProxyFactory的setInTerfaces方法指定目标接口进行代理,则使用JDK代理,如果是针对类代理,则使用CGLib代理。此外还可以通过setOptimize方法让ProxyFactory启动优化代理方式。

3.3.后置增强

后置增强和前置增强的代码类似,前置增强要实现接口MethodBeforeAdvice,后置增强则要实现接口AfterReturningAdvice

public class GreetingAfterAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("enjoy yourself!");
    }
}
3.4.环绕增强

环绕增强综合实现了前置、后置增强的功能

public class GreetingInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {//截获目标类方法的执行,并在前后添加横切逻辑
        Object[] args = methodInvocation.getArguments();
        String clientName = (String) args[0];
        System.out.println("How are you !Mr."+clientName+".");//在目标方法前执行
        Object obj=methodInvocation.proceed();//通过反射机制调用目标方法
        System.out.println("Please enjoy yourself!");
        return obj;
    }
}

3.5.异常抛出异常

异常抛出异常最适合的应用场景是事务管理,当参与事务的某个DAO发生异常时,事务管理器就必须回滚事务

ThrowAdvice异常抛出增强接口没有定义任何方法,它是一个标签接口,在运行期Spring使用反射机制自行判断,必须采用以下签名形式定义异常抛出的增强方法。

public class TransactionManager implements ThrowsAdvice {

    //定义增强逻辑
    public void afterThrowing(Method method,Object[] args,Object target,Exception ex) throws Throwable{
        System.out.println("--------------");
        System.out.println("method:"+method.getName());
        System.out.println("抛出异常:"+ex.getMessage());
        System.out.println("成功回滚事务!");

    }
}

3.6.引介增强

引介增强是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的,通过引介类增强,可以为目标类添加一个接口的实现,即原来目标类未实现某个方法接口,通过引介增强可以为目标类创建实现某接口的代理。

public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable {

    private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<>();


    @Override
    public void setMonitorActive(boolean active) {
        MonitorStatusMap.set(active);
    }

    //拦截方法
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object obj = null;
        //对于性能监视可控代理,通过判断其状态决定是否开启性能监控功能
        if(MonitorStatusMap.get()!=null&&MonitorStatusMap.get()){
            PerformanceMonitor.begin(mi.getClass().getName()+"."+mi.getMethod().getName());
            obj=super.invoke(mi);
            PerformanceMonitor.end();
        }else {
            obj=super.invoke(mi);
        }
        return obj;
    }
}

4.创建切面

增强被织入目标类的所有方法中,假设我们希望有选择地织入目标类的某些特定的方法中,就需要使用切点进行目标连接点的定位。

Spring通过Pointcut接口来描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。

4.1.切点类型
4.2.切面类型
4.3.静态普通方法名匹配切面
public class GreetingBeforeAdvisor extends StaticMethodMatcherPointcutAdvisor {
    @Override
    public boolean matches(Method method, Class<?> clazz) {
        return "greetTo".equals(method.getName());
    }

    public ClassFilter getClassFilter(){
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return Waiter.class.isAssignableFrom(clazz);
            }
        };
    }
}

4.4.静态正则表达式方法切面
<!--静态正则表达式方法匹配切面-->
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
    p:advice-ref="greetingBefore">
 <property name="patterns">
  <list>
    <value>.*greet.*</value><!--定义可匹配模式串,“.*greet.*”-->
  </list>
</property>
</bean>

<bean id="waiter1"class="org.springframework.aop.framework.ProxyFactoryBean"
    p:proxyTargetClass="true"
    p:interceptorNames="regexpAdvisor"
    p:target-ref="waiterTarget"
/>

4.5.动态切面

Spring会在创建动态代理织入切面时,对目标类中所有方法进行静态切点检查;在生成织入切面的代理对象后,第一次调用代理类的每一个方法时都会进行一次静态切点检查,如果本次检查就能从候选者列表中将该方法排除,则以后对该方法的调用就不再执行静态切点检查;对于那些在静态切点检查时匹配的方法,在后续调用该方法时,将执行动态切点检查

public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {

    private static List<String> specialClientList=new ArrayList<>();

    static{
        specialClientList.add("Jhon");
        specialClientList.add("Tom");
    }


    public ClassFilter getClassFilter(){
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                System.out.println("调用getClassFilter()对"+clazz.getName()+"静态检查.");

                return Waiter.class.isAssignableFrom(clazz);
            }
        };
    }

    @Override
    public boolean matches(Method method, Class<?> clazz, Object... objects) {//对方法进行动态切点检查
        System.out.println("调用动态检查方法对"+clazz.getName()+"类的"+method+"动态检查.");
        String clientName= (String) objects[0];
        return specialClientList.contains(clientName);
    }

    @Override
    public boolean matches(Method method, Class<?> clazz) {//对方法进行静态切点检查
        System.out.println("调用静态检查方法对"+clazz.getName()+"类的"+method+"静态检查.");
        return "greetTo".equals(method.getName());
    }
}

<!--动态切面-->
<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
  <bean class="cn.com.dynamicPoint.GreetingDynamicPointcut"/>
</property>
<property name="advice">
  <bean class="cn.com.beforeEnhancer.GreetingBeforeAdvice"/>
</property>
</bean>

<bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean"
    p:interceptorNames="dynamicAdvisor"
    p:target-ref="waiterTarget"
    p:proxyTargetClass="true"
/>

每次调用代理对象的任何一个方法,都会执行动态切点检查,这将导致很大的性能问题,所以,在定义动态切点时,切勿忘记同时覆盖getClassFilter()matches(Metho method,Class clazz)方法,通过静态切点检查排除大部分方法

4.6.流程切面

流程切点代表由某个方法直接或间接发起调用的其他方法。流程切面和动态切面从某种程度上来说可以算是一类切面,因为二者都需要在运行期判断动态的环境。对于流程切面来说,代理对象在每次调用目标类方法时,都需要判断方法调用堆栈中是否有满足流程切点要求的方法,因此,和动态切面一样,流程切面对性能的影响也很大。

public class WaiterDelegate {

    private Waiter waiter;

    public void service(String clientName){//waiter的方法通过该方法发起调用
        waiter.serveTo(clientName);
        waiter.greetTo(clientName);
    }

    public void setWaiter(Waiter waiter) {
        this.waiter = waiter;
    }
}

 <!--流程切面-->
  <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
    <constructor-arg type="java.lang.Class">
      <value>cn.com.flowPoint.WaiterDelegate</value>
    </constructor-arg>
    <constructor-arg type="java.lang.String">
      <value>service</value>
    </constructor-arg>
  </bean>

  <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut-ref="controlFlowPointcut"
        p:advice-ref="greetingBefore"
  />

  <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="controlFlowAdvisor"
        p:target-ref="waiterTarget"
        p:proxyTargetClass="true"
  />
4.7.符合切点切面

有时,一个切点可能难以描述目标连接点的信息,比如在前面的例子中,假设我们希望WaiterDelegate#service发起调用且被调用的方法是Waiter#greetTo时才织入增强,这个切点就是符合切点。

public class GreetingComposablePointcut {

    public Pointcut getInTersectionPointcut(){
        ComposablePointcut cp=new ComposablePointcut();//创建一个复合切点
        Pointcut pt1=new ControlFlowPointcut(WaiterDelegate.class,"service");//创建一个流程切点
        NameMatchMethodPointcut pt2=new NameMatchMethodPointcut();//创建方法名切点
        pt2.addMethodName("greetTo");
        return cp.intersection(pt1).intersection((Pointcut) pt2);
    }
}

<!--复合切点切面-->
  <bean id="gcp" class="cn.com.composablePointcut.GreetingComposablePointcut"></bean>
  <bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut="#{gcp.inTersectionPointcut}"
        p:advice-ref="greetingBefore"
  />
  <!--引用gcp.inTersectionPointcut方法返回的复合切点-->

  <bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean"
        p:interceptorNames="composableAdvisor"
        p:target-ref="waiterTarget"
        p:proxyTargetClass="true"
  /><!--使用复合切点-->
4.8.引介切面

5.自动创建代理

在前面的例子中,都通过ProxyFactoryBean创建织入切面的代理,每个需要被代理的Bean都需要使用一个ProxyFactoryBean进行配置,虽然可以使用父子<bean>进行改造,但还是很麻烦,Spring提供了自动代理机制,让容器自动生成代理,可以从繁琐的配置中解放出来。Spring使用BeanPostProcessor自动完成这项工作

5.1.实现类介绍

这些基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则自动在容器实例化Bean时为匹配的Bean生成自动代理实例。

5.2.BeanNameAutoProxyCreator
<!--自动代理BeanNameAutoProxyCreator-->
  <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
        p:beanNames="*er"
        p:interceptorNames="greetingBefore"
        p:optimize="true"
  />

5.3.DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator能够扫描容器中的Advisor,并将Advisor自动织入匹配的目标Bean中,即为匹配的目标Bean自动创建代理

<bean id="regexpAdvisor1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
        p:patterns=".*greet.*"
        p:advice-ref="greetingBefore"
  />
  <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
5.4.AOP无法增强疑难问题剖析

因为AOP底层的实现原理基于JDK和CGLib动态代理,在JDK动态代理中通过接口来实现方法拦截,所以必须保证要拦截的目标方法在接口中有定义,在CGLib动态代理中通过动态生成代理子类来实现方法拦截,所以必须要确保拦截的目标方法可被子类访问,也就是目标方法必须定义为非final,即非私有实例方法

<bean id="regexpAdvisor1" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
        p:pattern=".*To.*"
        p:advice-ref="greetingBefore"
  />
  <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
  <!--修改一下自动代理的正则表达式,所有方法中包含To的都要织入增强,-->
  
  
  public class NativeWaiter implements Waiter {
    @Override
    public void greetTo(String name) {
        //throw new RuntimeException("运行异常");
        System.out.println("greet to "+name+"...");
    }

    @Override
    public void serveTo(String name) {
        greetTo(name);
        System.out.println("serving to "+name+"...");
    }
    
    //只调用serveTo方法,serverTo和greetTo方法能否可以同时增强
    /**
    console打印的日志如下
    
    How are you! Mr.Jhon
    greet to Jhon...
    serving to Jhon...
    
    表明被调用的方法没有织入增强
    **/
}

>在调用内部方法时,让其通过代理类调用内部的方法来解决内部方法不能被代理的情况,因此,需要让原来的Waiter实现一个可注入自身代理类的接口

注:通过配置代理类来调用内部方法还是不能够给方法织入增强,关于这点如果有大神知道,请告知,我后面也会持续研究这个问题,如果有进展会第一时间更新文章

Aop实例的github地址

上一篇 下一篇

猜你喜欢

热点阅读