1. 代理模式使用场景
生活中的房产中介、婚介、经纪人、快递、无侵入式日志监听等都是代理模式的实际体现。**代理模式的定义也非常简单,是指为其他对象提供一种代理,以控制这个对象的访问。代理对象在客户端和目标对象之前起到中介的作用,代理模式属于结构型设计模式。使用代理模式有两个目的:一是保护目标对象,二是增强目标对象
代理模式又被分为两种,一是静态代理,二是动态代理。
2. 静态代理
静态代理就是代理类在编译期间就创建好了,是手动创建的类。即代理类和目标对象的关系在程序运行前就已经存在了。它适合用于目标对象中要代理的方法比较少且确定的情况,如果目标对象中有方法很多就会很难受,要在代理类中写一堆的代理方法
2.1 静态代理场景演绎
下面来模拟一个场景,就是房主提供房源,房产中介帮助销售,成功销售后并收取 2% 的利润
首先需要一个房主接口类 HomeOwnerInterface
/**
* 房主接口,拥有一套房,交给房产中介打算转手卖掉
*
* 具体的房主对象和房产中介对象都要实现这个接口
*/
public interface HomeOwnerInterface {
/**
* prise - 房主期望的卖掉房子的价格
*/
public void sailHome(double prise);
}
现在张三找到房产中介,想卖掉自己的房子,因此需要创建张三房主类 ZhangSanHomeOwner
/**
* 房主张三
*/
public class ZhangSanHomeOwner implements HomeOwnerInterface {
@Override
public void sailHome(double prise) {
System.out.println("房主卖掉房子获得" + prise + "元");
}
}
我们还需要创建房产中介对象 StaticHomeAgent 来帮助房主卖房子
/**
* 房产中介,帮助房主卖房,并收取2%的利润,作为房主的代理对象,也要实现房主接口
*/
public class StaticHomeAgent implements HomeOwnerInterface{
private final HomeOwnerInterface homeOwnerInterface;
public StaticHomeAgent(HomeOwnerInterface homeOwnerInterface) {
this.homeOwnerInterface = homeOwnerInterface;
}
@Override
public void sailHome(double prise){
homeOwnerInterface.sailHome(prise * (1 - 0.02));
System.out.println("房产中介收取" + prise * 0.02 + "的利润");
}
}
最后顾客找房产中介买房,房产中介以房主期望的价格卖掉房子,并收取 2% 的提成
/**
* 主函数,顾客找房产中介买房,房产中介以房主期望的价格卖掉房子,并收取 2% 的提成
*/
public class StaticMain {
public static void main(String[] args) {
ZhangSanHomeOwner owner = new ZhangSanHomeOwner();
StaticHomeAgent homeAgent = new StaticHomeAgent(owner);
homeAgent.sailHome(10000);
}
}
最后看一下控制台输出的结果
2.2 静态代理总结
静态代理的代理类是程序编译前就已经编写好的,若是目标对象,也就是这里的房主对象有很多的方法需要代理,那代理类实现和房主实现同样的接口,需要对每个接口分别进行处理,十分的麻烦,无法进行统一的处理
3. 动态代理
代理类是在程序运行时创建的代理方式称为动态代理,相比于静态代理而言,动态代理的优势是可以对代理类中的方法进行统一的处理,而不用修改每个代理类中的方法。动态代理常用的又有两种:一是 jdk 动态代理,**二是 cglib 动态代理,**两种方式各有好处
3.1 jdk 动态代理
3.1.1 jdk 动态代理代码实现
在 jdk 动态代理中主要涉及两个类,java.lang.reflect.InvocationHandler 和 java.lang.reflect.Proxy,需要创建一个代理类来实现InvocationHandler 接口,这个接口中只有一个方法 invoke,我们在对目标对象中所有的方法调用都会变成是调用invoke 方法。这样我们就可以在 invoke 方法中添加一些特定的逻辑进行处理
现在我们对上面静态代理的案例进行改造,实现动态代理卖房子。房主类不需要变,只需要新建一个动态代理类
/**
* 房产中介,帮助房主卖房,并收取2%的利润,作为房主的代理对象
*/
public class DynamicHomeAgent{
private static final List<HomeOwnerInterface> homeOwners = new ArrayList<>();
static {
homeOwners.add(new ZhangSanHomeOwner());
}
public HomeOwnerInterface getHomeOwnerProxy(){
HomeOwnerInterface homeOwner = homeOwners.remove(0);
return (HomeOwnerInterface) Proxy.newProxyInstance(
homeOwner.getClass().getClassLoader(),
homeOwner.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("房产中介收取" + (double) args[0] * 0.02 + "元的利润");
args[0] = (double)args[0] * (1 - 0.02);
return method.invoke(homeOwner,args);
}
});
}
}
最后通过主函数进行测试
/**
* 主函数,模拟房主提供房源,顾客找房产中介买房
*/
public class DynamicMain {
public static void main(String[] args) {
HomeOwnerInterface homeOwnerProxy = new DynamicHomeAgent().getHomeOwnerProxy();
homeOwnerProxy.sailHome(10000);
}
}
最后控制台输出结果
3.1.2 jdk 动态代理核心 API
jdk 动态代理的用法已经写在代码里了,咱对这里的用法和核心 api 做一个回顾
jdk 的动态代理,要求被代理的对象所属类必须实现一个以上的接口,代理对象的创建使用 Proxy.newProxyInstance 方法,该方法中有三个参数:
- ClassLoader loader :被代理的对象所属类的类加载器
- Class<?>[] interfaces :被代理的对象所属类实现的接口
- InvocationHandler h :代理的具体代码实现
在这三个参数中,前面两个都容易理解,最后一个 InvocationHandler 是一个接口,它的核心方法 invoke 中也有三个参数,一一来看:
- Object proxy :代理对象的引用(代理后的)
- Method method :代理对象执行的方法
- Object[] args :代理对象执行方法的参数列表
具体的代理逻辑就在 InvocationHandler 的 invoke 方法中编写。
3.2 cglib 动态代理
使用 jdk 动态代理需要我们对目标对象进行接口的抽取,而 Cglib 不用,它可以直接使用字节码增强的技术,同样实现动态代理。
3.2.1 导包
使用 cglib 动态代理之前需要先导 cglib 的依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
使用 Cglib 时有几个小小的前提:被代理的类不能是 final 的( Cglib 动态代理会创建子类,final 类型的 Class 无法继承),被代理的类必须有默认的 / 无参构造方法(底层反射创建对象时拿不到构造方法参数)。
3.2.2 创建代理类
然后只需要新建一个基于 cglib 的代理类即可
/**
* cglib 动态代理,房产中介代理类
*/
public class CglibHomeAgent {
private static final List<HomeOwnerInterface> homeOwners = new ArrayList<>();
static {
homeOwners.add(new ZhangSanHomeOwner());
}
public HomeOwnerInterface getProxy(){
HomeOwnerInterface homeOwner = homeOwners.remove(0);
return (HomeOwnerInterface) Enhancer.create(homeOwner.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("房产中介收取" + (double) args[0] * 0.02 + "元的利润");
args[0] = (double)args[0] * (1 - 0.02);
return method.invoke(homeOwner,args);
}
});
}
}
3.2.3 使用主函数测试
/**
* 主函数,模拟房主提供房源,顾客找房产中介买房
*/
public class DynamicMain {
public static void main(String[] args) {
HomeOwnerInterface homeOwnerProxy = new DynamicHomeAgent().getHomeOwnerProxy();
homeOwnerProxy.sailHome(10000);
}
}
查看控制台,得到一样的结果

