Java动态代理详解(一)

简介

日常生活中我们经常会碰到代理模式,例如我们找房产中介帮我们介绍房子,找代购来帮我们购买商品,找保洁帮我们打理房间,我们在无形中就运用到了代理模式。

在软件开发中,也有一种设计模式可以提供与房产中介与代购等的功能。由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为“代理”的第三者来实现间接访问,该设计模式就被称为代理模式。

代理模式是一种使用率非常高的模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.

其主要意思就是为其他对象提供一种代理以控制对这个对象的访问。

代理模式的通用类图如下所示:


代理模式包含如下三个角色:

  • Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
  • RealSubject具体主题角色:也叫作被委托角色、被代理角色。是业务逻辑的具体执行者。
  • Proxy代理主题角色:也叫作委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕后做预处理和善后处理的工作。

在Java语言中常用的代理有静态代理和动态代理,我们先来简单看一下JDK实现的静态代理

JDK静态代理

我们通过一段代码来介绍静态代理:

Subject抽象主题角色代码:

public interface Subject {
	public void request();
}

RealSubject具体主题角色代码:

public class RealSubject implements Subject {
	@Override
	public void request() {
		System.out.println("RealSubject : request()");
	}
}

Proxy代理主题角色代码:

public class SubjectProxy implements Subject {
	// 目标代理对象
	private Subject subject = new RealSubject();
	
	@Override
	public void request() {
		System.out.println("SubjectProxy : request()");
		// 调用目标方法前的处理方法
		PreRequest();
		// 调用目标对象的方法
		subject.request();
		// 调用目标方法后的处理方法
		PostRequest();
	}

	public void PreRequest() {
		System.out.println("SubjectProxy : PreRequest()");
	}

	public void PostRequest() {
		System.out.println("SubjectProxy : PostRequest()");
	}
}

客户端代码:

public class Client {
	public static void main(String[] args) {
		// 创建代理对象, 并使用接口对其进行引用
		Subject subject = new SubjectProxy();
		// 调用目标方法
		subject.request();
	}
}

运行结果:

SubjectProxy : request()
SubjectProxy : PreRequest()
RealSubject : request()
SubjectProxy : PostRequest()

静态代理的实现很简单,SubjectProxy代理类持有一个RealSubject的目标对象,通过代理类可以来间接访问该目标对象。虽然静态代理简单易懂,但是,静态代理中的代理类和目标对象紧密地耦合在了一起,如果Subject的另外一个实现类也需要进行代理控制,那我们就需要重新写一个代理类,这样就会产生需要重复代码,不能做到代码复用;而且,一旦接口增加方法,那么目标对象与代理对象都需要进行维护。而使用动态代理就能够解决上述问题。

JDK动态代理

我们先来看如何通过动态代理实现与静态代理一样的功能,源码如下:

Subject抽象主题角色代码:

public interface Subject {
	public void request();
}

RealSubject具体主题角色代码:

public class RealSubject implements Subject {
	@Override
	public void request() {
		System.out.println("RealSubject : request()");
	}
}

Proxy代理主题角色代码:

public class SubjectProxy implements InvocationHandler {
	private Subject subject;
	
	public Object getInstance(Subject subject) {
		this.subject = subject;
		Class clazz = subject.getClass();
		Object obj = Proxy.newProxyInstance(subject.getClass().getClassLoader(), clazz.getInterfaces(), this);
		return obj;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("SubjectProxy : request()");
		// 调用目标方法前的处理方法
		PreRequest();
		// 调用目标方法
		method.invoke(subject, args);
		// 调用目标方法后的处理方法
		PostRequest();
		return null;
	}
	
	public void PreRequest() {
		System.out.println("SubjectProxy : PreRequest()");
	}

	public void PostRequest() {
		System.out.println("SubjectProxy : PostRequest()");
	}
}

客户端代码:

public class Client {
	public static void main(String[] args) {
		// 创建被代理对象
		Subject subject = new RealSubject();
		// 创建代理对象, 并使用接口对其进行引用
		Subject subjectProxy = (Subject) (new SubjectProxy()).getInstance(subject);
		// 调用目标方法
		subjectProxy.request();
	}
}

运行结果:

