一、代理模式
给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问.
按照代理对象的创建时期
,可分为静态代理和动态代理。
静态代理:静态代理在编译时
就已经实现,编译完成后代理类是一个实际的class文件
动态代理:动态代理是在运行时
动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
二、静态代理
举例:我想卖车,但我很忙,不想被电话骚扰,于是交给二手车交易所来帮我卖车
卖车子接口
interface SaleCar{
void sale();
}
张三真正卖车子的实现类(委托类
)
class ZhangSanTrade implements SaleCar{
@Override
public void sale() {
System.out.println("zhangsan sale his car");
}
}
二手车交易类(代理类
)
class CarTradeProxy implements SaleCar{
private ZhangSanTrade owner;
public CarTradeProxy(ZhangSanTrade owner){
this.owner=owner;
}
@Override
public void sale() {
System.out.println("proxy add price");
owner.sale();
}
}
测试
疑问:代理类和委托类差别不大,直接创建委托类调用sale方法不就可以了吗?
解答:代理类在真正调用委托类的方法之前做了中间加价的操作。即代理模式实现在委托类的基础上增加了额外的逻辑操作
需求增加:
我想用卖车的钱加自己的一些存款买个房子,自己也不想东奔西跑,于是把买房委托房产中介
在定义一个买房的接口
interface BuyHouse{
void buy();
}
重写委托类,实现卖车和买房两个接口
class ZhangSanTrade implements SaleCar,BuyHouse{
@Override
public void sale() {
System.out.println("zhangsan sale his car");
}
@Override
public void buy() {
System.out.println("zhangsan buy house");
}
}
可以看到,我现在既要卖车,也要买房子
在创建一个买房子的中介代理类
class HouseTradeProxy implements BuyHouse{
private ZhangSanTrade owner;
public HouseTradeProxy(ZhangSanTrade owner){
this.owner=owner;
}
@Override
public void buy() {
System.out.println("proxy add price");
owner.buy();
}
}
测试类
public static void main(String[] args) {
//委托类
ZhangSanTrade zhangSanSaleCar=new ZhangSanTrade();
//代理类
CarTradeProxy carTradeProxy=new CarTradeProxy(zhangSanSaleCar);
carTradeProxy.sale();
System.out.println("------------------------------");
HouseTradeProxy houseTradeProxy=new HouseTradeProxy(zhangSanSaleCar);
houseTradeProxy.buy();
}
优点:可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
-
冗余
。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。 -
不易维护
。一旦接口增加方法,目标对象与代理对象都要进行修改。
通过静态代理的方式,可以完美解决我们的问题,但当越来越多的委托类需要代理,而且代理做的工作又一样,会多出很多的代理类。此时想,我们可以只做一次,代理一类委托类,此时动态代理应运而生,它可以只定义一次就能为一类委托类做代理
三、动态代理
动态代理常见JDK 动态代理与 CGLIB 动态代理
区别:
- Jdk动态代理:利用拦截器(必须实现InvocationHandler接口)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
- Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来进行代理
因此:
如果想要实现JDK动态代理那么代理类必须实现接口,否则不能使用;
如果想要使用CGlib动态代理,那么代理类不能使用final修饰类和方法;
使用场景:
AOP 的实现原理、RPC远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理
1 JDK动态代理实现
UserService接口
public interface UserService {
void addUser();
void updateUser(String str);
}
UserServiceImpl实现类
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void updateUser(String str) {
System.out.println("更新用户信息" + str);
}
}
UserProxy代理类,实现InvocationHandler接口重写invoke方法
public class UserProxy implements InvocationHandler {
private Object target;
public UserProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(target, args);
System.out.println("记录日志");
return res;
}
}
测试 ,实现了增强,打印出了日志
Proxy.newProxyInstance 方法得到的也是 UserService 的实现类对象,那么其实这是一种基于接口的动态代理。也叫做 JDK 动态代理
2 CGlib动态代理
JDK 动态代理是基于接口的代理
,而 CGLIB 动态代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
,也就是说 CGLIB 动态代理采用类继承 -> 方法重写的方式进行的,下面我们先来看一下 CGLIB 动态代理的结构。
如上图所示,代理类继承于目标类,每次调用代理类的方法都会在拦截器中进行拦截,拦截器中再会调用目标类的方法。
CGlib需要导入Jar包,那么我用SpringBoot直接导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
UserServiceImpl被代理类
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void updateUser(String str) {
System.out.println("更新用户信息" + str);
}
}
UserServiceCGlib代理
public class UserServiceCGlib implements MethodInterceptor {
private Object target;
public UserServiceCGlib() {
}
public UserServiceCGlib(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.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 result = methodProxy.invokeSuper(o, objects);
System.out.println("增强结束~~~");
return result;
}
}
测试类
注:代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理
总结:
- 如果加入容器的目标对象有实现接口,用JDK代理
- 如果目标对象没有实现接口,用Cglib代理
- 如果目标对象实现了接口,且强制使用cglib代理,则会使用cglib代理。