Java动态代理
什么是动态代理
动态代理就是在 程序运行期 ,创建目标对象的 代理对象 ,对 目标对象 中的 方法 进行 功能性增强 的一种技术
核心概念:
动态代理能够让真实对象专注于自己的核心功能,让代理对象拦截客户对真实对象的访问,另外可以在不修改方法源码的情况下,增强被代理对象的方法的功能。
我们在做项目的时候把日志和异常统一处理就是利用了动态代理,让我们能够关注核心业务。
举个例子,小明(真实角色)的主要业务是唱歌,在还没火的时候自己跑东跑西去街头、酒吧等地方唱歌,在这期间需要自己负责找合适的场地,以及和酒吧老板谈工资等业务,突然有一天他火了,很多人要请他唱歌,所有的事亲历亲为他根本忙不过来,这个时候经纪人(代理角色)出现了,客户们要请小明唱歌,不能直接找到小明了,而是需要和经纪人谈,经纪人在商业等方面的理解比小明强(对代理对象的方法增强)。这样小明就可以专注于自己的唱歌业务了,经纪人的存在就是拦截了客户对小明(真实对象)的访问。
动态代理的常用方式
- JDK动态代理:JDK自带的动态代理功能(基于接口的动态代理),它的实现前提是现有类必须拥有一个接口,因为它是通过 对现有类接口的实现 来完成的。
- CGLIB代理:一个开源工具包(基于类的动态代理),它的实现是通过 继承现有类 ,然后 重写现有类的方法 实现的。
JDK动态代理
在java的 java.lang.reflect
包下提供了一个 Proxy
类和一个 InvocationHandler
接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
代码实现
我们以上述小明故事的场景,代理对象代理了小明的谈演出费的方法,看看具体怎么实现
- 首先定义一个接口,这个接口是一个公共接口,这个接口有一个talk()方法
public interface Person {
//谈薪
public void talk();
}
- 创建真实对象,实现上述接口,那么代理对象需要代理的就是这个接口
public class XiaoMing implements Person{
@Override
public void talk(){
System.out.println("小明成功收取唱歌费");
}
}
- 创建BrokerInvocationHandler类,这个类实现了
InvocationHandler
接口,我们通过这个类来动态生成代理类,InvocationHandler中有一个invoke
方法,所有执行代理对象的方法都会被替换成执行invoke方法。然后通过反射在invoke方法中执行代理类的方法。在代理过程中,在执行代理类的方法前或者后可以执行自己的操作,这就是spring aop的主要原理。
//用这个类自动生成代理类(这个并不是代理类,我们通过这个生成代理类)
public class BrokerInvocationHandler implements InvocationHandler {
//被代理的接口
private Person person;
public void setPerson(Person person) {
this.person = person;
}
//生成得到代理类 固定的代码
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),person.getClass().getInterfaces(),
this);
}
/**
*处理代理实例并返回结果
* @param proxy:代表动态代理对象
* @param method:代表正在执行的方法
* @param args:代表调用目标方法时传入的实参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理过程中插入其他操作 对方法的增强
System.out.println("客户和经纪人交流");
//动态代理本质就是使用反射机制!!如下
Object result = method.invoke(person,args);
return result;
}
}
- 创建客户对象,通过BrokerInvocationHandler生成代理对象,客户通过访问代理对象实现谈薪
public class TestMain{
public static void main(String[] args) {
//真实角色
XiaoMing xiaoming = new XiaoMing();
//代理角色:现在没有 先得到BrokerInvocationHandler对象
BrokerInvocationHandler bid = new BrokerInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象(知道我们要调用的是实现这个接口的哪个对象)
bid.setPerson(xiaoming);
//得到代理对象
Person proxy = (Person) bid.getProxy();//这里的proxy是动态生成的,我们并未写!!
proxy.talk();
}
}
- 输出结果如下
客户和经纪人交流
小明成功收取唱歌费
小结
可以看到,代理对象成功代理了小明的talk()方法;
JDK动态代理首先需要创建一个interface然后一个class实现这个interface,然后对这个class进行代理,这个class必须实现至少一个接口,否则不能进行代理。
JDK动态代理实现步骤总结
- 实现
InvocationHandler
接口,将我们具体地增强逻辑代码写在invoke()
方法中 - 使用
Proxy
类的newProxyInstance()
方法去创建一个代理类的实例对象. - 使用这个代理类对象
CGLIB动态代理
JDK动态代理需要被代理类实现接口,如果被代理类没有实现接口,就只能使用CGLib了。
这种代理方式就叫做CGlib代理。
CGlib代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。
因为使用的是继承的方式,所以不能代理final 类。
CGLIB是一个功能强大,高性能的
代码生成包
(CGLIB就是用来生成代码的,它也是在内存中动态的去生成的)
代码实现
同样以上述小明故事的场景,代理对象代理了小明的谈演出费的方法,看看具体怎么实现
- CGLIB是第三方提供的包,所以我们需要在maven工程中引入对应jar包
<!--CGLIB-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2.新建被代理类,此时并不需要再新建接口,因为我们现在是要使用CGLIB进行代理
public class XiaoMing {
public void talk(){
System.out.println("小明成功收取唱歌费");
}
}
- 继续创建BrokerInvocationHandler类,这个类实现了
MethodInterceptor
接口,我们通过这个类来动态生成代理类,此时,获得的代理类就是目标对象所属类的子类。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class BrokerInvocationHandler implements MethodInterceptor {
//被代理类
private XiaoMing xiaoMing;
public void setXiaoMing(XiaoMing xiaoMing) {
this.xiaoMing = xiaoMing;
}
//生成得到代理类 固定的代码
public Object getProxy(){
// 1. 创建Enhancer类对象,它类似于JDK动态代理中的Proxy类,该类就是用来获取代理对象的
Enhancer enhancer = new Enhancer();
// 2. 设置父类的字节码对象。为啥子要这样做呢?因为使用CGLIB生成的代理类是属于目标类的子类的,也就是说代理类是要继承自目标类的
enhancer.setSuperclass(xiaoMing.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4. 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//代理过程中插入其他操作 对方法的增强
System.out.println("客户和经纪人交流");
Object invoke = method.invoke(xiaoMing, objects);
return invoke;
}
}
```
4. 创建测试类测试
```java
public class TestMain {
public static void main(String[] args) {
//真实角色
XiaoMing xiaoMing = new XiaoMing();
//代理角色:现在没有 先得到BrokerInvocationHandler对象
BrokerInvocationHandler brokerInvocationHandler =new BrokerInvocationHandler();
brokerInvocationHandler.setXiaoMing(xiaoMing);
//得到代理对象
XiaoMing proxy = (XiaoMing) brokerInvocationHandler.getProxy();
proxy.talk();
}
}
- 输出结果如下
客户和经纪人交流
小明成功收取唱歌费
小结
可以看到,代理对象同样成功代理了小明的talk()方法;
CGLIB动态代理不需要创建一个interface,代理的class不需要实现接口。
CGLIB动态代理实现步骤总结
- 实现
MethodInterceptor
接口,将我们具体地增强逻辑代码写在intercept()
方法中 - 使用
Enhancer.create()
方法去创建一个代理类的实例对象. - 使用这个代理类对象
实现原理、优缺点、两者区别
JDK | CGLIB | |
---|---|---|
实现原理 | JDK动态代理是由Java内部的反射机制来实现的,它是JDK原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。 | CGLIB动态代理是通过继承来实现的,底层则是借助asm(Java 字节码操控框架)来实现的(采用字节码的方式,给A类创建一个子类B,子类B使用方法拦截的技术拦截所有父类的方法调用)。 |
优缺点 | JDK动态代理有一定的局限性,只能基于接口。 | CGLIB这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。但无法处理final的情况,因为它通过继承实现。 |
两者区别 | JDK动态代理只能对实现了接口的类生成代理,而不能针对类。 | CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,但不能代理final修饰的类。 |