SubjectProxy : request()
SubjectProxy : PreRequest()
RealSubject : request()
SubjectProxy : PostRequest()

可以看到,通过动态代理实现了与静态代理一样的功能,同时,如果Subject的另外一个实现类也需要进行代理控制,那么我们只需要将客户端代码中的

Subject subject = new RealSubject();

修改为

Subject subject = new AnotherRealSubject();

即可,不用修改其余代码,动态代理是将代码中横向切面的逻辑剥离了出来,对该部分逻辑实现了代码复用。

同时,若接口增加了方法,那么我们只需要对目标对象进行维护,而代理对象并没有实现接口,所以我们不用修改代理对象。很好地解决了静态代理的问题。

但是,动态代理也有如下缺点:

  • 动态代理的实现比较复杂且难以理解;
  • 动态代理要求代理对象必须实现某个接口;
  • 动态代理会为接口中声明的所有方法添加上相同的逻辑,不够灵活;

这只是JDK动态代理所存在的一些缺陷,动态代理还可以使用一些其他开源库来实现,如使用CGLIB库就可以解决上述问题。

cglib动态代理

cglib - Byte Code Generation Library是一个强大的、高性能的代码生成库。cglib不要求代理对象必须实现某个接口,而且cglib很灵活,它可以为目标对象的不同方法添加上不同的逻辑,它广泛地被使用于多种AOP框架中,例如Spring AOP、dynaop等,为它们提供方法的interception(拦截)。cglib源码托管在github中:https://github.com/cglib/cglib

cglib的组成结构如下:


cglib底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了cglib库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

下面,我们通过一个例子来看一下cglib相比于JDK实现动态代理的强大之处。

首先,我们需要在工程的POM文件中引入cglib的dependency,这里我们使用的是3.2.6版本:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.6</version>
</dependency>

程序源码

RealSubject具体主题角色代码:

public class RealSubject {
	public void request() {
		System.out.println("RealSubject : request()");
	}
}

RealSubject并没有实现任何接口。

Proxy代理主题角色代码:

public class SubjectProxy implements MethodInterceptor {
	// 目标对象
	private Object target;
	
	public Object getInstance(Object target) {
		this.target = target;
		// 工具类
		Enhancer enhancer = new Enhancer();
		// 设置父类
		enhancer.setSuperclass(target.getClass());
		// 设置回调方法
		enhancer.setCallback(this);
		// 创建子类,即代理对象
		return enhancer.create();
	}

	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("SubjectProxy : request()");
		PreRequest();
		// 执行目标对象的方法
		Object returnValue = method.invoke(target, args);
		PostRequest();
		return returnValue;
	}
	
	public void PreRequest() {
		System.out.println("SubjectProxy : PreRequest()");
	}

	public void PostRequest() {
		System.out.println("SubjectProxy : PostRequest()");
	}
}

客户端代码:

public class Client {
	public static void main(String[] args) {
		// 创建被代理对象
		RealSubject realSubject = new RealSubject();
		// 创建代理对象, 并使用接口对其进行引用
		RealSubject realSubjectProxy = (RealSubject) (new SubjectProxy()).getInstance(realSubject);
		// 调用目标方法
		realSubjectProxy.request();
	}
}

运行结果:

SubjectProxy : request()
SubjectProxy : PreRequest()
RealSubject : request()
SubjectProxy : PostRequest()

可以看到,通过cglib实现的动态代理同样可以实现与JDK动态代理一样的功能,但是cglib不要求被代理类必须实现某一接口,cglib能够实现的功能远不至此,它还可以实现很多强大的功能,如修改方法返回值、对特定的方法进行拦截、对bean进行复制、修改bean属性等多种操作,这里就不再具体介绍,有兴趣的可以自己查看相关资料。

总结

首先为大家介绍了什么是代理模式,并使用简单的例子为大家介绍了静态代理、JDK动态代理和cglib动态代理,并分析了静态代理、动态代理和cglib各自的优缺点,使大家对代理模式有了一些大致的了解。我们后续会对JDK动态代理的实现,以源码的角度来进行详细讲解。

参考资料

秦小波:《设计模式之禅》

CGLIB(Code Generation Library)详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值