代理模式
代理模式(Proxy Design Pattern)的原理和代码实现都不难掌握。它在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
java中实现的动态代理的三种方法
- 静态代理
- 使用jdk api实现的动态代理
- 使用cglib实现的动态代理
基于接口实现的静态代理
基于接口的静态代理需要代理对象和目标对象实现同一个接口,所以一大致的流程为:
1、 定义所需的接口的
2、 目标对象实现定义好的接口。
3、代理对象也同样实现实现定义好的接口,通过组合的方式来维护目标对象并通过构造器来初始化。
// 定义待需实现的接口
public interface BuyTickets {
User buyTickets();
}
// 目标对象
public class RealProxyTarget implements BuyTickets {
@Override
public User buyTickets() {
User user = new User();
user.setHaveTicket(true);
System.out.println("实际需要代理的目标对象");
return user;
}
}
// 代理对象
public class ProxyTicketCenter implements BuyTickets {
// 真正的要被执行的目标对象,并使用构造器的方式将目标对象注入进来
private RealProxyTarget target;
public ProxyTicketCenter(RealProxyTarget target) {
this.target = target;
}
@Override
public User buyTickets() {
System.out.println("静态代理开始!");
User user = target.buyTickets();
System.out.println("静态代理结束!");
return user;
}
}
// 测试基于接口的静态代理
public class StaticProxyTest {
@Test
public void proxy() {
// 目标对象
RealProxyTarget target = new RealProxyTarget();
// 代理对象
ProxyTicketCenter ticketCenter = new ProxyTicketCenter(target);
User user = ticketCenter.buyTickets();
/**
* print result
* 静态代理开始!
* 实际需要代理的目标对象
* 静态代理结束!
* true
*/
System.out.println(user.isHaveTicket());
}
}
基于继承的静态代理
基于继承实现的静态代理即通过继承目标对象来实现的静态代理,基于接口的静态代理目标对象和代理对象都需要实现同一个接口。所以有时候目标对象没有实现接口或者我们不能改变目标对象的代码的时候(如第三方库提供的api),可以通过继承的方式,示例如下:
// 目标对象
public class RealProxyTarget implements BuyTickets {
@Override
public User buyTickets() {
User user = new User();
user.setHaveTicket(true);
System.out.println("实际需要代理的目标对象");
return user;
}
}
// 基于继承实现的静态代理
public class InheritanceProxy extends RealProxyTarget {
public User buyTickets() {
System.out.println("开始购票");
// 执行实现的目标对象
User user = super.buyTickets();
System.out.println("结束~~~~~~~~~");
return user;
}
public static void main(String[] args) {
InheritanceProxy proxy = new InheritanceProxy();
System.out.println(proxy.buyTickets());
// 开始购票
//实际需要代理的目标对象
//结束~~~~~~~~~
}
}
静态代理的优缺点:
优点: 可以在不修改目标对象的提前下扩展目标对象的功能
缺点: 1、由于目标对象和代理对象实现同一个接口,会产生过多的代理类。
2、不易维护,一旦接口改变,各个实现类都需要更改代码。以上两点都是针对基于接口实现的静态代理。
动态代理
静态代理存在着一下问题:
一方面,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。针对以上的问题可以通过动态代理来完成修改。
所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
java中实现动态代理的方式有
- 利用jdk api实现的动态代理
- 使用cglib实现的动态代理
基于jdk实现的动态代理大致流程为:
- 定义接口
- 实现接口
- 定义代理类,继承InvocationHandler接口
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。动态在内存中创建对象,从而实现对目标对象的代理功能,其又称为jdk代理或者接口代理。实现示例如下:
// 定义接口
public interface BuyTickets {
User buyTickets();
}
// 实现接口
public class TargetObject implements BuyTickets {
@Override
public User buyTickets() {
System.out.println("已经购买成功");
return null;
}
}
// 定义代理对象
public class DynamicProxyObject {
// 维护的目标对象
private Object targetObject;
public DynamicProxyObject() {
this.targetObject = new Object();
}
// 创建代理对象(Proxy对象)
public Object creatProxyObject(Object target) {
// 获取类加载器
ClassLoader loader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(target);
return Proxy.newProxyInstance(loader, interfaces, handler);
}
private class DynamicProxyHandler implements InvocationHandler {
private Object proxyTargetObject;
public DynamicProxyHandler(Object proxyTargetObject) {
this.proxyTargetObject = proxyTargetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始购票");
Object invoke = method.invoke(proxyTargetObject, args);
System.out.println("已结束,正在结算中!");
return invoke;
}
}
}
// 测试代码
public class DynamicProxyTest {
@Test
public void dynamicProxy() {
DynamicProxyObject proxyObject = new DynamicProxyObject();
// 在做类型转换的时候需要注意,要用接口来承接,如果使用实现类来承接会报类型转换异常的(基于接口的哦)
BuyTickets proxy = (BuyTickets) proxyObject.creatProxyObject(new TargetObject2());
// 输出代理对象的信息
System.out.println(proxy.getClass());
// 执行代理方法
proxy.buyTickets();
// class com.sun.proxy.$Proxy4
//开始购票
//已经购买成功
//已结束,正在结算中!
}
}
特点:动态代理对象不需要实现接口,但是目标对象必须实现接口,否则不能实现动态代理这一个功能的。
注意:Java代理不可以对类进行代理,只能针对接口代理,如果在进行类型转行的时候是java类型则会出现类型转换。
cglib动态代理:
cglib动态代理 利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
其实现的动态代理过程如下:
- 创建Enhancer对象
- 用Enhancer对象设置代理类的父类(被代理类)
- 创建回调对象(回调类实现 MethodInterceptor 接口)
- 用Enhancer对象设置回调对象
- 用Enhancer对象创建代理对象
引入cglib依赖:
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
创建被代理对象:
// 被代理对象
public class ProxyObject implements BuyTickets {
public void sayHello() {
System.out.println("使用cglib的动态代理的sayHello方法(public)");
}
public final void useFinal() {
System.out.println("使用cglib的动态代理的useFinal方法(final修饰)");
}
@Override
public User buyTickets() {
System.out.println("买票成功!");
return null;
}
}
创建回调对象:
// 创建回调对象
public class UserCglib implements MethodInterceptor {
/**
* 重写方法拦截在方法前和方法后加入业务
*
* @param o obj为目标对象
* @param method method为目标方法
* @param objects 为参数,
* @param methodProxy CGlib方法代理对象
*/
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("-------------------");
return methodProxy.invokeSuper(o, objects);
}
}
测试使用cglib实现的动态代理:
public class CglibProxyTest {
@Test
public void CglibProxyTest() {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置要Enhancer代理类的父类(被代理类)
enhancer.setSuperclass(ProxyObject.class);
// 设置Enhancerr对象设置回调对象
enhancer.setCallback(new UserCglib());
// 创建代理对象
ProxyObject proxy = (ProxyObject) enhancer.create();
// 调用代理对象的方法
proxy.sayHello();
proxy.useFinal();
proxy.buyTickets();
//-------------------
//使用cglib的动态代理的sayHello方法(public)
//使用cglib的动态代理的useFinal方法(final修饰)
//-------------------
//买票成功!
}
}
总结:
动态代理和静态代理的区别:
1、静态代理在编译时就已经实现,编译完成后代理对象实际就是一个class文件
2、动态代理是在运行时动态生成的,即为编译完成后是没有class文件的,而是在运行时自动生成字节码并加到jvm中的
jdk实现动态代理和cglib动态代理的区别:
- jdk实现动态代理只能针对实现类的接口生成代理,而不是针对类,所以目标对象必须实现了接口(接口动态代理)。
- cglib实现动态代理只要针对实现类代理的,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强。但是由于是继承关系的,所以被代理类最好不要使用final修饰其或者方法,使其变成不可变的类或方法,因为针对final修饰的类和方法是不可被继承的。
spring中何时选择cglib实现动态代理或jdk动态代理?
- 当bean实现接口时,spring默认就会选择jdk动态代理方式。
- 而当bean没有实现接口时,spring会默认使用cglib实现,
- 强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)
- 这些的bean也适用于平时工作中的一个普通javaBean。