目录
前言
代理模式
静态代理
优缺点
动态代理
JDK动态代理
工作原理
JDK动态原理实现关键步骤
CGLib动态代理
CGLIB动态代理实现关键步骤
总结
前言
在上一篇中,我们讲解了什么是AOP,以及Spring AOP是如何使用的,那么本篇我们就来学习一下Spring AOP的原理,也就是Spring是如何实现AOP的。
Spring AOP是基于动态代理来实现AOP的,什么是动态代理呢?动态代理其实是一种代理模式。
代理模式
代理模式,又可以称为委托模式,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
简单来说:代理模式可以在不改变原对象的基础上,通过引入一个代理对象来间接访问原对象,从而实现堆原对象的某些操作的控制。
使用代理前:
使用代理后:
在生活中,能够体现代理模式的有很多,如房屋中介。当我们想要租房的时候,如果我们直接去找房东,如果房东一个个带我们去看,就会很浪费时间,若房东比较忙,就有可能会处理不过来,所以房东就会把租房这件事情交给中介,而我们直接去找中介,并提出我们的需求,中介就会帮助我们找到合适的房子。这样就极大的方便了我们租房的流程。
代理模式中主要角色:
- Subject:业务接口类,可以是抽象类或者接口(不一定有);
- RealSubject:业务实现类,具体的业务逻辑,也就是被打理对象,如房东;
- Proxy:代理类。RealSubject 的代理,通过中介体现。
代理模式我们可以分为两种:静态代理或动态代理。
在静态代理中,我们对目标对象中的每个方法的增强都是手动完成的,耦合性高,而动态代理,则是通过在运行时创建一个子类实例来实现的,可以更加灵活地实现代理。
- 静态代理:在编译时就已经确定的代理类,代理类和真实主题类通常实现相同的接口。
- 动态代理:在运行时利用反射机制动态生成代理类,不需要先定义代理类,而是在运行时根据指定的类和接口动态生成代理类。
静态代理
静态代理是指代理类在程序运行前就已经定义好,与目标类的关系在运行前就确定。
在静态代理中,真实主题类和代理类都实现一个相同的接口,代理类通过调用真实主题类的方法来实现接口中定义的业务逻辑,同时可以在调用前后增加额外的操作。
优缺点
- 优点:简单直观,易于理解和实现;
- 缺点:
- 每个目标类都需要创建对应的代理类,如果目标类太多,就会导致类的数量急剧增加,增加了系统的复杂性;
- 代理类是硬编码,不灵活。
我们以前面讲的中介为例:
在用户租房前,中介和房东需要先协商好,中介就能帮助房东出租或出售房子,当有人去找中介讲需求的时候,中介就可以直接联系客户和房东进行对接。
通过代码展示:
定义一个接口(房东想要的事,也就是中介要做的事):
package com.example.demo.aspect;
public interface HouseSubject {
void rent();
}
房东:
package com.example.demo.aspect;
public class RealHouseSubject implements HouseSubject{
@Override
public void rent() {
System.out.println("我要出租房子");
}
}
中介:
package com.example.demo.aspect;
public class ProxyHouse implements HouseSubject{
private RealHouseSubject realHouseSubject;
public ProxyHouse(RealHouseSubject realHouseSubject){
this.realHouseSubject = realHouseSubject;
}
@Override
public void rent() {
System.out.println("我是中介,开始代理");
realHouseSubject.rent();
System.out.println("我是中介,结束代理");
}
}
客户租房:
通过这个案例,我们能够进一步理解静态代理。
那么如果房东现在想要对房子进行出租,那么中介和房东就需要再一次协商。
public interface HouseSubject {
void rent();
void sale();
}
中介:
package com.example.demo.aspect;
public class ProxyHouse implements HouseSubject{
private RealHouseSubject realHouseSubject;
public ProxyHouse(RealHouseSubject realHouseSubject){
this.realHouseSubject = realHouseSubject;
}
@Override
public void rent() {
System.out.println("我是中介,开始代理");
realHouseSubject.rent();
System.out.println("我是中介,结束代理");
}
@Override
public void sale() {
System.out.println("我是中介,开始代理");
realHouseSubject.sale();
System.out.println("我是中介,结束代理");
}
}
房东:
package com.example.demo.aspect;
public class RealHouseSubject implements HouseSubject{
@Override
public void rent() {
System.out.println("我要出租房子");
}
@Override
public void sale() {
System.out.println("我要出售房子");
}
}
客户买房:
package com.example.demo.aspect;
public class Main {
public static void main(String[] args) {
RealHouseSubject realHouseSubject = new RealHouseSubject();
ProxyHouse proxyHouse = new ProxyHouse(realHouseSubject);
// proxyHouse.rent();
proxyHouse.sale();
}
}
我们可以看到,当我们想要对业务进行修改的时候,那么对应的其它两个实现类进行修改,若增加的业务越多,修改的就越多。
那么有没有一种方法?让他们通过一个代理类来实现呢?这就需要用到动态代理了。
动态代理
动态代理是在运行时利用反射机制动态生成代理类的代理方式,不需要事先定义代理类,而是在运行时根据指定的类和接口动态生成代理类。
动态代理不需要修改原始对象,就能对对象的功能进行增强,例如实现横切关注点,日志记录、性能监控、事务管理等。
常见的动态代理有 JDK代理 和 CGLib代理。
JDK动态代理
JDK动态代理是Java标准库中提供的一种动态代理机制,允许在运行时动态生成实现了一个或多个接口的代理类,而无需事先知道具体的类。
工作原理
- 使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。
- 目标类必须实现一个或多个接口。
- 代理类在运行时生成,实现了与目标类相同的接口。
- 通过实现 InvocationHandler 接口,可以自定义方法调用的行为,即在调用目标方法前后添加额外逻辑。
不过JDK动态代理其实有个缺点:只能代理实现了接口的类,对于没有实现接口的类,是不能代理实现的。
JDK动态原理实现关键步骤
- 定义接口:首先需要定义一个接口,该接口将由目标对象和代理对象实现。
- 创建目标对象:需要先创建目标对象的实例,该实例将被代理对象代理。
- 实现 InvocationHandler 接口:创建一个实现 java.lang.reflect.InvocationHandler 接口的类,该类将定义代理对象的方调用逻辑。在实现该接口的类中,需要重写 invoke() 方法,以便在目标对象的方法调用前后执行额外的业务逻辑。
- 创建代理对象:使用 java.lang.reflect.Proxy 类 和 InvocationHandler 对象创建代理对象。具体来说就是要调用 Proxy类中的静态方法newProxyInstance() 创建一个代理对象,该实例将调用InvocationHandler对象的invoke() 方法来处理方法调用。
- 使用代理对象:使用代理对象调用目标对象的方法时,会在方法执行前后执行额外的逻辑。
我们利用中介的例子来用代码实现:
实现接口
package com.example.demo.aspect;
public interface HouseSubject {
void rent();
void sale();
}
创建目标对象
package com.example.demo.aspect;
public class RealHouseSubject implements HouseSubject{
@Override
public void rent() {
System.out.println("我要出租房子");
}
@Override
public void sale() {
System.out.println("我要出售房子");
}
}
创建一个实现InvocationHandler接口的代理类:
package com.example.demo.aspect;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
private Object target;
public JDKInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目标对象执行之前,代理增强
System.out.println("我是中介,开始代理");
//通过反射执行被代理对象的方法
Object result=method.invoke(target, args);
//目标对象执行完成之后,代理增强
System.out.println("我是中介,结束代理");
return result;
}
}
创建代理对象并调用
package com.example.demo.aspect;
import java.lang.reflect.Proxy;
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
HouseSubject proxy= (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target));
proxy.rent();
}
}
结果
那么上面这些InvocationHandler接口以及Proxy到底是什么呢?
InvocationHandler接口是java动态代理的关键接口之一,定义了一个单一方法 invoke() 方法,该方法是代理示例调用的中心分发点。当代理实例的任意方法被调用时,调用会自动委托给关联的 InvocationHandler 实例的 invoke() 方法。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
- proxy:就是要代理对象本身,可以用来调用目标对象的其他方法或者属性;
- method:被调用的方法的 Mathod 对象,提供了关于方法的详细信息,如方法名、参数类型等;
- args:调用方法时传递的参数数组。
Proxy类是位于 java.lang.reflect 包中的一个类,提供了创建动态代理类和实例的静态方法。它是JDK动态代理机制的入口点。Proxy类没有公共的构造函数,所以不能直接实例化。但我们可以用它里面的 newProxyInstance 来创建代理实例。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler invocationHandler)
throws IllegalArgumentException
- loader:定义了代理类的类加载器,通常是目标类的类加载器;
- interfaces:代理类需要实现的接口数组,代理类中会实现这些接口中定义的所有方法;
- invocationHandler:一个InvocationHandler示例,定义了当代理实例的方法被调用时的处理逻辑。
CGLib动态代理
JDK动态代理有一个致命的问题就是只能代理实现了接口的类。
但是在有些情况下,我们的业务代码是直接实现的,并没有接口定义,那就不能实现JDK代理了,为了解决这个问题,我们可以用CGLIB动态代理机制来解决。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,通过字节码技术在运行时为没有实现接口的目标类生成子类作为代理类。
CGLIB能代理任何未实现接口的类,是因为CGLIB是通过继承方式生成一个目标类的子类,重写父类的方法并在方法中加入增强逻辑实现动态代理的。
注意:CGLIB动态代理要求被代理的目标类不能声明为final类,并且方法也不能是final方法,否则无法被CGLIB成功继承和重写。
CGLIB动态代理实现关键步骤
- 添加CGLIB库:我们需要将CGLIB依赖引入;
- 创建目标对象:创建一个需要被代理的类;
- 实现接口:自定义 MethodInterceptor 并重写 intercept() 方法,intercept() 用于增强目标方法,和JDK动态代理的 invoke() 方法类似;
- 创建代理对象:通过调用Enhancer对象中的create()方法创建代理对象;
- 使用代理对象:利用代理对象来调用目标对象的方法,在方法调用前后会执行额外的逻辑。
演示:
添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
创建目标类
package com.example.demo.aspect;
public class RealHouseSubject implements HouseSubject{
@Override
public void rent() {
System.out.println("我要出租房子");
}
@Override
public void sale() {
System.out.println("我要出售房子");
}
}
实现MethodInterceptor接口:
package com.example.demo.aspect;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibInterceptor implements MethodInterceptor {
private Object target;
public CGLibInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//跟JDK 代理一样,代理增强的逻辑写在这个方法中
System.out.println("我是中介,开始代理");
Object invoke = method.invoke(target, objects);
System.out.println("我是中介,结束代理");
return invoke;
}
}
使用
package com.example.demo.aspect;
import net.sf.cglib.proxy.Enhancer;
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
HouseSubject proxy= (HouseSubject) Enhancer.create(target.getClass(),new CGLibInterceptor(target));
proxy.rent();
}
}
总结
Spring AOP是如何实现的?
Spring AOP是基于动态代理实现的,有两种方式:基于JDK动态代理实现和基于CGLIB动态代理实现,运行时使用哪种方法与项目配置和代理的对象有关。
Spring AOP的实现方式常见的有几种?
Spring AOP常见的实现方式有两种:1、基于注解@Aspect来实现的;2、基于自定义注解实现,还有一些更原始的方式,如基于代理、基于XML配置等
JDK动态代理和CGLIB动态代理有什么区别以及如何选择?
- 实现方式:JDK代理是基于接口实现的,必须先定义接口;CGLIB代理是直接基于被代理类实现的,不需要定义接口。
- 代理机制:JDK动态代理机制是委托机制,通过委托handler调用原始实现类方法;而CGLIB则使用继承机制,被代理类和代理类是继承关系,因此代理类对象可以直接赋值给代理类类型的变量。总的来说:JDK代理是基于接口实现的,CGLIB是基于继承实现的。
- 选择:如果需要代理实现了接口的类,且对性能要求不是特别高,可以选择JDK代理;如果需要代理没有实现接口的类,或者对性能要求较高,可以用CGLIB代理。
- 限制:JDK动态代理和CGLIB动态代理都不能代理 final 类和方法。对于JDK动态代理,final 类不能实现接口;对于CGLIB动态代理, final 类不能被继承。此外,两者都不能代理 final 方法,因为 final 方法不能被重写。
以上就是本篇所有内容~
若有不足,欢迎指正~