1.需求
(1):在程序中,对象A和对象B无法直接交互时。
(2):在程序中,功能需要增强时。
(3):在程序中,目标需要被保护时
代理模式中有一个非常重要的特点:对于客户端程序来说,使用代理对象时就像在使用目标对象一样。
2.分类
在代码形式上,代理模式分为动态代理和静态代理两种,两种原理相同,静态代理工作都是由我们完成,动态代理工
作则是自动完成,下面介绍两种动态代理。
2.1 jdk动态代理
JDK动态代理技术:只能代理接口。
以一个例子来说明
创建业务类接口
package com.hkd.service;
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 删除订单
*/
void delete();
}
业务类接口实现类(目标类)
package com.hkd.service.impl;
import com.hkd.service.OrderService;
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
try {
Thread.sleep(1024);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在生成订单.....");
}
@Override
public void delete() {
try {
Thread.sleep(1002);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在删除订单.....");
}
}
其中sleep用来模拟执行时间
需求
需要统计每个业务的执行时间
解决
我们可以使用代理的方式
客户端代码
package com.hkd.client;
import com.hkd.invocationHandler.TimerInvocationHandler;
import com.hkd.service.OrderService;
import com.hkd.service.impl.OrderServiceImpl;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 创建目标类
OrderService orderService = new OrderServiceImpl();
// 创建代理对象
OrderService proxyInstance = (OrderService) Proxy.newProxyInstance(orderService.getClass().getClassLoader(), orderService.getClass().getInterfaces(), new TimerInvocationHandler(orderService));
// 执行目标方法
proxyInstance.generate();
proxyInstance.delete();
}
}
重点创建代理对象这一行代码
1,该行做了什么?
答:第一件事:在内存中生成了代理类的字节码
第二件事:创建代理对象
2.三个参数的作用分别是什么?
答:第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到
内存当中的。所以要指定使用哪个类加载器加载。
第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实
现哪些接口。
第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。
显 然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
第三个参数对应的类
package com.hkd.invocationHandler;
import com.hkd.service.OrderService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
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();
// 调用目标对象目标方法
OrderService invoke = (OrderService) method.invoke(target, args);
// 目标执行之后执行
long end = System.currentTimeMillis();
System.out.println("一共耗时"+ (end - begin) +"毫秒");
return invoke;
}
}
invoke 方法也有三个参数下面一一解释
第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象
的话,尽管通过这个参数来使用。
第二个参数:Method method。目标方法。
第三个参数:Object[] args。目标方法调用时要传的参数。
问题:这个invoke方法什么时候执行?
当代理对象调用代理方法时,invoke就会执行。
上面client执行结果如下
2.2 CGLIB动态代理
CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
使用
1.引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2.目标类
package com.hkd.service;
public class UserService {
public void login(){
System.out.println("正在登录....");
}
public void logout(){
System.out.println("正在退出....");
}
}
3.客户端代码
package com.hkd.client;
import com.hkd.methodinterceptor.TimerMethodInterceptor;
import com.hkd.service.UserService;
import net.sf.cglib.proxy.Enhancer;
public class Client2 {
public static void main(String[] args) {
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(new TimerMethodInterceptor());
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userService = (UserService) enhancer.create();
// 执行代理方法
userService.login();
userService.logout();
}
}
和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
编写MethodInterceptor接口实现类:
package com.hkd.methodinterceptor;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 增强前
long begin = System.currentTimeMillis();
// 执行目标方法
Object invokeSuper = methodProxy.invokeSuper(target, objects);
// 增强后
long end = System.currentTimeMillis();
System.out.println("一共消耗"+ (end - begin) +"毫秒");
return invokeSuper;
}
}
MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
第一个参数:目标对象
第二个参数:目标方法
第三个参数:目标方法调用时的实参
第四个参数:代理方法
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
–add-opens java.base/java.lang=ALL-UNNAMED
–add-opens java.base/sun.net.util=ALL-UNNAMED
运行结果