3.2.4 Cglib动态代理的核心 API
同 jdk 动态代理相似,Cglib 动态代理的内容相对较少,它只需要传入两个东西:
Class type:被代理的对象所属类的类型Callback callback:增强的代码实现
由于一般情况下我们都是对类中的方法增强,所以在传入 Callback 时通常选择这个接口的子接口 MethodInterceptor (所以也就有了上面代码中 new 的 MethodInterceptor 的匿名内部类)。MethodInterceptor 的 intercept 方法中参数列表与 InvocationHandler 的 invoke 方法类似,唯独多了一个 MethodProxy ,它是对参数列表中的 Method 又做了一层封装,利用它可以直接执行被代理对象的方法,就像这样:
// 执行代理对象的方法
method.invoke(proxy, args);
// 执行原始对象(被代理对象)的方法
methodProxy.invokeSuper(proxy, args);
3.3 cglib 和 jdk 动态代理对比
- jdk 动态代理生成的代理类实现了被代理对象的接口,cglib 代理生成的代理类继承了被代理对象
- jdk 动态代理和 cglib 动态代理都在运行期生成字节码,jdk 动态代理直接写 Class 字节码,cglib 代理使用 ASM 框架写 Class 字节码,cglib 代理实现更复杂,生成代理类的效率比 jdk 代理低
- jdk 动态代理调用代理方法是通过反射机制调用的,cglib 代理是通过 fastclass 机制直接调用方法的,cglib 代理的执行效率更高
4. 静态代理和动态代理本质区别
1、静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则
2、动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则(即被代理类增加了新的方法,代理类会自动生成)
5. 代理模式的优缺点
代理模式具有以下优点:
1、代理模式能将代理对象与真实被调用目标对象分离,可以起到保护目标对象的作用
2、在一定层度降低了系统的耦合性,扩展性好
3、可以增强目标对象的功能
代理模式也有缺点:
1、在客户端和目标对象总增加代理对象,会导致请求处理速度变慢
2、增加了系统的复杂度