Spring知识学习(二)AOP面向切面编程

本文详细探讨了面向切面编程(AOP)的概念及其在软件开发中的应用,特别是通过动态代理技术实现AOP的过程。文章对比了JDK动态代理和CGLIB动态代理的优缺点,解释了如何使用这两种技术创建代理对象,并通过实例展示了如何在代码中应用AOP,包括拦截器的使用和Spring框架中的AOP实现。
摘要由CSDN通过智能技术生成

编程语言的发展历程从机器语言到面向过程语言,再到面向对象的语言(OOP),它的终极目标是能够以更自然、更灵活的方式来模拟世界。AOP 是发展到一定阶段的产物,它是 OOP 的有益补充 。 AOP 只适合那些具有横切逻辑的应用场景,比如性能监测 、访问控制 、事务管理以及日志记录等。

基本概念

面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。

  • 主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。
  • 主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
  • 主要思想:采用横向抽取机制,取代了传统纵向继承体系重复性代码。

可以想象这样一种场景,在一个银行管理系统中每一个功能模块都必须登录之后才能操作,每一个模块中都必须加上同样的代码段,而真正业务代码淹没在重复性的非业务性的代码之中 ,无疑使得代码更加臃肿。

AOP 会将这些分散在各个业务逻辑代码中的同类代码,通过横向切割的方式抽取到一个独立模块中。 AOP 要解决的主要问题是如何将这些独立的逻辑功能融合到业务逻辑中以完成和原来一样的业务流程 。

动态代理技术

AOP的底层使用动态代理技术来实现。主要包括两种方式:

  • 使用JDK动态代理实现
  • 使用CGLIB动态代理实现

在JDK动态代理中,我们必须使用接口,它是JDK自带的功能;而CGLIB则不需要,它是第三方提供的技术。

动态代理的实现主要分为两个步骤:

  1. 代理对象和真实对象建立代理关系
  2. 实现代理对象的代理逻辑方法

1、JDK动态代理

JDK动态代理是 java.lang.reflect 包提供的方式,它必须借助一个接口才能产生代理对象,所以先定义一个HelloWorld接口:

public interface HelloWorld {
    public void sayHelloWorld();
}

然后提供接口的实现类HelloWorldImpl来实现此接口

public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World");
    }
}

有了接口了其实现类就可以开始动态代理了,按照之前的步骤先在代理对象和真实对象建立代理关系,然后再实现代理对象的代理逻辑方法。

