1、代理模式
Java代理模式是一种设计模式,它允许通过一个代理对象来控制对其他对象的访问。代理模式可以用于许多不同的情况,例如:虚拟代理、远程代理、保护代理等。
在代理模式中,代理对象充当了被代理对象的中间人,并控制对被代理对象的访问。当客户端请求访问被代理对象时,代理对象会处理这些请求,并将结果返回给客户端。代理模式可以提供额外的功能,如权限验证、性能优化、访问控制等。
在Java中,代理模式可以使用接口或继承来实现。如果被代理对象实现了接口,那么代理类也可以实现相同的接口,并在方法中调用被代理对象的方法。如果被代理对象没有实现接口,那么代理类可以通过继承被代理对象的类,并在方法中调用父类的方法来实现代理。
代理模式可以提高代码的灵活性和可扩展性,并将关注点分离,使系统更易于维护和理解。它常用于网络编程、缓存、日志记录等场景中。
举个例子:你想买个手机但是太贵了想找个便宜买手机的方法,代理孕育而生,代理有自己的内部价可以便宜卖给你,你的想法是买手机,直接买手机就是原价,通过代理商买手机就可以内部价,在写代码中就是我们要去达成某件事情,在不修改源代码的情况下修改达成这件事情的方法我们就可以采用代理。
2、静态代理
静态代理是代理模式的一种实现方式,它在编译时就已经确定了被代理对象和代理对象的关系。在静态代理中,需要手动创建一个代理类,该代理类与被代理对象实现相同的接口或继承相同的父类,并在方法中调用被代理对象的方法。
具体而言,静态代理包含以下几个主要角色:
- 被代理对象(目标对象):被代理的对象,它定义了业务逻辑的具体实现。
- 代理对象:代理的对象,它与被代理对象实现相同的接口或继承相同的父类,用于控制对被代理对象的访问。
- 客户端:使用代理对象的对象,它通过代理对象间接地访问被代理对象。
在静态代理中,代理对象拥有对被代理对象的引用,并在方法中调用被代理对象的对应方法。代理对象可以在方法调用前后添加额外的逻辑,如权限验证、日志记录、性能监控等。静态代理的特点是在编译时期就确定了被代理对象和代理对象的关系,所以代理对象通常是手动编写的。
静态代理的优点是简单直观,易于理解和实现;缺点是每一个被代理的类都需要编写一个代理类,如果被代理的类较多,会导致代码冗余。此外,由于静态代理在编译时就已确定代理对象,所以无法动态改变代理对象。
静态代理实现步骤:
1.定义一个接口及其实现类;
2.创建一个代理类同样实现这个接口
3.将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
代码示例如下:
定义买手机接口
public interface BuyPhoneService { void buyPhone(); }
实现接口
public class BuyPhoneServiceImpl implements BuyPhoneService{ @Override public void buyPhone() { System.out.println("买手机"); } }
定义代理并实现买接口
public class BuyPhoneProxy implements BuyPhoneService{ private final BuyPhoneService buyPhoneService; public BuyPhoneProxy(BuyPhoneService buyPhoneService) { this.buyPhoneService = buyPhoneService; } @Override public void buyPhone() { System.out.println("内部优惠打八折"); buyPhoneService.buyPhone(); System.out.println("送手机大礼包!!!"); } }
最终效果
public class Buy { public static void main(String[] args) { BuyPhoneService buyPhoneService = new BuyPhoneServiceImpl(); System.out.println("使用代理前"); System.out.println("----------------------"); buyPhoneService.buyPhone(); System.out.println("----------------------"); System.out.println("使用代理后"); System.out.println("----------------------"); BuyPhoneProxy buyPhoneProxy = new BuyPhoneProxy(buyPhoneService); buyPhoneProxy.buyPhone(); } }
3、动态代理
3.1、动态代理的介绍与实现
动态代理是一种在运行时生成代理类的机制,它允许我们在不事先知道被代理对象的具体类型的情况下创建代理对象。
在动态代理中,代理类是在运行时动态生成的,而不是在编译时静态生成的。动态代理使用了反射机制来动态地创建代理类,并通过代理类来代理被代理对象的方法调用。
动态代理有两种常见的实现方式:基于接口的动态代理和基于类的动态代理。基于接口的动态代理是利用 Java 提供的 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现的。这种方式要求被代理的对象必须实现至少一个接口。
基于类的动态代理则是通过字节码生成库(如 CGLIB)在运行时生成一个继承自被代理类的子类,并通过重写父类的方法来实现代理的功能。这种方式可以代理没有实现任何接口的类。
动态代理的使用场景包括但不限于:
- 在不改变原有代码的情况下,对方法进行增强、添加日志记录、性能监控等额外操作。
- AOP(面向切面编程)框架中,通过动态代理来将横切逻辑与业务逻辑分离。
动态代理的优点包括:
- 不需要事先知道被代理对象的具体类型,提高了灵活性。
- 可以在运行时动态地增强被代理对象的功能,无需修改原有代码。
- 可以减少代码的重复,实现横切逻辑的复用。
动态代理的缺点包括:
- 相对于静态代理来说,由于需要动态生成代理类,动态代理的性能一般会较慢一些。
- 在基于接口的动态代理中,被代理的对象必须实现至少一个接口,对于没有接口的类无法直接代理。
总结来说,动态代理是一种在运行时生成代理类的机制,它允许我们在不事先知道具体类型的情况下创建代理对象,并通过代理对象来对被代理对象的方法调用进行拦截和增强。它在增加灵活性、解耦和代码复用方面具有很多优势,但使用时需要注意性能方面的考量。
JDK动态代理使用步骤:
1.定义一个接口及其实现类;
2.自定义 InvocationHandler
并重写invoke
方法,在 invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3.通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
代码示例:
定义接口
public interface BuyPhoneService { void buyPhone(String phone); }
定义实现类
public class BuyPhoneServiceImpl implements BuyPhoneService { @Override public void buyPhone(String phone) { System.out.println("买"+ phone +"手机"); } }
定义一个 JDK 动态代理类
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class BuyPhoneInvocationHandler implements InvocationHandler { private final Object target; public BuyPhoneInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); // 调用方法之后,我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return result; } }
获取代理对象的工厂类
import java.lang.reflect.Proxy; public class JdkProxyFactory { public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目标类的类加载器 target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个 new BuyPhoneInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler ); } }
结果
public class Buy { public static void main(String[] args) { BuyPhoneService buyPhoneService = (BuyPhoneService) JdkProxyFactory.getProxy(new BuyPhoneServiceImpl()); buyPhoneService.buyPhone("华为"); } }
3.2、CGLIB 动态代理机制
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM(Java 字节码操作和分析框架)的字节码生成库,用于在运行时动态生成 Java 类的子类,实现动态代理的功能。CGLIB 动态代理是一种基于类的动态代理方式。
CGLIB 动态代理的原理如下:
- 首先,CGLIB 通过继承被代理类来创建一个代理类的子类。
- 在子类中,通过方法拦截器(MethodInterceptor)来拦截被代理类的方法调用。
- 当调用代理对象的方法时,子类会先委托给方法拦截器处理,在处理逻辑中可以加入我们自己的增强逻辑。
- 最后,方法拦截器可以决定是否调用被代理类的原始方法或直接返回结果。
CGLIB 动态代理的优点包括:
- 不需要被代理类实现接口:相比基于接口的动态代理,CGLIB 可以代理没有实现任何接口的类。
- 运行时动态生成代理类:在运行时生成字节码,避免了静态代理中需要手动编写代理类的繁琐过程。
- 灵活性和易用性:可以对类的任意方法进行代理,无需修改原有代码,方便增加、修改和调试代理逻辑。
然而,CGLIB 动态代理也有一些限制和注意事项:
- 无法代理 final 方法和 final 类:CGLIB 动态代理通过继承类来创建子类,无法代理被 final 修饰的方法和类。
- 对于私有方法和方法实现为 final 的方法,也无法直接代理。
- 对于静态方法,CGLIB 会直接调用原始方法,不会进行代理。
在使用 CGLIB 动态代理时,通常需要使用 CGLIB 库提供的 Enhancer
类来创建代理类,并设置好拦截器(MethodInterceptor)和被代理类等参数。
总结来说,CGLIB 动态代理是一种基于类的动态代理方式,通过运行时生成代理类的子类,实现对被代理类方法的拦截和增强。它对于没有实现接口的类可以进行代理,提供了更大的灵活性和易用性。然而,也存在一些限制和注意事项,需要注意在使用过程中合理选择和处理。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
public interface MethodInterceptor extends Callback{ // 拦截被代理类中的方法 public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable; }
- obj : 被代理的对象(需要增强的对象)
- method : 被拦截的方法(需要增强的方法)
- args : 方法入参
- proxy : 用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
CGLIB 动态代理类使用步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类;
代码示例:
不同于 JDK 动态代理不需要额外的依赖。CGLIBopen in new window(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
实现一个买手机类
public class BuyPhone { public String buy(String phone) { System.out.println("买了" + phone + "手机" + phone + "就是牛!"); return phone; } }
自定义 MethodInterceptor
(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class BuyPhoneMethodInterceptor implements MethodInterceptor { /** * @param o 被代理的对象(需要增强的对象) * @param method 被拦截的方法(需要增强的方法) * @param objects 方法入参 * @param methodProxy 用于调用原始方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object ob = methodProxy.invokeSuper(o, objects); // 调用方法之后我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return ob; } }
获取代理类
import net.sf.cglib.proxy.Enhancer; public class CglibProxyFactory { public static Object getProxy(Class<?> clazz) { // 创建动态代理增强类 Enhancer enhancer = new Enhancer(); // 设置类加载器 enhancer.setClassLoader(clazz.getClassLoader()); // 设置被代理类 enhancer.setSuperclass(clazz); // 设置方法拦截器 enhancer.setCallback(new BuyPhoneMethodInterceptor()); // 创建代理类 return enhancer.create(); } }
实际使用
public class Buy { public static void main(String[] args) { BuyPhone aliSmsService = (BuyPhone) CglibProxyFactory.getProxy(BuyPhone.class); aliSmsService.buy("华为"); } }
效果图
3.3、 JDK 动态代理和 CGLIB 动态代理对比
JDK动态代理和CGLIB动态代理是实现动态代理的两种常见方式,它们在一些方面有所不同。下面是对JDK动态代理和CGLIB动态代理进行对比的几个方面:
- 代理对象类型:
- JDK动态代理:基于接口生成代理对象,代理对象实现了被代理接口。被代理的类必须实现至少一个接口。
- CGLIB动态代理:通过生成被代理类的子类来创建代理对象,代理对象继承了被代理类。不需要被代理类实现接口,也可以代理没有实现任何接口的类。因为CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 实现原理:
- JDK动态代理:利用Java提供的
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口,在运行时动态生成一个实现被代理接口的代理类,并通过实现InvocationHandler接口的invoke()方法来拦截方法调用。 - CGLIB动态代理:使用基于ASM的字节码生成库,通过继承被代理类来创建一个代理类的子类,子类重写父类的方法,并通过重写的方法来实现拦截和增强逻辑。
- 性能:
- JDK动态代理:由于需要通过反射调用被代理对象的方法,相比原生方法调用,JDK动态代理的性能稍低。
- CGLIB动态代理:由于是通过生成代理类的子类,并直接调用子类的方法,相对来说,CGLIB动态代理的性能更高。
- 被代理对象限制:
- JDK动态代理:被代理的类必须实现接口,因为生成的代理对象实现了被代理接口。
- CGLIB动态代理:不需要被代理类实现任何接口,可以代理没有实现接口的类。
- 底层库依赖:
- JDK动态代理:基于Java标准库,无需额外依赖。
- CGLIB动态代理:基于ASM(Java字节码处理库),需要依赖ASM库。
根据这些对比,选择使用JDK动态代理还是CGLIB动态代理取决于具体的需求和场景。如果被代理的类实现了接口,则可以选择JDK动态代理;如果被代理的类没有实现接口或者需要更高的性能,则可以选择CGLIB动态代理。
4、静态代理和动态代理的对比
静态代理和动态代理是两种实现代理模式的不同方式,它们在代理对象的创建时机、代理行为的灵活性以及维护成本等方面有所不同。下面是对静态代理和动态代理进行对比的几个方面:
- 创建时机:
- 静态代理:在编译时期就已经创建代理类,需要手动编写代理类。
- 动态代理:在运行时期动态生成代理类,无需手动编写代理类,通过反射等机制动态生成代理对象。
- 代理对象的数量:
- 静态代理:每个被代理类都需要创建一个对应的静态代理类。
- 动态代理:可以通过同一个代理类来代理多个不同的被代理类,减少了代理类的数量。
- 灵活性:
- 静态代理:代理类在编译时期已经固定,无法动态改变代理行为。
- 动态代理:可以在运行时动态地改变代理行为,可以在不修改代理类的情况下增加、删除或修改代理的功能。
- 维护成本:
- 静态代理:需要手动编写代理类的代码,当被代理类发生改变时,代理类也需要相应地修改。
- 动态代理:不需要手动编写代理类,被代理类的改变对代理类没有直接影响,更加灵活和易于维护。
- 性能:
- 静态代理:代理对象在编译时期已经创建,直接调用代理方法,性能较高。
- 动态代理:在运行时期动态生成代理类并通过反射等机制调用方法,相对于静态代理,性能稍低,但差异可能不明显。
综上所述,静态代理在代理对象的创建时期和代理行为的灵活性方面受到限制,但相对较为简单,性能较高;动态代理在代理对象的创建时机和维护成本方面具有优势,能够动态改变代理行为,但性能稍低。在实际应用中,根据具体的需求和场景,选择合适的代理方式。
参考: