背景和概念
代理模式指为对象提供一种通过代理的方式来访问并控制该对象行为的方法。在客户端不适合或者不能够直接引用一个对象时,可以通过该对象的代理对象来实现对该对象的访问,可以将该代理对象理解为客户端和目标对象之间的中介者。起到对目标对象增强的目的,有时候也是为了保护目标对象。
在代理模式下有两种角色,一种是被代理者,一种是代理(Proxy),在被代理者需要做一项工作时,不用自己做,而是交给代理做。代理模式分类静态代理和动态代理,静态代理需要程序员自己实现,动态代理目前有基于JDK和CgLib实现两种方式。
静态代理
这里场景是我们想在用户工作的前后记录一些日志操作,首先声明抽象接口(通常叫主题subject)如下:
/**
* 定义用户相关的接口
*
* @Author: ganbo
* @Date: 2020/6/9 18:04
*/
public interface IUserService {
void work(String name);
}
具体实现类(也叫目标对象)
/**
* 目标对象
*
* @Author: ganbo
* @Date: 2020/6/9 18:05
*/
public class UserServiceImpl implements IUserService {
public void work(String name) {
System.out.println(name + "开始工作了,大家都别吵。");
}
}
接下来声明代理对象:
/**
* 静态代理对象
*
* @Author: ganbo
* @Date: 2020/6/9 18:07
*/
public class UserServiceProxy implements IUserService {
private IUserService target;
public UserServiceProxy(IUserService target) {
this.target = target;
}
public void work(String name) {
before();
target.work(name);
after();
}
private void before() {
System.out.println("早上开车到公司.");
}
private void after() {
System.out.println("工作完毕,下班。");
}
}
测试代码:
public class StaticProxyTestDemo {
public static void main(String[] args) {
UserServiceImpl target = new UserServiceImpl(); //目标对象
UserServiceProxy proxy = new UserServiceProxy(target);
proxy.work("小王");
}
}
运行结果如下:
这里代理类UserServiceProxy内部持有一个被代理对象target,target在创建代理类的时候通过构造方法传入。重写代理类里面的目标方法,在实现代理类里面目标方法work()的里面调用目标对象的代理方法,在调用前后实现要增强的逻辑。
静态代理总结:
- 可以做到在不修改目标对象功能的情况下对目标对象进行功能增强。
- 因为代理对象需要实现与目标对象相同的接口,所以会有很多代理类,类膨胀;同时一旦接口新增和删除方法目标对象和代理对象都需要维护。如果实现的接口里面的方法很多就需要在代理类中每个主题接口实现方法中都要进行硬编码的增强,任务量大,重复代码多,不便于维护。静态代理我们每天都在使用,比如三层架构里面的controller里面注入service,service里面注入dao/mapper等。。。
- 这里要求静态代理的代理类也要实现IUserService接口,就有了硬编码,不够灵活,解决方案就是使用静态代理。
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(Proxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。具体实现有JDK动态代理和Cglib动态代理两种实现方式。
JDK动态代理
代理步骤:
(1)定义一个事件管理器类实现invocationHandle接口,并重写invoke(代理类,被代理的方法,方法的参数列表)方法。
(2)实现被代理类及其实现的接口,
(3)调用Proxy.newProxyInstance(类加载器,类实现的接口,事务处理器对象);生成一个代理实例。
(4)通过该代理实例调用方法。
//1.抽象主题
interface Moveable {
void move();
}
//2.真实主题
class Car implements Moveable {
public void move() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("骑车行驶中...");
} catch (InterruptedException e) {
}
}
}
//3.事务处理器
class TimeHandler implements InvocationHandler {
private Object target;
public TimeHandler(Object target) {
this.target = target;
}
public Object getInstance(ClassLoader loader, Class<?>[] interfaces) {
return Proxy.newProxyInstance(loader, interfaces, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶…");
Object invoke = method.invoke(target, args);
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶…汽车行驶时间:" + (stopTime - startTime) + "毫秒!");
return invoke;
}
}
//4.测试
public class Test {
public static void main(String[] args) {
Car car = new Car();
TimeHandler timeHandler = new TimeHandler(car);
Moveable carProxy = (Moveable) timeHandler.getInstance(Car.class.getClassLoader(), car.getClass().getInterfaces());
carProxy.move();
}
}
在测试代码中,Proxy.newProxyInstance()方法需要3个参数:类加载器(要进行代理的类)、被代理类实现的接口,事务处理器。所以先实例化Car,实例化InvocationHandler的子类TimeHandler,将各参数传入Proxy的静态方法newProxyInstance()即可获得Car的代理类,前面的静态代理,代理类是我们编写好的,而动态代理则不需要我们去编写代理类,是在程序中动态生成的。Jdk底层是采用反射生成代理类。静态代理类里面这里我的目标对象target是Object类型,说明可以代理所有类的对象,只要该类实现了接口。一个静态代理类只能代理“一种接口”类型的类。同时动态代理接口里面新增一个方法以后,代理工具类不需要修改代码,静态代理的话需要去处理代理类里面的新增的实现方法,这里可以看出动态代理更加灵活和强大。
Cglib动态代理
由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。
代理步骤:
- 引入cglib依赖包
- 定义一个事件管理器类实现MethodInterceptor接口,并重写intercept
- 获取根据目标类对象的Class信息获取代理对象
- 通过该代理实例调用方法
引入cglib依赖:
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
</dependency>
<!-- cglib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
</dependency>
</dependencies>
编写代码如下:
/**
* 1:定义目标对象
*/
class Car {
public String drive(String name) {
System.out.println(name + "正在开车....");
return "success";
}
}
/**
* 2:编写代理工具类
*/
public class CglibDynimicProxy implements MethodInterceptor {
private Object target;
/**
* 构造方法传入目标对象
*/
public CglibDynimicProxy(Object target) {
this.target = target;
}
/**
* 获取代理对象(这里基本上是固定写法)
*/
public Object getInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("准备开车");
Object invoke = methodProxy.invokeSuper(o, params); //调用父类方法
System.out.println("结束开车,营养跟不上了。开车时长:" + (System.currentTimeMillis() - start) + "毫秒。");
return invoke;
}
//测试
public static void main(String[] args) {
Car car = new Car(); //创建目标对象
CglibDynimicProxy proxy = new CglibDynimicProxy(car); //获取代理对象
Car carProxy = (Car) proxy.getInstance();
carProxy.drive("隔壁老王");
}
}
测试结果:
CGLIB的动态代理原理
CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。CGLIB缺点:对于final方法,无法进行代理。