在JDK动态代理中,要实现代理逻辑类就必须去实现  java.lang.reflect.InvocationHandler 接口,它定义了一个 invoke 方法,并提供接口数组用于下挂代理对象,如下为一次简单的动态代理过程:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyExample implements InvocationHandler {

    //真实的对象
    private Object target = null;

    /*
     * 第一步,建立真实对象与代理对象之间的代理关系并返回代理对象
     * @param target真实的对象
     * @return 代理对象
     */
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
    }

    /*
     * 代理方法逻辑
     * @param proxy  --代理对象
     * @param method --当前调度方法Method实例
     * @param args   --当前方法参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入代理逻辑方法");
        System.out.println("在调度真实对象之前的服务");
        Object obj = method.invoke(target, args);//相当于调用sayHelloWorld方法
        System.out.println("在调度真实对象之后的服务");
        return obj;
    }
}

对于上述代码的解释:

第一步,建立代理对象和真实对象的关系

这里是通过bind方法实现的,方法里首先用类的成员属性 target 保存了真实的对象,然后通过如下代码生成对象:

Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);

解释这段代码前,首先需要了解一下 java.lang.reflect.Proxy 类,它提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。其静态 newProxyInstance 方法如下:

所以对于上述例子的newProxyInstance 方法中:

  1. target.getClass().getClassLoader()表示代理对象采用了 target 真实对象本身的类加载器。
  2. 代理类要实现的接口列表,也就是把动态代理对象下挂到哪些接口下,target.getClass().getInterfaces() 表示将代理对象放在 target 实现的接口下。HelloWorldImp 对象实现的接口显然就是 HelloWorld ,所以代理对象可以这样声明:HelloWorld proxy = xxxxx;
  3. 第三个参数是定义实现方法逻辑的代理类,this表示当前对象,它必须实现 InvocationHandler 接口的 invoke 方法,它就是代理逻辑的方法的现实方法。

第二步,实现代理逻辑方法

invoke 方法可以实现代理逻辑, 它的方法含义如下:

 Objectinvoke(Object proxy, Method method, Object[] args) 
          在代理实例上处理方法调用并返回结果。
  1. Object proxy:在其上调用方法的代理实例,也就是上述bind方法生成的对象。
  2. Method method: 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。(这里即为当前方法)
  3. Object[ ] args: 调度方法的参数。

当我们使用了代理对象的调度方法后,它就会进入到 invoke 方法里:

 Object obj = method.invoke(target, args);

这行代码相当于调用了真实对象的方法(sayHelloWorld方法),只不过是通过反射实现的。接下来可以对此代理逻辑类进行测试:

public static void testJdkProxy() {
        JdkProxyExample jdk = new JdkProxyExample();
        //绑定关系,因为挂在HelloWorld接口下,所以声明代理对象类型HelloWorld proxy
        HelloWorld proxy = (HelloWorld) jdk.bind(new HelloWorldImpl());
        //此时proxy对象已经是一个代理对象,它会进入到代理的逻辑方法invoke里
        proxy.sayHelloWorld();
}

首先通过bind方法绑定了代理关系,然后在代理对象调度 sayHelloWorld 方法时进入了代理的逻辑。

需要注意的是,在真实的代理逻辑类中(此处为JdkProxyExample类),此过程中的调度方法之前和之后都可以加入相关的逻辑代码,甚至可以根据情况选择要不要调用真实对象原来的方法,这就是后续会提到的拦截器的思想。

总结

JDK 动态代理的原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。简约步骤如下:

  1. 定义一个实现接口InvocationHandler的类
  2. 通过构造函数,注入被代理类
  3. 实现invoke( Object proxy, Method method, Object[] args)方法
  4. 在主函数中获得被代理类的类加载器
  5. 使用Proxy.newProxyInstance( )产生一个代理对象
  6. 通过代理对象调用各种方法

2、CGLIB 动态代理

JDK 动态代理必须提供接口才能使用,而CGLIB 动态代理 针对类实现代理对是否实现接口无要求。原理是对指定的类(非抽象类)生成一个子类,覆盖其中的方法,因为是继承,所以被代理的类或方法最好不要声明为final类型。

假设有一个没有实现任何接口的类 HelloConcrete 如下:

public class ReflectServiceImpl{
    public String sayHello(String str) {
        return "ReflectServiceImpl: " + str;
    }
}

因为没有实现接口该类无法使用JDK代理,通过CGLIB代理实现步骤如下:

  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
  2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
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 CglibProxyExample implements MethodInterceptor {
    /*
     * 生成CGLIB代理对象
     * @param cls -- Class类
     * @return Class类的CGLIB代理对象
     */
    public Object getProxy(Class cls) {
        // CGLIB enhancer增强类对象
        Enhancer enhancer = new Enhancer();
        // 设置增强类型
        enhancer.setSuperclass(cls);
        // 定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor方法
        enhancer.setCallback(this);
        // 生成并返回代理对象
        return enhancer.create();
    }

    /*
     * 代理逻辑方法
     * @param proxy       代理对象
     * @param method      方法
     * @param args        方法参数
     * @param methodProxy 方法代理
     * @return 代理逻辑返回
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Object proxy, Method method, 
            Object[] args, MethodProxy methodProxy) throws Throwable {
        System.err.println("调用真实对象前可以做..");
        // CGLIB反射调用真实对象方法
        Object result = methodProxy.invokeSuper(proxy, args);
        System.err.println("调用真实对象后可以做..");
        return result;
    }
}

上述代码中,我们通过CGLIB的加强者Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;

通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是ReflectServiceImpl的具体方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

对于CglibProxyExample 代理逻辑类进行测试:

public void tesCGLIBProxy() {
        CglibProxyExample cpe = new CglibProxyExample();
        ReflectServiceImpl obj = (ReflectServiceImpl)
cpe.getProxy(ReflectServiceImpl.class);
        obj.sayHello("罗辑");
}

3、对于两种代理方式的选择

上文介绍了Java两种常见动态代理机制的用法和原理,JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理;CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。

  1. 如果目标对象实现了接口,默认情况下回采用JDK的动态代理实现AOP,也可以强制使用cglib实现AOP
  2. 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换

静态代理和动态代理的区别:

  • 静态代理:自己编写创建代理类,然后再进行编译,在程序运行前,代理类的.class文件就已经存在了。
  • 动态代理:在实现阶段不用关心代理谁,而在运行阶段(通过反射机制)才指定代理哪一个对象。

4、拦截器

Java 里的拦截器是动态拦截 action 调用的对象。它提供了一种机制可以使开发者可以定义在一个 action 执行的前后执行的代码,也可以在一个 action 执行前阻止其执行,同时也提供了一种可以提取 action 中可重用部分的方式。在AOP中拦截器用于在某个方法或字段被访问之前进行拦截,然后在之前或之后加入某些操作,完成面向切面的编程拦截。

拦截器在流行的开源框架中也很常见,其依赖的技术就是 Java 的动态代理。下面以一个简单的模型的来说明拦截器实现的一般方法(以JDK动态代理为例)。模型主要分为五个模块,分别:

  1. 业务组件,被代理和被拦截的对象;
  2. 代理处理器,实现了 InvocationHandler 接口的一个对象;
  3. 代理对象,Proxy 对象;
  4. 拦截器,普通的 JavaBean,在调用业务方法之前或者之后会自动拦截并执行自己的一些方法;
  5. 客户端,执行业务处理的入口。

根据JDK动态代理原理,首先还是需要提供一个业务组件的接口及其实现类,这是需要被代理的类:

//业务组件接口
public interface HelloWorldImpl {
    public void sayHelloWorld();
}

//业务组件接口的实现类
public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World");
    }
}

定义拦截器接口,开发者只需要知道接口的方法、含义和作用即可使用;一般拦截器都会定义3个方法:before、aroundafter,用于拦截器的语义,其中参数为:proxy代理对象、target真实对象、method方法、args方法参数

public interface Interceptor {
    //before方法在真实对象之前调用,返回Boolean值
    //若为true则反射执行真实对象的方法,若为false则调用下面的around方法
    public boolean before(Object proxy, Object target, Method method, Object[] args);
    //around方法为真实方法的替代,它与真实方法只能选择其一执行
    public void around(Object proxy, Object target, Method method, Object[] args);
    //after后置方法,在反射执行真实对象方法或around方法后,调用after方法
    public void after(Object proxy, Object target, Method method, Object[] args);
}

实现一个自定义的拦截器MyInterceptor :

public class MyInterceptor implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.err.println("反射方法前的逻辑");
        return false;//不反射被代理对象的原有方法
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.err.println("发射方法后置逻辑");
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.err.println("取代了被代理对象的方法");
    }
}

在JDK动态代理中使用拦截器,创建动态代理处理器工具InterceptorJdkProxy类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class InterceptorJdkProxy implements InvocationHandler {

    private Object target; //被代理的真实对象
    private String interceptorClass = null;//拦截器全限定名
    //构造方法需提供拦截器和被代理对象
    public InterceptorJdkProxy(Object target, String interceptorClass) {
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    /*
     * 绑定委托(真实)对象并返回一个代理对象占位
     * @param target 真实对象
     * @return 代理对象(占位)
     */
    public static Object bind(Object target, String interceptorClass) {
        //获取代理对象 
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InterceptorJdkProxy(target, interceptorClass));
    }

    @Override
    /*
     * 通过代理对象调用方法,首先进入这个方法
     * @param proxy --代理对象
     * @param method --被调用方法
     * @param args -- 方法参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (interceptorClass == null) {
            //判断是否存在拦截器,没有拦截器直接反射原方法
            return method.invoke(target, args);
        }
        Object result = null;
        //通过反射生成拦截器
        Interceptor interceptor =
                (Interceptor) Class.forName(interceptorClass).newInstance();
        //调用拦截器的前置before方法
        if (interceptor.before(proxy, target, method, args)) {
            //若返回结果为true,则发射原有方法
            result = method.invoke(target, args);
        } else {//否则调用around方法
            interceptor.around(proxy, target, method, args);
        }
        //最后调用拦截器后置方法
        interceptor.after(proxy, target, method, args);
        return result;
    }
}

概括来说整个过程执行可分为以下步骤:

  1. 准备好一个真实目标对象 target 和一个拦截器 com.spring.MyInterceptor(全限定名)
  2. bind 方法中使用JDK动态代理绑定一个对象,然后返回此代理对象
  3. 判断是否存在拦截器,没有则直接反射原方法,然后结束,否则继续执行
  4. 反射生成拦截器,执行其前置before方法,若返回true,则反射执行原方法;否则运行拦截器的around方法
  5. 执行拦截器的后置after方法,返回invoke方中原方法的执行结果

接下来就可以对此带拦截器的动态代理处理器工具类进行测试使用了:

public static void main(String[] args) {
        HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(
              new HelloWorldImpl(),"com.spring.MyInterceptor");
        proxy.sayHelloWorld();
}

需要注意的是,在真正的项目实践之中,要想实现拦截器的功能,一般采用继承类HandlerInterceptorAdapter或者抽象类AbstractInterceptor,或者实现HandleInterceptor接口。即只需要关心如何重写方法,而不需要关心其内部的实现原理。

面向切面的编程(AOP)

在上文中我们学习了拦截器的实现原理后,想必已经对面向切面编程这个概念有些熟悉了,它就像很多个不同的拦截器组成的切面一样,将这些分散在各个业务逻辑代码中的同类代码,通过横向切割的方式抽取到一个独立模块中,然后统一管理。

1、AOP术语

(1)连接点(Joinpoint)

程序执行过程中的某个特定的点,或者说特定的时候。比如类初始化的时候,方法调用的时候,异常处理的时候,在 Spring AOP 中,一个连接点总是表示一个方法执行的时候。能够被拦截的地方。

连接点的这个“点”,不能以数学思维来理解,这个“点”我们可以理解为一个程序单元,里面包含着一段程序,是有代码的。从概念上讲,就是某件事发生的时候,或者是某段代码执行的时候。再简言之,连接点就是一段可执行的程序代码。

(2)切点(Pointcut)

切点是用来匹配定位连接点的。通常,程序中总会有很多很多的连接点,需要的不需要的都有。我们要做的,就是利用切点,定义一些规则,匹配出我们需要的连接点。具体定位的连接点。

在 Spring 中,切点是由 org.springframework.aop.Pointcut 接口类来描述的,它使用类和方法作为连接点的查询条件, Spring AOP 的规则引擎会解析切点所设定的查询条件,找到对应的连接点 。 连接点是方法执行前 、 执行后等包含方位信息的具体程序执行点, 而切点只是定位到某个方法上, 必须再提供方位信息,才能定位到具体的连接点上。

(3)通知/增强(Advice)

通知是织入目标类连接点上的一段程序代码 。通知包含两层内容,一是“什么事情”,二是“什么时间”。通俗讲,“什么事情”就是一段程序代码,代码需要完成的一些工作;“什么时间”就是连接点代码执行前或者执行后。通知要做的事情,就是在连接点代码执行到某个特定的时候,执行自己的代码。

拿 Spring AOP 来讲,通知的代码在连接点代码执行前先执行,就是前置通知;在连接点代码执行之后再执行,就是后置通知;在之前、之后都要执行,就是环绕通知;在抛出异常的时候执行,就是异常通知;在连接点代码执行完以后,忽略其返回结果,也不管它有没有抛出异常,都要执行通知的代码,这就是最终通知

(4)切面(Aspect)

切面是通知和切点的结合,是指在一个怎么样的环境中工作

其实,光有通知是完成不了任何事情的。举个例子,我写了一份通知“今天下班后,公司技术部全体员工聚餐”,通知的时间是“今天下班后”,通知的事情是“聚餐”。这件事情到时候能完成吗?肯定不能,因为我没有把通知下发到技术部员工的手里,他们不知道这件事情,下班后他们会拍拍屁股走人,各回各家,没人会理会我的通知。我写的通知就是一张废纸,毫无用处。

想让通知起到作用,想让大家下班后留下来聚餐,我就需要给技术部全体员工下发该通知。这个地方我们可以再做个比喻,公司是整个程序,公司的运作就是程序执行的过程,公司的每个员工都是一个连接点,切点定义的规则就是“技术部全体员工”,通过切点,我就可以找到每一个技术部员工,从而把通知下发到他们的手上。之后,事情就可以顺利的执行了。

通知需要切点的匹配,才能正确的应用到连接点上,通知和切点共同定义了关于切面的全部内容——它是什么,在何时和何处完成其功能。

(5)引介(Introduction)

引介是一种特殊的增强,它为类添加了一些属性和方法 。 这样,即使一个业务类原本没有实现某个接口,通过 AOP 的引介功能,也可以动态的为该类添加接口的实现逻辑,让业务类成为这个接口的实现类 。

(6)织入(Weaving)

织入是将通知添加到目标对象的具体连接点上的过程

在程序中,织入的实现,通常是利用设计模式中的代理模式,在目标对象的基础上生成新的代理对象,用代理对象去完成工作。

2、Spring AOP

AOP是一种思想,并不是Spring框架独有的,Spring只是支持AOP编程的框架之一。每种框架实现AOP的方式也是不同的,Spring AOP是基于动态代理的,所以是基于方法拦截的,每个成员方法都可以称之为连接点。

Spring支持3种方式实现AOP拦截功能:

  • @AspectJ注解驱动的切面
  • 纯POJO切面,使用XML配置,AOP命名空间
  • 基于代理的经典SpringAOP,需要实现接口,手动创建代理

使用@AspectJ注解实现AOP

(1)选择连接点

Spring是方法级别的AOP框架,所以类每个成员方法都可以称之为连接点。所以我们先建立一个接口和其实现类:

//RoleService接口,printRole方法作为连接点
public interface RoleService {
    public void printRole(Role role);
}

//RoleService接口的实现类
@Component
public class RoleServiceImpl implements RoleService {
    @Override
    public void printRole(Role role) {
        System.out.println("{id: " + role.getId() + ", "
                + "role_name : " + role.getRoleName() + ", "
                + "note : " + role.getNote() + "}");
    }
}

这个类很简单,这个时候如果把 printRole 方法作为AOP的连接点,那么就需要为类RoleServiceImpl 生成代理对象,然后拦截printRole方法,然后织入各种AOP通知方法。

(2)创建切面

选择连接点后就可以创建切面了,就如同之前创建拦截器一样,在Spring中需要使用@AspectJ注解一个类,那么Spring IoC容器就会认为这是一个切面。

在确定了切面后,Spring还需要判断某个方法是否需要拦截,毕竟并不是所有的方法都需要AOP编程,这时候就需要引入切点来描述哪些是需要被拦截的方法,在注解中定义了正则表达式:

 execution(* com.spring.RoleServiceImpl.printRole(..) )

  • execution:代表执行方法的时候会触发
  • *  : 代表任意的返回类型的方法
  • com.spring.RoleServiceImpl:表示类的全限定名
  • printRole:表示拦截的方法名称
  • (..) :  表示任意的参数

但是这种正则表达式重复写太麻烦,所以Spring提供了一个切点注解@Pointcut避免这个麻烦

import ...

@Aspect
public class RoleAspect {

    @Pointcut("execution(* com.spring.RoleServiceImpl.printRole(..))")
    public void print() {
    }

    @Before("print()")
    // @Before("execution(* com.spring.RoleServiceImpl.printRole(..))")
    public void before() {
        System.out.println("before ....");
    }

    @After("print()")
    // @After("execution(* com.spring.RoleServiceImpl.printRole(..))")
    public void after() {
        System.out.println("after ....");
    }

    @AfterReturning("print()")
    // @AfterReturning("execution(* com.spring.RoleServiceImpl.printRole(..))")
    public void afterReturning() {
        System.out.println("afterReturning ....");
    }

    @AfterThrowing("print()")
    // @AfterThrowing("execution(* com.spring.RoleServiceImpl.printRole(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing ....");
    }

    @Around("print()")
    public void around(ProceedingJoinPoint jp) {
        System.out.println("around before ....");
        try {
            jp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("around after ....");
    }

}

              

(3)配置Spring Bean

创建好切面实例后,还需要对Spring的Bean进行配置,这样Spring IoC容器才能对其进行管理。配置方式有多种,关于Spring中Bean的装配具体可参考这篇文章,这里主要介绍2种:

使用Java注解显示配置:

@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.spring")
public class AopConfig {
    @Bean
    public RoleAspect getRoleAspect() {
        return new RoleAspect();
    }
}

其中@EnableAspectJAutoProxy注解表示 AspectJ 框架的自动代理,@Bean表示Spring IoC会把此方法的返回结果对象作为 Spring的Bean资源,这时候Spring才会生成动态代理对象,进而也可以使用AOP,而代码中getRoleAspect方法则生成一个切面实例。同样可以用XML来进行配置。

使用XML来进行配置:

<?xml version='1.0' encoding='UTF-8' ?>
<beans ..>
    <aop:aspectj-autoproxy/>
    <bean id="roleAspect" class="com.ssm.chapter11.aop.aspect.RoleAspect"/>
    <bean id="roleService" class="com.ssm.chapter11.aop.service.impl.RoleServiceImpl"/>
</beans>

其中 <aop:aspectj-autoproxy/> 与@EnableAspectJAutoProxy注解的作用相同。

(4)测试AOP

无论是XML还是Java显示配置都能使Spring产生动态代理对象,从而组织切面,把各类通知织入到程序流程中。测试如下:

public static void main(String[] args) {
        ApplicationContext context = new 
             AnnotationConfigApplicationContext(AopConfig.class);
/* 
 * 使用XML配置,ClassPathXmlApplicationContext作为IoC容器
 * ApplicationContext context = new 
 * ClassPathXmlApplicationContext("spring-cfg3.xml");
 */      
        Role role = new Role();
        role.setId(1L);
        role.setRoleName("role_name_1");
        role.setNote("note_1");
        RoleService roleService = context.getBean(RoleService.class);
        roleService.printRole(role);
}

