1.代理模式
代理模式主要有两种:
静态代理模式
动态代理模式
2. 静态代理
有这样一个业务:订单的生成,修改,查看详情。实现如下
package com.sdnu.proxy.service;
/**
* 订单业务接口
*/
public interface OrderService {
void generate();
void modify();
void detail();
}
package com.sdnu.proxy.service;
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
//模拟生成订单的耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生产");
}
@Override
public void modify() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
@Override
public void detail() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("查看订单详情");
}
}
测试
package com.sdnu.proxy.client;
import com.sdnu.proxy.service.OrderService;
import com.sdnu.proxy.service.OrderServiceImpl;
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
orderService.generate();
orderService.modify();
orderService.detail();
}
}
过了一段时间,有一个新的业务,我们需要统计订单的生成总时间,于是我们有如下的解决方案:
方案一:采用硬编码的方式,在每一个业务接口中的每一个业务方法直接写一个统计时间。
这种方案的缺点:(1)违背OCP原则
(2)代码没有得到复用
方案二:编写业务的子类,让子类继承原来的业务类,对每个业务方法进行重写。
这种方案的缺点:(1)虽然没有违背OCP原则,但是由于采用继承方式会导致代码的耦合度变高。
(2)代码没有得到复用
方案三:静态代理
代理对象
package com.sdnu.proxy.service;
//代理对象
public class OrderServiceProxy implements OrderService{
//将目标对象作为代理对象的一个属性,这种关系叫做关联关系,比继承关系耦合度低
private OrderService target;
public OrderServiceProxy(OrderService target) {
this.target = target;
}
@Override
public void generate() {//代理方法
long begin = System.currentTimeMillis();
target.generate();
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
}
@Override
public void modify() {//代理方法
long begin = System.currentTimeMillis();
target.modify();
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
}
@Override
public void detail() {//代理方法
long begin = System.currentTimeMillis();
target.detail();
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
}
}
测试
package com.sdnu.proxy.client;
import com.sdnu.proxy.service.OrderService;
import com.sdnu.proxy.service.OrderServiceImpl;
import com.sdnu.proxy.service.OrderServiceProxy;
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
OrderServiceProxy orderServiceProxy = new OrderServiceProxy(orderService);
orderServiceProxy.generate();
orderServiceProxy.modify();
orderServiceProxy.detail();
}
}
这种静态代理符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。
缺点:类爆炸问题。
3. 动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
常见的动态代理:
- JDK动态代理
- CGLIB动态代理
- javassit动态代理
3.1 JDK动态代理
测试程序Test
package com.sdnu.proxy.client;
import com.sdnu.proxy.service.OrderService;
import com.sdnu.proxy.service.OrderServiceImpl;
import com.sdnu.proxy.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//创建目标对象
OrderService target = new OrderServiceImpl();
//创建代理对象
OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
//调用代理对象的代理方法
orderServiceProxy.generate();
orderServiceProxy.modify();
orderServiceProxy.detail();
}
}
代码OrderService orderServiceProxy = Proxy.newProxyInstance(类加载器, 接口类型, 调用处理器);
做了两件事
● 第一件事:在内存中生成了代理类的字节码
● 第二件事:创建代理对象
参数:
类加载器:在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
接口类型:代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
调用的处理器:这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
代理类
package com.sdnu.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
private Object target;
public TimerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目标执行之前的增强
long begin = System.currentTimeMillis();
//反射机制调用目标对象上的目标方法
Object retValue = method.invoke(target, args);
//目标执行之后的增强
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
return retValue;
}
}
invoke方法有三个参数
- Object proxy。代理对象。计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用
- Method method。目标方法。
- Object[] args。目标方法调用时要传的参数。
3.2 CGLIB动态代理
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>