Spring AOP(面向切面编程)
AOP(Aspect Oriented Programming)与 OOP(Object Oriented Programming,面向对象编程)相辅相成。AOP 提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,我们以类(Class)作为基本单元,而在 AOP 中则以切面(Aspect)作为基本单元。
AOP 是一种增强的编程方式,可以解耦一些非业务逻辑,如声明式事务管理、日志管理或异常处理等。从底层原理来讲,AOP 实际上是基于 Java 的代理模式实现的。
(1)新建一个公共接口 UserInterface,代码如下:
首先我们来看一个基于 JDK 反射生成代理类的例子。
(1)定义一个公共接口 UserServiceInterface,代码如下:
上面主要讲的是基于 JDK 反射的例子。下面来看一下 CGLIB 实现动态代理的原理。它是通过继承父类的所有公共方法,然后重写这些方法,并在重写方法时对这些方法进行增强处理来实现的。根据里氏代换原则(LSP),父类出现的地方子类都可以出现,因此 CGLIB 实现的代理类也是可以被正常使用的。
CGLIB 的基本架构如图 1 所示,代理类继承自目标类,每次调用代理类的方法时都会被拦截器拦截,然后在拦截器中调用真实目标类的方法。
图1 CGLIB动态代理实现原理
CGLIB 实现动态代理的方式比较简单,具体如下:
(1)直接实现 MethodInterceptor 拦截器接口,并重写 intercept() 方法。代码如下:
Spring AOP 的 Advice 类型如表 2 所示。
AOP 是一种增强的编程方式,可以解耦一些非业务逻辑,如声明式事务管理、日志管理或异常处理等。从底层原理来讲,AOP 实际上是基于 Java 的代理模式实现的。
代理模式
代理模式是经典的设计模式之一,目的是为了扩展和增强类或接口。代理模式通常可以分为静态代理模式和动态代理模式。1) 静态代理模式
静态代理模式的实现比较简单,主要的实现原理是:代理类与被代理类同时实现一个主题接口,代理类持有被代理类的引用。(1)新建一个公共接口 UserInterface,代码如下:
//声明 UserInterface 接口 public interface UserInterface { //声明方法 public abstract void getUser(); }(2)定义真实执行类 RealUser 并实现公共接口 UserInterface,代码如下:
//声明 RealUser 类,实现 UserInterface 接口 public class RealUser implements UserInterface { @Override public void getUser() { //新建UserService对象 System.out.println("真实用户角色执行!"); UserService userService = new UserService(); userService.setId(1); userService.setName("www.weixueyuan.net"); userService.getUser(); } }(3)定义代理类 UserProxy 实现公共接口 UserInterface,并持有被代理类的实例。在执行时,调用被代理类(RealUser)实例的 getUser()方法。代码如下:
//声明 UserProxy 代理类,并实现 UserInterface 接口 public class UserProxy implements UserInterface { private UserInterface userInterface; //构造方法传入 UserInterface 类型参数 public UserProxy(UserInterface userInterface) { this.userInterface = userInterface; } //实现getUser()方法,在执行方法前后进行额外操作 @Override public void getUser() { doBefore(); userInterface.getUser(); doAfter(); } //真实方法执行前操作 private void doBefore() { System.out.println("代理类开始执行"); } //真实方法执行后操作 private void doAfter() { System.out.println("代理类结束执行"); } }(4)编写测试代码,具体如下:
public class SpringProxyTest { public static void main(String[] args) { UserInterface realUser = new RealUser(); //传入真实对象RealUser UserProxy userProxy = new UserProxy(realUser); userProxy.getUser(); } }运行结果如下:
代理类开始执行
真实用户角色执行!
id:1
name:www.weixueyuan.net
代理类结束执行
2) 动态代理
顾名思义,动态代理是指在程序运行时动态地创建代理类。动态代理的使用方式主要分为两种:一种是基于接口的代理,另一种则是基于类的代理。基于接口的代理方式是指通过 JDK 自带的反射类来生成动态代理类;基于类的代理方式则是指通过字节码处理来实现类代理,如 CGLIB 和 Javassist 等。首先我们来看一个基于 JDK 反射生成代理类的例子。
(1)定义一个公共接口 UserServiceInterface,代码如下:
public interface UserServiceInterface { public void getUser(); }(2)定义真实用户角色类 UserServiceImpl 并实现公共接口 UserServiceInterface,代码如下:
public class UserServiceImpl implements UserServiceInterface { @Override public void getUser() { System.out.println("www.weixueyuan.net"); //实现getUser()方法 } }(3)定义代理类 UserServiceProxy,实现 InvocationHandler 接口,并重写 invoke() 方法,代码如下:
//定义实现 InvocationHandler 接口的代理类 UserServiceProxy public class UserServiceProxy implements InvocationHandler { private Object target; //构造方法 public UserServiceProxy(Object target) { this.target = target; } //通过Proxy动态生成代理类对象 public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //动态执行方法 @Override public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { System.out.println("JDK before"); method.invoke(target, args); System.out.println("JDK after"); return null; } }(4)编写测试代码:
public class SpringProxyTest { public static void main(String[] args) { //通过代理类生成UserServiceInterface接口类型对象 UserServiceInterface userServiceInterface = new UserServiceProxy(new UserServiceImpl()).getProxy(); userServiceInterface.getUser(); //调用getUser()方法 } }运行结果如下:
JDK proxy before
www.weixueyuan.net
JDK proxy after
上面主要讲的是基于 JDK 反射的例子。下面来看一下 CGLIB 实现动态代理的原理。它是通过继承父类的所有公共方法,然后重写这些方法,并在重写方法时对这些方法进行增强处理来实现的。根据里氏代换原则(LSP),父类出现的地方子类都可以出现,因此 CGLIB 实现的代理类也是可以被正常使用的。
CGLIB 的基本架构如图 1 所示,代理类继承自目标类,每次调用代理类的方法时都会被拦截器拦截,然后在拦截器中调用真实目标类的方法。
图1 CGLIB动态代理实现原理
CGLIB 实现动态代理的方式比较简单,具体如下:
(1)直接实现 MethodInterceptor 拦截器接口,并重写 intercept() 方法。代码如下:
//继承MethodInterceptor类并实现intercept()方法 public class UserMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Cglib before"); proxy.invokeSuper(obj, args); System.out.println("Cglib after"); return null; } }(2)新建Enhancer类,并设置父类和拦截器类。代码如下:
public class SpringProxyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl.class); //设置父类 //设置拦截器 enhancer.setCallback(new UserMethodInterceptor()); UserServiceImpl userServiceImpl = (UserServiceImpl) enhancer.create(); //创建对象 userServiceImpl.getUser(); //调用getUser方法 } }运行结果如下:
Cglib before
www.weixueyuan.net
Cglib after
AOP中的术语
Spring AOP 就是负责实现切面编程的框架,它能将切面所定义的横切逻辑织入切面所指定的连接点中。AOP 是一种面向切面的编程,有很多独有的概念,如切面、连接点和通知等,它们组合起来才能完成一个完整的切面逻辑。因此,AOP 的工作重心在于如何将增强织入目标对象的连接点上。1) 切面
切面(Aspect)通常由 Pointcut(切点)和 Advice(通知)组合而成。通常是定义一个类,并在类中定义 Pointcut 和 Advice。定义的 Pointcut 用来匹配Join point(连接点),也就是对那些需要被拦截的方法进行定义。定义的 Advice 用来对被拦截的方法进行增强处理。在 Spring AOP 中,切面定义可以基于 XML 配置定义,也可以用 @Aspect 注解定义。我们可以简单地认为,使用 @Aspect 注解的类就是一个切面类。2) 连接点
连接点是程序执行过程中的一个明确的点,如方法的执行或者异常处理。在 Spring AOP 中,一个连接点一般代表一个方法的执行。3) 通知
通知是切面在特定的连接点上执行的特殊逻辑。通知可以分为方法执行前(Before)通知、方法执行后(After)通知和环绕(Around)通知等。包括Spring AOP 在内的许多 AOP 框架通常会使用拦截器来增强逻辑处理能力,围绕着连接点维护一个拦截器链。Spring AOP 的 Advice 类型如表 2 所示。
类 型 | 说 明 |
---|---|
Before advice | 前置增强,在连接点之前执行 |
After returning advice | 后置增强,在方法正常退出时执行 |
After throwing advice | 后置增强,在抛出异常时执行 |
After (finally) advice | 后置增强,不管是抛出异常还是正常退出都会执行 |
Around advice | 在连接点之前和之后均执行 |