赞
踩
场景:拍电影的时候,演员找替身演员,这就是一个代理模式
替身演员 去代理 演员 完成表演
思考:演员为什么要找替身呢?(为什么要使用代理模式呢?)
Java程序中的代理模式的作用:
代理模式中有一个非常重要的特点:对于客户端程序来说,使用代理对象时就像在使用目标对象一样。【在程序中,目标需要被保护时】
代理模式中有三大角色:
为什么演员和替身演员要有相同的行为动作呢?
是因为不想让观众知道是替身演员。这里的观众其实就是“客户端程序”,如果你使用代理模式的话,对应客户端程序来所,客户端是无法察觉到的,客户端在使用代理对象的时候就象在使用目标对象。
代理模式的类图:
代理模式在代码实现上,包括两种形式:
代理类是程序员提前写好的,成为静态代理
下面是静态动力的演示:
定义一个接口:OrderService
package com.julissa.proxy.service; public interface OrderService { /** * 生成订单 */ void generate(); /** * 查看订单详情 */ void detail(); /** * 修改订单 */ void modify(); }
定义接口的实现类:OrderServiceImpl
package com.julissa.proxy.service; public class OrderServiceImpl implements OrderService{ @Override public void generate() { //模拟生成订单的耗时 try { Thread.sleep(1234); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已生成"); } @Override public void detail() { //模拟查看订单的耗时 try { Thread.sleep(2541); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单信息如下:******"); } @Override public void modify() { //模拟修改订单的耗时 try { Thread.sleep(1010); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已修改"); } }
客户端程序:
package com.julissa.proxy.client;
import com.julissa.proxy.service.OrderService;
import com.julissa.proxy.service.OrderServiceImpl;
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
orderService.generate();
orderService.modify();
orderService.detail();
}
}
其中Thread.sleep()方法的调用是为了模拟操作耗时。
项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。如果是你,你该怎么做呢?
第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:
package com.julissa.proxy.service; public class OrderServiceImpl implements OrderService{ @Override public void generate() { //模拟生成订单的耗时 long begin = System.currentTimeMillis(); try { Thread.sleep(1234); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已生成"); long end = System.currentTimeMillis(); System.out.println("耗费时长"+(end - begin)+"毫秒"); } @Override public void detail() { //模拟查看订单的耗时 long begin = System.currentTimeMillis(); try { Thread.sleep(2541); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单信息如下:******"); long end = System.currentTimeMillis(); System.out.println("耗费时长"+(end - begin)+"毫秒"); } @Override public void modify() { //模拟修改订单的耗时 long begin = System.currentTimeMillis(); try { Thread.sleep(1010); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已修改"); long end = System.currentTimeMillis(); System.out.println("耗费时长"+(end - begin)+"毫秒"); } }
需求可以满足,但显然是违背了OCP开闭原则,而且代码没有得到复用,这种方案不可取。
第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:
package com.julissa.proxy.service; public class OrderServiceImplSub extends OrderServiceImpl{ @Override public void generate() { long begin = System.currentTimeMillis(); super.generate(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } @Override public void detail() { long begin = System.currentTimeMillis(); super.detail(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } @Override public void modify() { long begin = System.currentTimeMillis(); super.modify(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } }
这种方式可以解决,但是存在两个问题:
这种方案也不可取。
第三种方案:使用代理模式(这里采用静态代理)
可以为OrderService接口提供一个代理类。
package com.julissa.proxy.service; public class OrderServiceProxy implements OrderService{// 代理对象(代理对象和目标对象要有相同的行为或方法,所有要实现公共接口) // 将目标对象作为代理对象的属性,这种关系叫做关联关系,比继承关系的耦合度低 private OrderService orderService; // 通过构造方法将目标对象传递给代理对象 public OrderServiceProxy(OrderService orderService) { this.orderService = orderService; } @Override public void generate() {// 代理方法 //增强 long begin = System.currentTimeMillis(); // 执行目标对象的目标方法 orderService.generate(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } @Override public void detail() {// 代理方法 //增强 long begin = System.currentTimeMillis(); // 执行目标对象的目标方法 orderService.detail(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } @Override public void modify() {// 代理方法 //增强 long begin = System.currentTimeMillis(); // 执行目标对象的目标方法 orderService.modify(); long end = System.currentTimeMillis(); System.out.println("耗时"+(end - begin)+"毫秒"); } }
这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。
编写客户端程序:
package com.julissa.proxy.client; import com.julissa.proxy.service.OrderService; import com.julissa.proxy.service.OrderServiceImpl; import com.julissa.proxy.service.OrderServiceProxy; public class Client { public static void main(String[] args) { // 创建目标对象 OrderService target = new OrderServiceImpl(); // 创建代理对象 OrderService proxy = new OrderServiceProxy(target); // 调用代理对象的代理方法 proxy.generate(); proxy.modify(); proxy.detail(); } }
运行结果:
以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。
大家思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
还是使用静态代理中的例子:一个接口和一个实现类。
OrderService
package com.julissa.proxy.service; public interface OrderService { /** * 生成订单 */ void generate(); /** * 查看订单详情 */ void detail(); /** * 修改订单 */ void modify(); }
OrderServiceImpl
package com.julissa.proxy.service; public class OrderServiceImpl implements OrderService{ @Override public void generate() { //模拟生成订单的耗时 try { Thread.sleep(1234); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已生成"); } @Override public void detail() { //模拟查看订单的耗时 try { Thread.sleep(2541); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单信息如下:******"); } @Override public void modify() { //模拟修改订单的耗时 try { Thread.sleep(1010); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已修改"); } }
我们在静态代理的时候,除了以上一个接口和一个实现类之外,还需要写一个代理类UserServiceProxy!
在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:
package com.julissa.proxy.client; import com.julissa.proxy.service.OrderService; import com.julissa.proxy.service.OrderServiceImpl; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { //创建目标对象 OrderService target = new OrderServiceImpl(); //创建代理对象 Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),调用处理器对象) //调用代理对象的代理方法 } }
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
这行代码做了两件事:
Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
其中newProxyInstance()方法有三个参数:
所以接下来我们要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:
package com.julissa.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:
我们将来肯定是要调用“目标方法”的,但要调用目标方法的话,需要“目标对象”的存在,“目标对象”从哪儿来呢?我们可以给TimerInvocationHandler提供一个构造方法,可以通过这个构造方法传过来“目标对象”,代码如下:
package com.julissa.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 { return null; } }
有了目标对象我们就可以在invoke()方法中调用目标方法了。代码如下:
package com.julissa.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; } }
到此为止,调用处理器就完成了。接下来,应该继续完善Client程序:
package com.julissa.proxy.client; import com.julissa.proxy.service.OrderService; import com.julissa.proxy.service.OrderServiceImpl; import com.julissa.proxy.service.TimerInvocationHandler; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { //创建目标对象 OrderService target = new OrderServiceImpl(); //创建代理对象 //实现了OrderService接口,所以可以向下转型 OrderService proxyObj =(OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target)); //调用代理对象的代理方法 proxyObj.generate(); proxyObj.detail(); proxyObj.modify(); } }
运行客户端程序
InvocationHandler接口中的invoke()方法什么时候被调用?
注意:当你调用代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法会被调用,注册在InvocationHandler接口中的invoke()方法都会被调用。
可以看到,不管你有多少个Service接口,多少个业务类,这个TimerInvocationHandler接口是不是只需要写一次就行了,代码是不是得到复用了
而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。
到这里,JDK动态代理的原理就结束了。
不过我们看以下这个代码确实有点繁琐,对于客户端来说,用起来不方便:
我们可以提供一个工具类:ProxyUtil,封装一个方法:
package com.julissa.proxy.utils; import com.julissa.proxy.service.OrderService; import com.julissa.proxy.service.TimerInvocationHandler; import java.lang.reflect.Proxy; public class ProxyUtil { /** * 获取代理对象 * @param target 目标对象 * @return 代理对象 */ public static OrderService getProxyObj(Object target){ //底层调用JDK动态代理 OrderService proxyObj =(OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target)); return proxyObj; } }
这样客户端代码就不需要写那么繁琐了:
package com.julissa.proxy.client; import com.julissa.proxy.service.OrderService; import com.julissa.proxy.service.OrderServiceImpl; import com.julissa.proxy.service.TimerInvocationHandler; import com.julissa.proxy.utils.ProxyUtil; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { //创建目标对象 OrderService target = new OrderServiceImpl(); //创建代理对象 //实现了OrderService接口,所以可以向下转型 OrderService proxyObj = ProxyUtil.getProxyObj(target); //调用代理对象的代理方法 proxyObj.generate(); proxyObj.detail(); proxyObj.modify(); } }
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
使用CGLIB,需要引入它的依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
我们准备一个没有实现接口的类,如下:
package com.julissa.proxy.service; /** * 目标类 */ public class UserService { /** * 用户登陆 * @param username 用户名 * @param password 密码 * @return 登陆结果 */ public Boolean login(String username, String password){ System.out.println("系统正砸验证信息"); if ("admin".equals(username) && "123456".equals(password)) { return true; } return false; } /** * 用户注销 */ public void logout(){ System.out.println("用户退出"); } }
使用CGLIB在内存中为UserService类生成代理类,并创建对象
package com.julissa.proxy.client; import com.julissa.proxy.service.UserService; import net.sf.cglib.proxy.Enhancer; /** * 客户端程序 */ public class Client2 { public static void main(String[] args) { // 创建字节码增强器 // 这个对象是CGLib库中的核心对象,就是依靠它生成代理类 Enhancer enhancer = new Enhancer(); // 告诉cglib要继承哪个类 enhancer.setSuperclass(UserService.class); // 设置回调函数(等同于JDK动态代理的调用处理器) enhancer.setCallback(new UserServiceInterceptor()); // 创建代理对象 // 这一步会做两件事: // 1.在内存中生成UserService的子类,其实就是代理类的字节码 // 2. 创建代理对象 UserService userService = (UserService) enhancer.create(); // 调用代理对象的方法 Boolean aBoolean = userService.login("admin", "123456"); System.out.println(aBoolean?"登陆成功":"登陆失败"); userService.logout(); } }
编写MethodInterceptor接口实现类:
package com.julissa.proxy.service; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class UserServiceInterceptor 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个参数:
第一个参数:目标对象
第二个参数:目标方法
第三个参数:目标方法调用时的实参
第四个参数:代理方法
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7WCayQAn-1679894878459)(null)]
运行客户端程序
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。