什么是代理?
代理(Proxy)是一种设计模式,它充当另一个对象的接口或占位符,以控制对该对象的访问。
静态代理是在编译时就确定代理关系的代理模式。在静态代理中,代理类和目标类在编译期间就确定了。代理类通常持有目标类的引用,并在调用目标方法前后进行一些额外的操作,例如记录日志、执行权限控制等。静态代理的缺点是代理类和目标类是一一对应的,如果需要代理的类很多,就需要编写大量的代理类。
动态代理是在运行时动态生成代理类的代理模式。在动态代理中,代理类的代码在运行时生成,而不是在编译时确定。Java 中的动态代理通常使用 Java 反射机制实现,通过动态生成字节码来创建代理类。动态代理的优点是可以减少代理类的数量,因为一个代理类可以代理多个目标类,从而简化了代码维护
代理三要素
目标角色(真实角色)
代理角色(用来增强目标角色行为)
实现共同接口
以租房子为例,租客(目标角色),中介(代理角色),租房子(共同行为),在这里,租客和中介都能租房子,但中间似乎能够能实现的行为更多(这里就表示能增强租客行为)
静态代理就是确定了租客是谁,动态代理就是我目前不知道租客是谁
代码实现:
首先我们定义一个共同行为的接口(租房子)
1 2 3 4 5 6 7 8
|
public interface RentHouse {
public void toRentHouse();
}
|
租客(目标角色)实现这个接口
1 2 3 4 5 6
| public class Owner implements RentHouse { @Override public void toRentHouse() { System.out.println("两室一厅,月租五千!"); } }
|
中介(代理角色),首先,中介知道租客是谁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class AgentProxy implements RentHouse {
private RentHouse target; public AgentProxy(RentHouse target) { this.target = target; }
@Override public void toRentHouse() { System.out.println("房型朝南,采光好!"); target.toRentHouse(); System.out.println("价格可议!");
} }
|
这里我们调用了目标角色的方法,同时在目标角色行为上进行了加强
测试:
1 2 3 4 5 6 7 8 9 10 11
| public class StaticProxy {
public static void main(String[] args) { Owner owner = new Owner(); AgentProxy agentProxy = new AgentProxy(owner); agentProxy.toRentHouse(); } }
|
动态代理
根据需要,通过反射机制在程序运行期间,动态的为目标对象创建对象,动态代理的两种实现方式:
1.JDK动态代理
2.CGLIB动态代理
动态代理的特点
1.目标对象不固定
2.在程序运行时,动态创建目标对象
3.代理对象会增强目标对象的行为
JDK动态代理
Proxy类是专门完成代理的操作类,可以通过此类为一个接口或多个接口动态的生成实现类,此类提供如下操作方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。 Proxy.newProxyInstance因为与IllegalArgumentException相同的原因而Proxy.getProxyClass 。
参数 loader - 类加载器来定义代理类 interfaces - 代理类实现的接口列表 h - 调度方法调用的调用处理函数 结果 具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例 异常 IllegalArgumentException - 如果对可能传递给 getProxyClass有任何 getProxyClass被违反 SecurityException -如果安全管理器,S存在任何下列条件得到满足: 给定的loader是null ,并且调用者的类加载器不是null ,并且调用s.checkPermission与RuntimePermission("getClassLoader")权限拒绝访问; 对于每个代理接口, intf ,呼叫者的类加载器是不一样的或类加载器的祖先intf和调用s.checkPackageAccess()拒绝访问intf ; 任何给定的代理接口的是非公和呼叫者类是不在同一runtime package作为非公共接口和调用s.checkPermission与ReflectPermission("newProxyInPackage.{package name}")权限拒绝访问。 NullPointerException - 如果 interfaces数组参数或其任何元素是 null ,或者如果调用处理程序 h是 null
|
实现步骤
1.定义类实现InvocationHandler接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class JdkDynamicProxy implements InvocationHandler { private Object target;
public JdkDynamicProxy(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("新娘是...."); Object invoke = method.invoke(target,args); return invoke; }
public Object getProxy(){ Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this ); return o; } }
|
1 2 3 4 5 6 7 8 9 10
| public class JdkHandlerTest {
public static void main(String[] args) { Owner owner = new Owner(); JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(owner); RentHouse rentHouse= (RentHouse) jdkDynamicProxy.getProxy(); rentHouse.toRentHouse(); } }
|
通过接口获取到代理对象,调用代理对象的方法;
CGLIB动态代理
jdk动态代理机制只能代理实现接口的累,而不能实现接口的类就不能使用jdk的动态代理,cglb是针对类来实现代理的,它的原理是对指定的目标类生成一个字类,并覆盖其中方法实现增强,但因为采用的是继承,所以并不能对final修饰的类进行代理。
实现步骤
1.添加依赖
1 2 3 4 5 6 7
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
|
2.定义类实现MethodInterceptor接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class CglibInterceptor implements MethodInterceptor {
private Object target; public CglibInterceptor(Object target) { this.target = target; }
public Object getProxy(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
@Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("===================方法前执行");
Object object = methodProxy.invoke(target, objects);
System.out.println("方法后执行===================");
return object; }
}
|
3.测试
1 2 3 4 5 6 7 8 9 10 11
| public class CglibInterceptorTest {
public static void main(String[] args) { Owner owner = new Owner(); CglibInterceptor cglibInterceptor = new CglibInterceptor(owner); Owner owner= cglibInterceptor.getProxy(); owner.toRentHouse();
|
总结:jdk动态代理于CGLIB动态代理的区别
jdk动态代理是基于接口,CGLIB是基于类,也就是说jdk动态代理生成代理对象,目标对象必须实现某个接口,CGLIB是基于类(生成一个代理类,该类生成一个目标类的字类,重写目标类的方法)
1 2 3 4 5
| Owner owner = new Owner(); JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy(owner); RentHouse rentHouse= (RentHouse) jdkDynamicProxy.getProxy(); rentHouse.toRentHouse();
|
jdk动态获取代理对象的返回值是实现的那个接口
1 2 3 4 5 6 7 8
| Owner owner = new Owner(); CglibInterceptor cglibInterceptor = new CglibInterceptor(owner); Owner owner= cglibInterceptor.getProxy(); owner.toRentHouse();
|
CGLIB是基于类的,代理对象必须是这里设置的
1 2 3 4 5 6 7 8
| Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create();
|
设置的父类是target,所以返回的类型也应该是target
源代码https://github.com/Breeze1203/JavaAdvanced/tree/main/springboot-demo/spring-aop-demo