使用XML配置实现AOP与使用注解原理基本相同,只是需要在XML中引入AOP的命名空间,如下所示

    <aop:config>
        <aop:aspect ref="xmlAspect">
            <!-- 自定义切点 -->
            <aop:pointcut id="printRole"
                        expression="execution(*com.spring.RoleServiceImpl.printRole(..))"/>
            <!-- 定义通知 -->
            <aop:before method="before" pointcut-ref="printRole"/>
            <aop:after method="after" pointcut-ref="printRole"/>
            <aop:after-throwing method="afterThrowing"
                                pointcut-ref="printRole"/>
            <aop:after-returning method="afterReturning"
                                 pointcut-ref="printRole"/>
            <aop:around method="around" pointcut-ref="printRole"/>
            <!-- 引入通知 -->
            <aop:declare-parents
                    types-matching="com.spring.RoleServiceImpl+"
                    implement-interface="com.spring.RoleVerifier"
                    default-impl="com.spring.RoleVerifierImpl"/>
        </aop:aspect>
    </aop:config>

 

另外,还可以设置多个切面对同一方法进行拦截,实现也很简单,就是多创建几个切面类,都对同一方法进行切入即可。

 

 

参考文章:

《Java EE 互联网轻量级框架整合开发》

 https://blog.csdn.net/qq_25827845/article/details/75208354

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值