原墨网站建设,怎么添加网站关键词,青岛福瀛建设集团网站,seo分析工具1 对代理模式的理解 【在程序中#xff0c;对象A和对象B无法直接交互时。】 【在程序中#xff0c;功能需要增强时。】 【在程序中#xff0c;目标需要被保护时】
业务场景#xff1a;系统中有A、B、C三个模块#xff0c;使用这些模块的前提是需要用户登录#xff0c;也…1 对代理模式的理解 【在程序中对象A和对象B无法直接交互时。】 【在程序中功能需要增强时。】 【在程序中目标需要被保护时】
业务场景系统中有A、B、C三个模块使用这些模块的前提是需要用户登录也就是说在A模块中要编写判断登录的代码B模块中也要编写C模块中还要编写这些判断登录的代码反复出现显然代码没有得到复用可以为A、B、C三个模块提供一个代理在代理当中写一次登录判断即可。代理的逻辑是请求来了之后判断用户是否登录了如果已经登录了则执行对应的目标如果没有登录则跳转到登录页面。【在程序中目标不但受到保护并且代码也得到了复用。】
代理模式是GoF23种设计模式之一。属于结构型设计模式。
代理模式的作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下一个客户不想或者不能直接引用一个对象此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身这种实现机制即为代理模式通过引入代理对象来间接访问一个对象这就是代理模式的模式动机。 代理模式中的角色
代理类代理主题目标类真实主题代理类和目标类的公共接口抽象主题客户端在使用代理类时就像在使用目标类不被客户端所察觉所以代理类和目标类要有共同的行为也就是实现共同的接口。
代理模式的类图 代理模式在代码实现上包括两种形式
静态代理动态代理
2 静态代理
有一个接口和实现类
package com.powernode.proxy.service;/*** 订单业务接口* 代理对象和目标对象的公共接口*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单信息*/void modify();/*** 查看订单详情*/void detail();
}package com.powernode.proxy.service.impl;import com.powernode.proxy.service.OrderService;/*** 目标对象*/
public class OrderServiceImpl implements OrderService {Override// 目标方法public void generate() {// 模拟生成订单的耗时try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(订单已生成);}Overridepublic void modify() {// 模拟修改订单的耗时try {Thread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(订单已修改);}Overridepublic void detail() {// 模拟查看订单的耗时try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(订单详情);}
}项目经理提出一个新的需求要统计所有业务接口中每一个业务方法的耗时。 解决方案一硬编码在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序。 这种方案的缺点 缺点一违背OCP开闭原则。缺点二代码没有得到复用。相同的代码写了很多遍。 解决方案二编写业务类的子类让子类继承业务类对每个业务方法进行重写。 缺点一虽然解决了OCP开闭原则。但是这种方式会导致耦合度很高因为采用了继承关系。继承关系是一种耦合度非常高的关系不建议使用。缺点二代码没有得到复用。相同的代码写了很多遍。 解决方案三代理模式。静态代理 为OrderService接口提供一个代理类。 package com.powernode.proxy.service.impl.static_proxy;import com.powernode.proxy.service.OrderService;/*** 代理对象代理对象 和目标对象要具有相同的行为就要实现公共接口* 客户端在使用代理对象的时候就像在使用目标对象一样*/
public class OrderServiceProxy implements OrderService {// 将目标对象作为代理对象的一个属性.关联关系比继承关系耦合度低// 这里写一个公共接口。因为公共接口耦合度低// 目标对象 目标对象一定实现了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)毫秒);}Overridepublic void modify() {// 增强long begin System.currentTimeMillis();// 调用目标对象的目标方法target.modify();long end System.currentTimeMillis();System.out.println(耗时(end - begin)毫秒);}Overridepublic void detail() {// 增强long begin System.currentTimeMillis();// 调用目标对象的目标方法target.detail();long end System.currentTimeMillis();System.out.println(耗时(end - begin)毫秒);}
}编写客户端程序 package com.powernode.proxy.client;import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.impl.OrderServiceImpl;
import com.powernode.proxy.service.impl.static_proxy.OrderServiceProxy;public class Test {public static void main(String[] args) {// 创建目标对象OrderService target new OrderServiceImpl();// 创建代理对象OrderService proxy new OrderServiceProxy(target);// 调用代理对象的代理方法proxy.generate();proxy.modify();proxy.detail();}
}以上就是代理模式中的静态代理其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。 优点1解决了OCP问题。 优点2采用代理模式的has a可以降低耦合度。
目前我们使用的是静态代理这个静态代理的缺点是什么 类爆炸。假设系统中有1000个接口那么每个接口都需要对应代理类这样类会急剧膨胀。不好维护。
怎么解决类爆炸问题 可以使用动态代理来解决这个问题。
动态代理还是代理模式只不过添加了字节码生成技术可以在内存中为我们动态的生成一个class字节码这个字节码就是代理类。 在内存中动态的生成字节码代理类的技术叫做动态代理。
3 动态代理
在程序运行阶段在内存中动态生成代理类被称为动态代理目的是为了减少代理类的数量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括
JDK动态代理技术只能代理接口。CGLIB动态代理技术CGLIB(Code Generation Library)是一个开源项目。是一个强大的高性能高质量的Code生成类库它可以在运行期扩展Java类与实现Java接口。它既可以代理接口又可以代理类底层是通过继承的方式实现的。性能比JDK动态代理要好。底层有一个小而快的字节码处理框架ASM。Javassist动态代理技术Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba 千叶 滋所创建的。它已加入了开放源代码JBoss 应用服务器项目通过使用Javassist对字节码操作为JBoss实现动态AOP框架。
3.1 JDK动态代理
在静态代理的时候除了接口和实现类之外还要写一个代理类OrderServiceProxy 在动态代理中OrderServiceProxy 代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可
package com.powernode.proxy.client;import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.impl.OrderServiceImpl;
import com.powernode.proxy.service.impl.TimerInvocationHandler;import java.lang.reflect.Proxy;public class DynamicProxyJDKTest {public static void main(String[] args) {// 创建目标对象OrderService target new OrderServiceImpl();// 创建代理对象// 参数类加载器代理类要实现的接口调用处理器OrderService proxyObj (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));// 调用代理对象的代理方法proxyObj.generate();proxyObj.modify();proxyObj.detail();}
}Proxy类全名java.lang.reflect.Proxy。这是JDK提供的一个类所以称为JDK动态代理。主要是通过这个类在内存中生成代理类的字节码。 static Object newProxyInstance(ClassLoader loader, Class?[] interfaces, InvocationHandler h) newProxyInstance : 新建代理对象 也就是说通过调用这个方法可以创建代理对象。 本质上这个Proxy.newProxyInstance()方法的执行做了两件事 第一件事在内存中动态的生成了一个代理类的字节码class。 第二件事new对象了。通过内存中生成的代理类这个代码实例化了代理对象。关于newProxyInstance()方法的三个重要的参数每一个什么含义有什么用 第一个参数ClassLoader loader 类加载器。这个类加载器有什么用呢 在内存当中生成的字节码也是class文件要执行也得先加载到内存当中。加载类就需要类加载器。所以这里需要指定类加载器。 并且JDK要求目标类的类加载器必须和代理类的类加载器使用同一个。 第二个参数Class?[] interfaces 接口类型 代理类和目标类要实现同一个接口或同一些接口。 在内存中生成代理类的时候这个代理类是需要你告诉它实现哪些接口的。 第三个参数InvocationHandler h InvocationHandler 被翻译为调用处理器。是一个接口。 在调用处理器接口中编写的就是增强代码。 既然是接口就要写接口的实现类。 这是一个JDK动态代理规定的接口接口全名java.lang.reflect.InvocationHandler。显然这是一个回调接口也就是说调用这个接口中方法的程序已经写好了就差这个接口的实现类了。
java.lang.reflect.InvocationHandler接口的实现类并且实现接口中的方法代码如下
package com.powernode.proxy.service.impl;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;}Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 这个接口的目的就是写增强代码// 目标执行之前增强。long begin System.currentTimeMillis();// 调用目标对象上的目标方法// 方法四要素哪个对象哪个方法传什么参数返回什么类型// invoke方法执行过程中使用method来调用目标对象的目标方法。Object retValue method.invoke(target, args);// 目标执行之后增强。long end System.currentTimeMillis();System.out.println(耗时 (end - begin) 毫秒);// 注意这个invoke方法的返回值如果代理对象调用代理方法之后需要返回结果的话invoke方法必须将目标对象的目标方法执行结果继续返回。return retValue;}
}invoke方法不是我们负责调用的JDK负责调用 invoke方法什么时候调用 当代理对象调用代理方法的时候注册在InvocationHandler调用处理器当中的invoke()方法被调用 invoke方法的三个参数 invoke方法是JDK负责调用的所以JDK调用这个方法的时候会自动给我们传过来这三个参数。 我们可以在invoke方法的大括号中直接使用。 第一个参数Object proxy 代理对象的引用。设计这个参数只是为了后期的方便如果想在invoke方法中使用代理对象的话尽管通过这个参数来使用。 第二个参数Method method 目标对象上的目标方法。要执行的目标方法就是它。 第三个参数Object[] args 目标方法上的实参。
3.2 CGLIB动态代理
CGLIB既可以代理接口又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。 使用CGLIB需要引入依赖
dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.3.0/version
/dependency准备一个没有实现接口的类
package com.dynamic.proxy.cglib.service;/*** 目标类*/
public class UserService {// 目标方法public boolean login(String username, String password){System.out.println(正在登录);if (admin.equals(username) 123.equals(password)) {return true;}return false;}// 目标方法public void logout(){System.out.println(退出系统);}
}使用CGLIB在内存中为UserService类生成代理类并创建对象
package com.dynamic.proxy.cglib.client;import com.dynamic.proxy.cglib.service.TimerMethodInterceptor;
import com.dynamic.proxy.cglib.service.UserService;
import net.sf.cglib.proxy.Enhancer;public class Client {public static void main(String[] args) {// 创建字节码增强器对象// 这个对象是CGLIB库中的核心对象依靠它来生成代理类Enhancer enhancer new Enhancer();// 告诉CGLIB父类(目标类)enhancer.setSuperclass(UserService.class);// 设置回调接口(等同于JDK动态代理中的调用处理器InvocationHandler)// 在CGLIB中是方法拦截器接口:MethodInterceptorenhancer.setCallback(new TimerMethodInterceptor());// 创建代理对象// 这一步做两件事// 1. 在内存中生成UserService类的子类代理类的字节码// 2. 创建代理对象 new// 生成源码编译class加载到JVM并创建代理对象// 父类是UserService子类一定也是UserServiceUserService userServiceProxy (UserService) enhancer.create();// CGLIB动态代理生成的代理对象的名字格式// com.dynamic.proxy.cglib.service.UserService$$EnhancerByCGLIB$$65b0e71c5d3411dSystem.out.println(userServiceProxy);// 调用代理对象的代理方法boolean success userServiceProxy.login(admin, 123);System.out.println(success ? 登录成功 : 登录失败);userServiceProxy.logout();}
}底层本质 class UserService$$EnhancerByCGLIB$$65b0e71c extends UserService{}
net.sf.cglib.proxy.MethodInterceptor 编写MethodInterceptor接口实现类
package com.dynamic.proxy.cglib.service;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TimerMethodInterceptor implements MethodInterceptor {Overridepublic 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需要在启动项中添加两个启动参数
–add-opens java.base/java.langALL-UNNAMED–add-opens java.base/sun.net.utilALL-UNNAMED JDK动态代理和CGLIB动态代理的区别
1、JDK动态代理只能代理接口CGLIB动态代理既可以代理接口也可以代理类。 2、JDK动态代理的底层是采用实现接口的方式实现的而CGLIB动态代理底层是使用继承实现的。 3、CGLIB动态代理的效率比JDK动态代理的高。