AOP:面向切面编程技术,被定义为促使软件系统实现关注点分离的技术,分为:核心关注点和横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如图所示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
1、切面(Aspect):横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能横切多个对象。
2、通知(Advice):在AOP中,描述切面要完成的工作被称为通知。
3、目标(Target):包含连接点的对象。也被称作被通知或被代理对象。
4、代理(Proxy):向目标对象应用通知之后创建的对象。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
5、连接点(Join point):连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。
6、切点(Pointcut):
指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。
7、织入(Weaving):织入描述的是把切面应用到目标对象来创建新的代理对象的过程。
Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
基于注解的方式,以用户注册、登录为例,UserService负责处理用户注册、登录等相关逻辑。
假设我们需要在UserService 的login方法执行前后增加日志服务模块,接下来我们要定义个切面,也就是所谓的日志功能的类。
Spring 的 AOP 是基于动态代理实现的,谈到动态代理就不得不提下静态代理。静态代理只能代理一个具体的类,如果要代理一个接口的多个实现的话需要定义不同的代理类。
需要解决这个问题就可以用到 动态代理,动态代理分为:JDK动态代理和Cglib动态代理。
JDK动态代理在1.3的时候引入,其底层需要依赖一个工具类java.lang.reflect.Proxy和一个接口java.lang.reflect.InvocationHandler ,Proxy 类是用于创建代理对象,而 InvocationHandler 接口主要你是来处理执行逻辑。JDK 动态代理 必须基于 接口进行代理
JDK动态代理解析:jdk代理最主要的就是三个类:目标类,代理类(实现了目标接口)和扩展处理器InvocationHandler类。
【1】jdk代理代码样例:
//目标接口,对应ITraget
public interface Subject {
void hello(String str);
}
//目标类,对应Target
public class RealSubject implements Subject{
public void hello(String str) {
System.out.println("hello" + str);
}
}
//InvocationHandler增强处理器接口实现类
//方法调用句柄invoke方法内部就是代理类的扩展点
public class DynamicHandler implements InvocationHandler{
private Object target;//反射代理目标类(被代理,解耦的目标类)
//可以通过构造器动态设置被代理目标类,以便于调用指定方法
public DynamicHandler(Object subject){
this.target = subject;
}
//代理过程中的扩展点
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("brfore call specific method >>" + method.getName());
Object result = method.invoke(target, args);//MethodAccessor.invoke()
System.out.println("after call specific method>>" + method.getName());
return result;
}
}
【2】客户端调用:
@Test
public void t() throws Exception{
Subject realSubject = new RealSubject();
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//1.0 获取代理类的类对象,主要设置相同的ClassLoader去加载目标类实现的接口Subject类
Class proxyClass = Proxy.getProxyClass(Client.class.getClassLoader(), new Class[]{Subject.class});
//2.0 得到代理类后,就可以通过代理类的处理器句柄来得到构造器
final Constructor con = proxyClass.getConstructor(InvocationHandler.class);
//3.0 获取具体执行方法的句柄处理器,目的通过构造器传入被代理目标类对象,注入到代理类处理器句柄中进行代理调用
final InvocationHandler handler = new DynamicHandler(realSubject);
//4.0 通过构造器创建代理类对象
Subject subject = (Subject)con.newInstance(handler);
//5.0 最后调用方法
subject.hello("proxy");
说明:InvocationHandler接口是 调用处理程序接口,每一个proxy代理实例都有一个关联的调用处理程序,每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。通过Java反射执行原始的目标类方法代码。
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
步骤总结:目标接口、目标实现类、InvocationHandler调用处理程序实现类
CGLIB(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,通俗说cglib可以在运行时动态生成字节码。
CGLIB实现原理:运行时,动态的生成一个被代理类的子类(其底层实现是通过 ASM字节码处理框架来转换字节码,并生成新的类),子类重写了被代理类中所有非Final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
优点:cglib代理要比JDK代理快。缺点:被代理类中的final方法无法被代理,因为子类无法重写final函数。
【1】cglib代理代码样例:
// 被代理类
public class HelloServiceImpl {
public void sayHello(){
System.out.println("Hello Zhanghao");
}
public void sayBey(){
System.out.println("Bye Zhanghao");
}
}
// 2.实现MethodInterceptor接口生成方法拦截器:
public class HelloMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("After: " + method.getName());
return object;
}
}
【2】客户端调用:生成代理类对象并打印在代理类对象调用方法之后的执行结果
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhanghao/Documents/toy/spring-framework-source-study/");
Enhancer enhancer = new Enhancer();
//继承被代理类
enhancer.setSuperclass(HelloServiceImpl.class);
//设置enhancer的回调对象,在调用中拦截对目标方法的调用
enhancer.setCallback(new HelloMethodInterceptor());
//创建代理类对象
HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
//在调用代理类中方法时会被我们实现的方法拦截器进行拦截
helloService.sayBey();
}
}
result:
Before: sayBey
Bye Zhanghao
After: sayBey
说明:Enhancer是一个非常重要的类,它允许为非接口类型创建一个JAVA代理,Enhancer动态的创建给定类的子类并且拦截代理类的所有的方法,和JDK动态代理不一样的是不管是接口还是类它都能正常工作。
步骤总结:被代理类、自定义MethodInterceptor方法拦截器
文章参考:
https://www.toutiao.com/a6811489014982377992/?timestamp=1586043155&app=news_article&group_id=6811489014982377992&req_id=2020040507323501012903209413A83F5D
https://www.toutiao.com/a6706764980106035719/?timestamp=1586047559&app=news_article&group_id=6706764980106035719&req_id=2020040508455801012904822311B51369