代理模式 Proxy 概述
- 什么是代理模式: 为目标对象提供一种代理,通过代理控制对目标对象的访问,代理对象在发起访问的客户端与需要访问的目标对象之间起到中介的作用
- 通过案例解释代理对象: 房客通过中介租房子,房客相当于发起访问的客户端,中介相当于代理类,房东相当于被代理的目标对象,房客只与中介打交道,但是是否将房子租给房客的实际功能使用房东来决定,并且房东只管将房子租出去,对于物业卫生,是有中介来完成
- 代理模式的优点: ,保护目标对象只能通过代理类去调用获取,增强目标对象,例如房东只管往外租房子,而卫生,水电交由代理类来完成,代理对象与调用者分离,一定程度的降低了代码耦合性,增强扩展
- 代理模式的分类: 静态代理,动态代理(接口代理,Cgilb代理)
静态代理
什么是静态代理: 在代码中创建指定的代理类,将被代理类组合到代理类中,代理类中创建代理方法,代理方法中增强被代理类的功能,由代理类调用被代理类,客户端只调用代理类即可
通过静态代理来实现上面的租房子案例
- 代理类与被代理类有相同的行为,创建抽象接口,定义相同行为的抽象方法
/*接口*/
public interface Lease {
//行为方法
public void toLease();
}
- 创建被代理类房东,实现接口,重写抽象方法,方法中定义房东同意将房子租出去
public class Landlord implements Lease {
@Override
public void toLease() {
System.out.println("我是房东,我同意出租自己的房子");
}
}
- 创建代理类,也就是中介,实现接口,代理类代理执行被代理的功能,所以需要将被代理类组合到代理类中,此处通过构造器赋值,重写抽象方法,该方法就是代理方法, 代理类可以增强被代理类的行为,例如打扫卫生,交水电费,在代理方法中通过组合进来被代理对象调用代理方法以外,增强调用打扫卫生,交水电费方法
public class Company implements Lease {
//代理类持有被代理类对象(构造代理类对象时需要传入被代理类对象)
private Landlord landlord;
//通过构造器对被代理类赋值
public Company(Landlord landlord){
this.landlord=landlord;
}
//代理类增强委托类的行为,创建增强服务行为
public void add1(){
System.out.println("我是中介代理类,增加打扫卫生服务");
}
public void add2(){
System.out.println("我是中介代理类,交水电费");
}
//代理行为
@Override
public void toLease() {
//代理行为中增强被代理类
add1();
add1();
//被代理类真实执行
landlord.toLease();
}
}
- 客户端调用测试
//创建被代理类房东对象
Landlord landlord = new Landlord();
//创建代理对象
Company company = new Company(landlord);
//通过代理对象调用执行功能
company.toLease();
JDK动态代理
什么是动态代理: JDK动态代是基于反射实现的,被代理类需要有接口,被代理执行的方法要在接口中,并且可以被重写,否则不能动态代理,在程序调用到代理类对象(接口)时,由 JVM 根据获取到的业务实现类对象,以及方法名,动态的创建一个.Class文件,Class文件被字节码引擎执行生成代理类对象,通过该代理类的对象进行方法调用
代码示例
- 创建被代理类需要实现接口的
//被代理类: 房东
class Landlord implements Lease{
@Override
public void toLease(){
System.out.println("房东同意将房子租给房客");
}
}
- 创建被代理类对象,定义需要被代理执行的方法
//被代理类: 房东
class Landlord implements Lease{
@Override
public void toLease(){
System.out.println("房东同意将房子租给房客");
}
}
- 创建代理类方式一: 实现InvocationHandler接口,重写invoke方法
class Company implements InvocationHandler{
//代理类持有目标对象
private Object lease;
//通过构造器实例化持有的被代理类
public Company(Object lease){
this.lease =lease;
}
public void add1(){
System.out.println("对代理增强,中介打扫卫生");
}
public void add2(){
System.out.println("对代理增强,中介交水电费");
}
/**
* 解释newProxyInstance()方法,该方法需要三个参数
* 1.ClassLoader: 指定被代理对象使用的类加载器
* 2.Class<?>[]: 被代理对象实现的接口,
* 3.InvocationHandler: 可以将当前类实现InvocationHandler接口,
* 重写invoke()方法,多态的原因当前类this就是InvocationHandler,
* 或者通过匿名接口的方式实现
* @return
*/
//程序运行时动态创建被代理类对象(返回的Objcet对象就是被代理的对象)
public Object getProxy(){
//通过Proxy获取
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),//获取当前对象的类加载器
lease.getClass().getInterfaces(), //获取被代理类的接口
this);//回调函数(当前对象为回调类)
}
/**
* 该方法就可以看为是代理方法
* @param proxy 执行newProxyInterface()方法返回的对象
* @param method 需要代理执行的方法(被代理类中与接口中同时存在,并且允许重写的)
* @param args 被代理执行的方法需要的参数,不需要就传null
* @return 有就返回,没有就返回null
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
add1();
add2();
method.invoke(lease, args);
return null;
}
}
- 实现接口方式的调用测试
public static void main(String[] args) throws Throwable {
//创建被代理对象
Landlord landlord=new Landlord();
//创建代理类对象(代理类对象持有被代理对象)
Company company=new Company(landlord);
//调用动态代理类对象中的方法获取Proxy对象
Object proxy=company.getProxy();
//动态代理类调用invoke(传入的上面获取到的对象,
//被代理类中的代理行为方法,args是方法参数,根据需求传入)
company.invoke(proxy,Landlord.class.getMethod("toLease"),null);
}
- 创建代理类方式二,通过接口匿名实现的方式,在newProxyInstance()方法中匿名实现InvocationHandler接口,重写invoke方法
class Company {
//代理类持有目标对象
private Object lease;
//通过构造器实例化持有的被代理类
public Company(Object lease){
this.lease =lease;
}
public void add1(){
System.out.println("对代理增强,中介打扫卫生");
}
public void add2(){
System.out.println("对代理增强,中介交水电费");
}
public Object getProxy(){
return Proxy.newProxyInstance(
this.getClass().getClassLoader(),
lease.getClass().getInterfaces(),
//通过接口的匿名实现方式
new InvocationHandler() {
//该方法就可以看为,代理类中的代理方法
//方法中编写需要代理添加的增强功能代码
//然后通过反射执行被代理类需要执行的方法
//该方法中的Object proxy与newProxyInstance返回的Object对象是同一个
//method 是需要代理执行的方法,args是代理执行的方法所需要的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
add1();
add2();
Object returnVal = method.invoke(lease,args);
//返回方法执行后的结果,如果没有则返回null
return returnVal;
}
}
);
}
}
- 调用测试
public static void main(String[] args) throws Throwable {
//创建被代理对象
Landlord landlord=new Landlord();
//创建代理类对象(代理类对象持有被代理对象)
Company company=new Company(landlord);
//调用动态代理类对象中的方法获取Proxy对象
Object proxy=company.getProxy();
//强制类型转换,转换为接口类型
Lease lease = (Lease) proxy;
lease.toLease();
}
根据代码示例进行总结
动态代理中的重点主要是通过Proxy.newProxyInstance()方法在内存中动态生成代理对象,通过InvocationHandler接口中的invoke()方法,通过反射,执行被代理的方法
根据两种不同方式的调用执行,区分实现InvocationHandler接口,与通过接口的匿名实现方式设计JDK动态代理的不同
Cglib动态代理
- 什么是Cglib动态代理: 底层采用ASM字节码生成的,该方式的动态代理是针对类的动态代理,若需要代理一个类,Cglib会生成一个该类的子类,通过子类覆盖父类,通过继承调用子类中重写的方法,假设一个类需要Cgilb动态代理,由于Cglib动态代理是通过继承与重写实现的,则该类不可以使用final修饰,该类中的方法不可以使用final,static修饰
- 使用Cglib动态代理需要引入外部Jar包 maven坐标
<!--cglib依赖-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
- 创建代理类,代理了需要实现MethodInterceptor接口,并重写intecept()方法,该方法就是代理方法,首先获取代理对象,也就是此处的第三步,在通过第三步获取到的代理类执行目标方法时,会自动出发执行intecept()
//1.定义一个类,该类实现MethodInterceptor接口,并重写intercept抽象方法
public class MyCglib implements MethodInterceptor {
//2.持有被代理类
private Object target;
//3.定义构造器(持有被代理类对象,创建该类对象时必须传入被代理类对象)
public MyCglib(Object target){
this.target=target;
}
//3. 创建获取运行期需要代理的代理类对象方法
public Object getProxy(){
//创建Enhancer对象
Enhancer enhancer=new Enhancer();
//设置父类class(继承被代理类)
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);//回调类为当前类
return enhancer.create();//返回代理类对象
}
//4. 增强方法
public void add1(){
System.out.println("我是代理类的增强行为方法执行");
}
//行为方法代理方法,在该方法中实现被代理方法的调用,增强
@Override
public Object intercept(Object o,
Method method,//行为方法
Object[] objects, //代理方法执行需要的参数
MethodProxy methodProxy) //代理对象
throws Throwable {
add1();
//执行真正的行为(代理对象执行被代理类对象,与执行该方法所需要的参数objects
Object result= method.invoke(target,objects);
return result;
}
}
- 调用测试
@Test
public void test0() throws NoSuchMethodException {
//创建被代理类对象
Landlord land=new Landlord();
//传入被代理类对象,创建代理类对象
MyCglib myCglib=new MyCglib(land);
//是基于继承方式实现的代理所以将获取到的被代理类使用多态赋值给父类引用
Landlord land2= (Landlord) myCglib.getProxy();
land2.toLease();
}
Spring代理的选择
- 当Spring中的Bean有接口时,Spring则使用JDK动态代理,当被代理Bean没有实现接口时,Spring使用Cglib动态代理,也可以强制使用Cglib代理,在Spring配置文件中添加
<aop:aspectj-autoproxy proxy-target-cass=“true”/>
代理模式与其它相关设计模式
- 代理模式与装饰者模式实现方式比较相似,但是目的不同
装饰者模式更关注与给被装饰者对象添加行为,代理模式更关注与控制访问,更加注重设置代理的方式增强被代理类,给被代理类增强某些行为 - 代理模式与适配器模式
适配器模式主要考虑改变被适配者的接口,而代理模式不可以改变被代理的接口
代理模式在框架中的应用案例
- Spring 中的 AOP 底层使用Cglib代理
- Dubbo 使用JDK 动态代理