今天讲一个比较深层的知识点:JDK动态代理,这是个可以让小白在大咖面前装逼的神器,顺便送你一个代理模式的温习机会。
代理模式场景
为了引出动态代理的用法,我们先看看代理设计模式,这能让你了解JDK动态代理的应用场景的同时,让你记忆深刻。代理模式是通过代理对象作为中间人来访问目标对象,这样可以完美的遵循开闭原则,从而避免修改目标对象来满足需求而降低可维护性。
现实生活中比较常见的各种中间商、经纪人都是活生生的代理模式例子。咱们拿明星经纪人来说
上述关系现实中还是非常复杂的,比如什么干爹啦、劈腿啦、潜规则啦,巴拉巴拉的.....剥茧抽丝后,其实流程可简化这样
老板不直接接触明星,而是直接和经纪人商谈,毕竟经纪人无论在经验和精力上都有优势。这里经纪人其实就是代理对象,明星就是目标对象,老板就是调用方。转换为代码流程如下
这里需要认真的强调一点:代理模式侧重于控制访问,代理对象不会改变目标对象的职责和能力,它提供与目标对象相同的接口,但会增加相应的逻辑来控制访问目标对象。
有些网友提出代理模式是为了在目标对象的基础上增强功能,如果较真的话小编并不不认同此种说法,因为增强基类的功能那是装饰模式干的活;代理模式和委托模式也有区别,后者是为了提供统一的接口服务,便于方便切换底层实现。
如果大家对设计模式感兴趣,可以留言给 码农神说 提出“设计模式系列专题”的文章需求。
代理模式实现
代理模式的演示实现如下(为了方便观众观看,我会把代码集中在App.java中,项目中不允许)
public class App{
public static void main( String[] args )
{
//代理对象(经纪人)
Broker broker = new Broker();
System.out.println("A老板的唱歌演出需求");
broker.doSing();
System.out.println("---------------");
System.out.println("B老板的跳舞演出需求");
broker.doDance();
}
}
/**
* 经纪人
*/
class Broker{
/**
* 唱歌演出
*/
public void doSing(){
//演出前业务处理
System.out.println("1.检查节目是否和明星的调性匹配");
System.out.println("2.出场费是否满足");
//演出
new Star().doSing();
//演出后业务处理
System.out.println("1.出场费尾款");
System.out.println("2.粉丝维护");
}
/**
* 跳舞演出
*/
public void doDance(){
//演出前业务处理
System.out.println("1.检查节目是否和明星的调性匹配");
System.out.println("2.出场费是否满足");
//演出
new Star().doDance();
//演出后业务处理
System.out.println("1.出场费尾款");
System.out.println("2.粉丝维护");
}
}
/**
* 明星
*/
class Star{
/**
* 唱歌技能
*/
public void doSing(){
System.out.println("唱歌");
}
/**
* 跳舞技能
*/
public void doDance(){
System.out.println("跳舞");
}
}
老板不直接接触明星,而是通过经纪人满足业务需求。细心的同学会发现,经纪人的业务处理中存在大量重复代码,当然你可以把演出前后的业务封装成方法调用如
/**
* 唱歌演出
*/
public void doSing(){
//演出前业务处理
beforeDo();
//演出
new Star().doSing();
//演出后业务处理
afterDo();
}
但依然不美,试想如果能把经纪人的业务技能直接一一匹配到明星的技能(doSing,doDance)就方便了,于是引出了JDK动态代理。
用JDK动态代理重构
1. 为了实现“动态”需要使用面向接口的编程思想,把明星和经纪人抽象出一个共同的接口
/**
* 明星和经纪人的接口
*/
interface IAct {
void doSing();
void doDance();
}
2. 通过实现InvocationHandler接口来做代理业务
/**
* 经纪人
*/
class Broker implements InvocationHandler {
//目标对象
private Object target;
public Broker(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
//演出前业务处理
System.out.println("1.检查节目是否和明星的调性匹配");
System.out.println("2.出场费是否满足");
//明星演出技能
Object object=method.invoke(target,args);
//演出后业务处理
System.out.println("1.出场费尾款");
System.out.println("2.粉丝维护");
return object;
}
}
3. 动态创建代理对象并处理业务
public static void main( String[] args ){
//目标对象
IAct star=new Star();
//使用newProxyInstance创建动态代理对象
IAct broker=(IAct) Proxy.newProxyInstance(
IAct.class.getClassLoader(),
star.getClass().getInterfaces(),
new Broker(star)
);
//业务处理
System.out.println("A老板的唱歌演出需求");
broker.doSing();
System.out.println("---------------");
System.out.println("B老板的跳舞演出需求");
broker.doDance();
}
经纪人执行doSing,经过业务逻辑处理后直接映射到明星的doSing,这样就减少了很多重复的代码,提高了可维护性。
总结JDK实现代理模式流程如下
1. 抽象出目标对象的接口
2. 实现接口InvocationHandler创建代理业务
3. 使用newProxyInstance创建代理对象
4. 业务处理
全部代码如下
package com.zhaiqianfeng;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class App{
public static void main( String[] args ){
//目标对象
IAct star=new Star();
//使用newProxyInstance创建动态代理
IAct broker=(IAct) Proxy.newProxyInstance(
IAct.class.getClassLoader(),
star.getClass().getInterfaces(),
new Broker(star)
);
//业务处理
System.out.println("A老板的唱歌演出需求");
broker.doSing();
System.out.println("---------------");
System.out.println("B老板的跳舞演出需求");
broker.doDance();
}
}
/**
* 明星和经纪人的接口
*/
interface IAct {
void doSing();
void doDance();
}
/**
* 经纪人
*/
class Broker implements InvocationHandler {
//目标对象
private Object target;
public Broker(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
//演出前业务处理
System.out.println("1.检查节目是否和明星的调性匹配");
System.out.println("2.出场费是否满足");
//明星演出技能
Object object=method.invoke(target,args);
//演出后业务处理
System.out.println("1.出场费尾款");
System.out.println("2.粉丝维护");
return object;
}
}
/**
* 明星
*/
class Star implements IAct {
/**
* 唱歌技能
*/
public void doSing(){
System.out.println("唱歌");
}
/**
* 跳舞技能
*/
public void doDance(){
System.out.println("跳舞");
}
}
Java8 lambda表达式重构
如果只是临时业务处理,可以使用匿名类或Java8的lambda表达式可以更优
public static void main(String[] args) {
//目标对象
IAct star = new Star();
//使用newProxyInstance创建动态代理
IAct broker = (IAct) Proxy.newProxyInstance(
IAct.class.getClassLoader(),
star.getClass().getInterfaces(),
(proxy, method, args2) -> {
//演出前业务处理
System.out.println("1.检查节目是否和明星的调性匹配");
System.out.println("2.出场费是否满足");
//明星演出技能
Object object = method.invoke(star, args2);
//演出后业务处理
System.out.println("1.出场费尾款");
System.out.println("2.粉丝维护");
return object;
}
);
//业务处理
System.out.println("A老板的唱歌演出需求");
broker.doSing();
System.out.println("---------------");
System.out.println("B老板的跳舞演出需求");
broker.doDance();
}
写在最后
JDK动态代理实际上是在运行时通过反射的方式来实现的,将代理的方法调用转到到目标对象上,最终将目标对象生成的任何结果返回给调用方。由于这是个链式调用,所以很方便代理在目标对象方法调用前后增加处理逻辑。根据这种思路可以在多种设计模式中使用JDK的动态代理比如代理模式、Facade、Decorator等。
在面向方面编程(AOP)也应用广泛,如事务管理、日志记录、数据校验等,主要是将横切关注点从业务逻辑中分离出来,所以一通百通。
补充一点,由于JDK的不断优化,到JDK8的时候JDK的动态代理不比CGLIB效率低,大家可以做些实验。
End
版权归@码农神说所有,转载须经授权,翻版必究
转载可联系助手,微信号:codeceo-01
往期精彩
给“小白”漫画+图示讲解MyBatis原理,就问香不香!
辟谣:程序员不配谈恋爱?你错的可以!真相来了
面试官:CAP都搞不清楚,别跟我说你懂微服务!
互联网人的娱乐精神之28岁退休 & P8和生活助理的故事
千万不能让程序员给娃娃取名字