动态代理是一种设计模式,它允许在不修改原对象的基础上增强该对象的方法。动态代理分为两种类型:JDK动态代理和Cglib动态代理。
- JDK动态代理:它针对接口进行代理,需要定义被代理的接口和其实现类。JDK动态代理通过实现
InvocationHandler
接口并重写invoke
方法来定义方法的增强方式。在运行过程中,会生成一个代理类,如XXProxy.class
,其中代理对象中实现的接口调用的是invoke
方法,该方法中植入了代理对象的逻辑。客户端通过代理类来调用真实对象的方法。 - Cglib动态代理:它可以在运行期间扩展Java类和Java接口。Cglib动态代理通过引入jar包
cglib
,使用增强器Enhancer
去创建一个业务代理类,实质上是为需要代理的真实类生成了一个子类,然后通过回调时调用intercept
方法植入逻辑。
动态代理的核心方法是通过newInstance
创建真实对象,并通过反射机制获取构造函数来构造真实对象,从而在运行过程中生成了代理类。这样,客户端调用时,实际上是通过代理类来间接访问真实对象。
比如说在Student类中有一个eat方法,它有个动作,“盛饭”,现在我想要在eat方法中添加两个动作“拿筷子”,“吃饭”。若直接添加在eat方法中,这属于侵入式修改,在实际开发中一般不会这样做。
我们可以把新增的代码交给一个“代理人”让他帮你处理
当需要使用该eat方法时,可以先找代理运行新增的方法,然后让代理再去找Student
如图:
这样就实现了无侵入式的给代码增加额外的功能,使得原本的方法体简洁明了,
具体实例:
杨超越是一个大明星,把她看作一个类,她有唱歌、跳舞的方法。
假如说有人要请她去唱歌、跳舞
但是开始唱歌需要一系列准备活动:“准备话筒,收钱”
跳舞也是一样需要准备:“准备场地,收钱”
这些行为肯定不能交给杨超越去做,她只需要专注于唱歌跳舞即可
所以我们把准备活动的行为交给代理去做。
即对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。
假如说这时有人需要杨超越唱歌跳舞,客户不可能直接先找到杨超越本人,肯定是找到她的代理。代理先完成准备工作,再去找杨超越唱歌跳舞
引用:
所以说代理人也需要有,唱歌、跳舞的方法
即对象有什么方法想被代理,代理就一定要有对应的方法
- 代理如何知道自己需要代理什么方法?
可以定义一个接口:接口内就是需要被代理的方法,
杨超越本身也要实现该接口!
现在就为杨超越创建一个代理!
代码实现:
思路逻辑如下:
1.BigStar类,用于创建杨超越的实例对象
//注意这里还未实现了任何接口
public class BigStar{
//属性:姓名
private String name;
//无有参构造
public BigStar() {
}
public BigStar(String name) {
this.name = name;
}
/**
* 唱歌
* @param
* @return
*/
public String sing(String song){
System.out.println(this.name+"正在唱"+song);
return "谢谢大家";
}
/**
* 跳舞
*
*/
public void dance(){
System.out.println(this.name+"正在跳舞");
}
//在该例子中ser、get、toString方法用不上
}
2.Show接口:里面放明星需要被代理的方法
public interface Show {
//我们可以把所有想要被代理的方法定义在接口当中
//唱歌
public abstract String sing(String song);
//
// //跳舞
public abstract void dance();
}
3.这时BigStar就要实现Show接口,如图:
4.ProxyUtil类:代理设计类
- 首先在该类中创建一个方法public static Show createProxy(BigStar y)
- 再调用java.lang.reflect.Proxy类的newProxyInstance(…)方法,并用接口show接收,
Show s=(Show) Proxy.newProxyInstance();
表示 为实现了Show接口的类(即大明星类)创建一个代理对象
接口Show定义了两个抽象方法:sing(String name)和dance()。
String sing(String song):这是一个抽象方法,表示代理对象可以“唱歌”,并接受一个表示歌曲名字的参数。
void dance():另一个抽象方法,表示代理对象可以“跳舞”。
通过Proxy.newProxyInstance()创建的代理对象将能够调用这些方法,并且在实际执行Show接口中定义的方法前或后插入自定义逻辑(如权限检查、日志记录等)。
/**
* 当前类:
* 设计代理人
*/
public class ProxyUtil {
/*
*
* ***方法的作用:
* 传一个明星的对象,创建一个代理
*
* 形参:
* 被代理的明星实例对象
*
* 返回值:
* 创建好的代理对象
*
*
*
* 需求:
* 外面的人想要大明星唱一首歌
* 1. 获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2. 再调用代理的唱歌方法
* 代理对象.唱歌的方法("只因你太美");
* */
//静态方法:方便调用
public static Show createProxy(BigStar y) {
/* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:用于指定用哪个类加载器,去加载生成的代理类(一般用本类)
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法(是接口数组)
参数三:用来指定生成的代理对象要干什么事情(匿名内部类)
*/
//目的:为实现了Show接口的类(即大明星类)创建一个代理对象 / s:代理对象
Show s = (Show) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类(一般用本类)
new Class[]{Show.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法(是接口数组)
new InvocationHandler() {//参数三:用来指定生成的代理对象要干什么事情(匿名内部类)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数一:代理的对象
* 参数二:要运行的方法 如:sing、dance
* 参数三:调用sing方法时,传递的实参
* */
if (method.getName().equals("sing")) {
System.out.println("准备话筒,收钱");
} else if (method.getName().equals("dance")) {
System.out.println("准备场地,收钱");
}
//***执行到这,说明这时代理的事做完了,现在去找杨超越唱歌或跳舞
//参数一:杨超越的实现对象,参数二:方法实参
return method.invoke(y, args);//谢谢
}
}
);
//方法结束,返回创建好的代理人
return s;
}
}
5.测试类:
public class Test {
public static void main(String[] args) {
/**
* 需求:
* 外面的人想要大明星唱一首歌
* 1. 获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2. 再调用代理的唱歌方法
* 代理对象.唱歌的方法("只因你太美");
*/
//调用ProxyUtil中的静态方法创建一个代理,发现要传递一个大明星的实例对象
Show proxy = ProxyUtil.createProxy(new BigStar("杨超越"));
//再用代理对象去调用唱歌
String results = proxy.sing("北京欢迎你");//因为sing方法有返回值,所以接收一下
System.out.println(results);
//或跳舞方法
proxy.dance();
}
}
总览图: