代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
ava当中有三种方式来创建代理对象:
- 静态代理
- 基于jdk(接口)的动态代理
- 基于CGLLIB(父类)的动态代理。
静态代理
这里我们用一个订单的操作来进行演示:
首先创建一个订单的service接口,同时创建实现类
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 修改订单信息
*/
void modify();
/**
* 查看订单详情
*/
void detail();
}
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
System.out.println("订单以生成");
}
@Override
public void modify() {
System.out.println("订单已修改");
}
@Override
public void detail() {
System.out.println("查看订单详情");
}
}
此时,我们想增加每一次订单方法的执行需要的时间功能,如果直接在 OrderServiceImpl 中写,就会对代码有侵入,此时我们用静态代理来做。
创建一个代理类,并且实现 OrderService 接口:
public class OrderServiceProxy implements OrderService {
private OrderService orderService;
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generate() {
long l = System.currentTimeMillis();
orderService.generate();
long l1 = System.currentTimeMillis();
System.out.println("生成订单消耗了"+ (l1-l) + "毫秒");
}
@Override
public void modify() {
long l = System.currentTimeMillis();
orderService.modify();
long l1 = System.currentTimeMillis();
System.out.println("修改订单消耗了"+ (l1-l) + "毫秒");
}
@Override
public void detail() {
long l = System.currentTimeMillis();
orderService.detail();
long l1 = System.currentTimeMillis();
System.out.println("查看订单消耗了"+ (l1-l) + "毫秒");
}
}
这样做不会应该到 OrderServiceImpl 中的代码,最后调用的时候要使用代理对象去调用:
public class OrderClient {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
OrderServiceProxy proxy = new OrderServiceProxy(orderService);
proxy.generate();
proxy.modify();
proxy.detail();
}
}
如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。
动态代理
jdk 动态代理
还是一样创建一个订单的service接口,同时创建实现类
public interface OrderService {
String getName();
/**
* 生成订单
*/
void generate();
/**
* 修改订单信息
*/
void modify();
/**
* 查看订单详情
*/
void detail();
}
public class OrderServiceImpl implements OrderService {
@Override
public String getName() {
return "张三";
}
@Override
public void generate() {
System.out.println("订单以生成");
}
@Override
public void modify() {
System.out.println("订单已修改");
}
@Override
public void detail() {
System.out.println("查看订单详情");
}
}
上面我们还要创建一个代理类,此时就不需要了,在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:
public class OrderClient {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
// 使用jdk动态代理
OrderService orderService = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimeInvocationHandler(target));
String name = orderService.getName();
System.out.println(name);
}
}
Proxy.newProxyInstance() 这行代码做了两件事:
- 在内存中生成了代理类的字节码
- 创建代理对象
Proxy.newProxyInstance() 有三个参数:
- 第一个参数是类加载器,在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
- 第二个参数是接口类型,代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
- 第三个参数是处理器
InvocationHandler,就是在此接口的实现类中完成对代码的增强。
所以接下来我们要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:
public class TimeInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
// 通过构造方法来传目标对象
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
// 调用目标方法的方法
Object invoke = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("消耗了"+(end-begin)+"毫秒");
return invoke;
}
}
InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:
- 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
- 第二个参数:Method method。目标方法。
- 第三个参数:Object[] args。目标方法调用时要传的参数。
如果有返回值需要将 invoke 返回。
我们可以看到,不管你有多少个Service接口,多少个业务类,这个TimerInvocationHandler接口是不是只需要写一次就行了。
而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。到这里,JDK动态代理的原理就结束了。
注意:
需要注意的是,jdk动态代理只能代理接口,目标类一定要实现一个接口
CGLIB 动态代理
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
使用CGLIB,需要引入它的依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
我们准备一个没有实现接口的类,如下:
public class UserService {
public void login(){
System.out.println("用户正在登录系统....");
}
public void logout(){
System.out.println("用户正在退出系统....");
}
}
使用CGLIB在内存中为UserService类生成代理类,并创建对象:
public class Client {
public static void main(String[] args) {
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(new TimerMethodInterceptor());
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login();
userServiceProxy.logout();
}
}
和JDK动态代理原理差不多,在CGLIB中需要提供的不是 InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 前增强
long begin = System.currentTimeMillis();
// 调用目标
Object retValue = methodProxy.invokeSuper(target, objects);
// 后增强
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
// 一定要返回
return retValue;
}
}
MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
- 第一个参数:目标对象
- 第二个参数:目标方法
- 第三个参数:目标方法调用时的实参
- 第四个参数:代理方法