Spring的AOP理解
AOP基础概念
1、什么是AOP?
AOP面向切面编程,可以不修改源代码进行方法增强,AOP是OOP(面向对象编程)的延续,主要用于日志记录、性能统计、安全控制、事务处理等方面。它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的JDK动态代理和基于类的cglib的动态代理,咱们正常都是面向接口开发,所以AOP使用的是基于接口的JDK动态代理。
2、AOP中的一些常用概念
-
切面(Aspect):AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。
-
连接点(Join Point):哪些方法需要被AOP增强,这些方法就叫做连接点。
-
通知(Advice):AOP在特定的切入点上执行的增强处理,有
-
before
-
after
-
afterReturning
-
afterThrowing
-
around
-
-
切入点(Pointcut):实际真正被增强的方法,称为切入点
3、通知类型
通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型:
- 前置通知(Before Advice):在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用@Before注解使用这个Advice。
- 返回之后通知(After Retuning Advice):在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning注解使用它。
- 抛出(异常)后执行通知(After Throwing Advice):如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通过 @AfterThrowing注解来使用。
- 后置通知(After Advice):无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After注解使用。
- 围绕通知(Around Advice):围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过@Around注解使用。
AOP底层实现
2.1 AOP底层原理
它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别由一个真实实现和一个代理实现,而动态代理分为基于接口的JDK动态代理和基于类的CGLIB的动态代理。
第一种 有接口情况,使用 JDK 动态代理
创建接口实现类代理对象,增强类的方法
JDK动态代理是去创建一个UserDao接口的实现类的代理对象,该接口实现类的代理对象会调用该接口的真实实现,并且在代理对象中调用真实实现类的前后做方法增强
第二种 没有接口情况,使用 CGLIB 动态代理
创建子类的代理对象,增强类的方法
2.2 代理概述
代理就是在不修改源代码的情况下使得原本不具备某种行为能力的类、对象具有该种行为能力,实现对目标对象的功能扩展
代理的应用场景
- 事务处理
- 权限管理
- 日志收集
- AOP切面
- …
Java的代理分为静态代理和动态代理
静态代理的局限性:只能代理某一类型接口的实例,不能代理任意接口任意方法的操作。
静态代理只能代理固定或单一接口的方法,也就是说不能做到任何类任何方法的代理。
2.3 静态代理实现
Movie 接口的实现
/**
* 委托类的父接口
*/
public interface Movie {
void player();
}
实现了Movie 接口的 真实实现类(委托类):
public class RealMovie implements Movie {
@Override
public void player() {
System.out.println(">>>>>>>> 您正在观看《士兵突击》");
}
}
实现了Movie 接口的 代理实现类:
public class Cinema implements Movie {
RealMovie realMovie;
public Cinema(RealMovie realmovie) {
this.realMovie = realmovie;
}
public void player() {
//对目标方法进行方法增强
System.out.println("|||||||||||||||||||||||电影开始前,卖爆米花");
//执行真实实现的目标方法
realMovie.player();
//对目标方法进行方法增强
System.out.println("----------------------电影结束了,打扫卫生");
}
}
具体的调用如下:
public class ProxyTest {
public static void main(String[] args) {
//创建电影院(静态代理)
Cinema cinema = new Cinema(new RealMovie());
cinema.player();
}
}
使用静态代理的好处:
使得真实角色处理的业务更加纯粹,不再去关注一些公共的事情。
公共的业务由代理来完成—实现业务的分工。
公共业务发生扩展时变得更加集中和方便。
缺点:每一个代理类都必须实现一遍真实实现类(也就是realMovie)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,每一个代理类对应一个真实实现类(委托类),如果真实实现(委托类)非常多,则静态代理类就非常臃肿,难以胜任。
2.3 JDK动态代理实现
动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。动态代理是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。
其步骤如下:
- 创建一个需要动态代理的接口,即Movie接口
- 创建一个需要动态代理接口的真实实现,即RealMovie类
- 创建一个动态代理处理器,实现InvocationHandler接口,并重写invoke方法去增强真实实现中的目标方法
- 在测试类中,生成动态代理的对象。
第一二步骤,和静态代理一样,不过说了。第三步,代码如下:
/**
* 动态代理处理类
*/
public class MyInvocationHandler implements InvocationHandler {
//需要动态代理接口的真实实现类
private Object object;
//通过构造方法去给需要动态代理接口的真实实现类赋值
public MyInvocationHandler(Object object) {
this.object = object;
}
/**
* 对真实实现(被代理对象)的目标方法进行增强
* 当代理对象调用真实实现类的方法时,就会执行动态代理处理器中的该invoke方法
*
* @param proxy 生成的代理对象
* @param method 代理对象调用的方法
* @param args 调用的方法中的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法增强
System.out.println("卖爆米花");
//object是真实实现,args是调用方法的参数
//当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法
method.invoke(object,args);
//方法增强
System.out.println("扫地");
return null;
}
}
第四步,创建动态代理的对象
public class DynamicProxyTest {
public static void main(String[] args) {
// 保存生成的代理类的字节码文件
//由于设置sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//需要动态代理接口的真实实现
RealMovie realMovie = new RealMovie();
//动态代理处理类
MyInvocationHandler handler = new MyInvocationHandler(realMovie);
//获取动态代理对象
//第一个参数:真实实现(被代理对象)的类加载器
//第二个参数:真实实现类(被代理对象)它所实现的所有接口的数组
//第三个参数:动态代理处理器
Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),
realMovie.getClass().getInterfaces(),
handler);
movie.player();
}
}
数:动态代理处理器
Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),
realMovie.getClass().getInterfaces(),
handler);
movie.player();
}
}