实用的设计模式06

编程入门 行业动态 更新时间:2024-10-06 10:37:21

实用的设计<a href=https://www.elefans.com/category/jswz/34/1771241.html style=模式06"/>

实用的设计模式06

结构型模式总共有七种,它们分别是适配器模式、装饰器模式、代理模式、外观模式(门面模式)、桥接模式、组合模式、享元模式,本文就来讨论一下大名鼎鼎的代理模式。代理模式使用非常多,比如在SpringAOP就是使用的JDK动态代理实现的,还有远程过程调用RPC等。

content

  • 代理模式及其作用
  • 静态代理
  • 两种动态代理
    • 基于反射实现的JDK动态代理
  • 深入JDK动态代理
  • 案例:SpringAOP

代理模式及其作用

代理模式就是为其他对象提供一种代理控制对这个对象的访问。例如:我要使用对象A的某些功能,但是我的权限比较低不能让我使用A的全部功能,就可以为A提供一个代理对象ProxyA,ProxyA就是A的一个代理,也可以理解成助理,我要操作A必须通过ProxyA。
动态代理有什么作用呢?

  • 首先就是上面提到的,可以做权限的控制,隔离或者隐藏原来的对象,也就是隔离作用。
  • 其次,可以对原来的对象做功能的扩充,也就是增强作用。
  • 代理对象甚至可以完全替代原来的对象。

静态代理

首先介绍静态代理,静态代理非常容易理解

  1. 静态代理的结构
    • 接口Subject:代理对象和真实对象的共同接口,这样在使用真实对象的地方才可以使用代理对象替换。
    • 真实对象RealObject:真实的对象
    • 代理对象Proxy:真实对象的代理对象,其中会包含真实对象
  2. 实现

Subject.java

public interface Subject {void sayHello(String name);
}

RealObject.java

public class RealObject implements Subject{@Overridepublic void sayHello(String name) {System.out.println("hello "+name);}
}

ProxyObject.java

public class ProxyObject implements Subject {private RealObject realObject;public ProxyObject() {if (realObject == null){this.realObject = new RealObject();}}@Overridepublic void sayHello(String name) {System.out.println("====before=====");realObject.sayHello(name);System.out.println("====after=====");}
}

Subject.java

public class Client {public static void main(String[] args) {System.out.println("原对象:");RealObject realObject = new RealObject();realObject.sayHello("real");System.out.println();System.out.println("代理对象:");ProxyObject proxyObject = new ProxyObject();proxyObject.sayHello("proxy");}
}

输出:

3.静态代理的特点
使用静态代理我们需要为每一个代理对象单独开发类,并且将对应的控制、增强等逻辑写到代理类中,在程序运行之前都已经固定了,正因此称之为静态代理。

两种动态代理

静态代理简单却不够灵活,首先代理对象的逻辑代码是提前写死的,其次代理对象和原对象实现了相同的接口,代理对象中的方法需要和原对象中的一致,因此任何原对象的更改都需要在代理对象中更改。
动态代理就是代码执行过程中动态的创建代理对象,这样我们就可以为代理对象动态地增加方法、增加行为,比如根据运行环境(客户类传来的参数等)动态决定代理类的行为等,同时不需要担心原对象的修改,在java中主要有两种实现方式:

  • 反射实现:基于反射的方式,需要被代理对象实现接口。
  • Cglib实现:基于字节码实现,不需要代理对象实现接口,但是效率稍低,生成的代理对象是原对象的子类,因此要求原对象不能是final的。

基于反射实现的JDK动态代理

在java中反射实现动态代理十分容易,JDK已经提供了创建代理对象的静态方法:
Object Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
我们先看一下如何使用,还是上面的例子,在Client.java中添加如下代码:
Client,java

public class Client {public static void main(String[] args) {System.out.println("原对象:");Subject realObject = new RealObject();realObject.sayHello("real");System.out.println("代理对象:");Subject proxyObject = new ProxyObject();proxyObject.sayHello("proxy");System.out.println();/**public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)ClassLoader loader :这是一个类加载器,作用就是将编译好的字节码文件加载到内存。Class<?>[] interfaces :这是一个Class数组,其中是原对象所实现的接口的Class对象,因为接口是多继承的,所以此处是一个数组,以此确定代理对象的类型。InvocationHandler h :InvocationHandler是一个接口,因此此处需要一个实现了该接口的对象,或者直接使用匿名内部类,其中只有一个方法invoke,在创建代理对象后,无论我们调用代理对象的任何方法,都会通过该invoke方法通过反射的方式执行。*/Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("-------dynamic before-----");/*method.invoke(realObject,args): 执行realObject对象的method方法,参数为args该案例中就是执行的subject.sayHello("dynamic proxy");*/method.invoke(realObject,args);return null;}});//subject就是realObject的代理subject.sayHello("dynamic proxy");}
}

执行后的输出:

我们可以看到,没有创建额外的类,甚至只需要一个对象realObject,使用Proxy.newProxyInstance就创建了一个该类的代理对象,并且实现了接口中所有方法的增强。使用JDK动态代理的要点就是Proxy.newProxyInstance方法的三个参数,详情参考代码注释即可。

深入JDK动态代理

知道了JDK动态代理怎么用是还不够的,其实现原理是什么呢?
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。
首先我们看一下Proxy.newProxyInstance:
Proxy.newProxyInstance

上述代码中可以看到Proxy.newProxyInstance方法是如何创建代理对象的,在深入的反射如何创建对象,我们不去讨论。但是代理对象为什么通过InvocationHandler的invoke方法执行原对象的方法,我们需要找到代理对象一探究竟。我们在Client类中添加如下代码
Client.java

public class Client {public static void main(String[] args) {System.out.println("原对象:");Subject realObject = new RealObject();realObject.sayHello("real");System.out.println("代理对象:");Subject proxyObject = new ProxyObject();proxyObject.sayHello("proxy");System.out.println();//Subject subject = (Subject) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), new Class[]{Subject.class}, new ProxyBean<Subject>(realObject));/**public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)ClassLoader loader :这是一个类加载器,作用就是将编译好的字节码文件加载到内存。Class<?>[] interfaces :这是一个Class数组,其中是原对象所实现的接口的Class对象,因为接口是多继承的,所以此处是一个数组。InvocationHandler h :InvocationHandler是一个接口,因此此处需要一个实现了该接口的对象,或者直接使用匿名内部类。*/Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("-------dynamic before-----");/*method.invoke(realObject,args): 执行realObject对象的method方法,参数为args*/method.invoke(realObject,args);return null;}});//下面的代码是将生成的代理类反编译写入到根目录的RealObject.classbyte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", RealObject.class.getInterfaces());String path = "./RealObject.class";try(FileOutputStream fos = new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println("代理类class文件写入成功");} catch (Exception e) {System.out.println("写文件错误");}subject.sayHello("dynamic proxy");}
}

执行上述代码我们就可以得到动态代理生成的代理类:

public final class $Proxy0 extends Proxy implements Subject {private static Method m1;private static Method m3;private static Method m2;private static Method m0;/*** 调用的是父类的构造方法生成对象,上述Proxy类中标记的第四部正是调用的该方法创建对象。*          protected Proxy(InvocationHandler h) {*              Objects.requireNonNull(h);*              this.h = h;//Proxy中有一个InvocationHandler*          }* @param var1*/public $Proxy0(InvocationHandler var1) throws  {super(var1);}/*** 这里就是代理对象的sayHello方法,我们可以看到正式通过InvocationHandler的invoke方法执行的* m3是一个Method,在下面的静态代码块中使用反射的方式创建的。* @param var1*/public final void sayHello(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.iwat.design.pattern.proxy.Subject").getMethod("sayHello", Class.forName("java.lang.String"));m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}/*省略toString() hashCode()*/
}

再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象(匿名内部类中的method.invoke(realObject,args);realObject对象),再联系到InvacationHandler中的invoke方法。

案例:SpringAOP

案例源码:

最后我实现了一个使用SpringBoot+自定义注解的SpringAOP使用案例,通过环绕通知的方式自动生成日志。我们来对比一下JDK动态代理,观察一下在AOP中是如何运用动态代理的。

此处我们重点看一下切面的实现:
SysLogAspect

/**** 系统日志:切面处理类** Created by LMD on 2019/3/22.*/
@Aspect
@Component
public class SysLogAspect {private static Logger LOG = LoggerFactory.getLogger(SysLogAspect.class);//定义切点 @Pointcut//在注解的位置切入代码@Pointcut("@annotation( com.iwat.spring.aop.annotation.OperLog)")public void logPoinCut() {}//@Around:环绕通知@Around("logPoinCut()")public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint) {System.out.println("环绕通知开始。。。。。");//保存日志SysLog sysLog = new SysLog();MethodSignature signature = null;//从切面织入点处通过反射机制获取织入点处的方法if (proceedingJoinPoint.getSignature() instanceof  MethodSignature){signature = (MethodSignature) proceedingJoinPoint.getSignature();}//获取切入点所在的方法Method method = signature.getMethod();//获取操作OperLog myLog = method.getAnnotation(OperLog.class);if (myLog != null) {String value = myLog.message();sysLog.setMessage(value);//保存获取的操作}//获取请求的类名String className = signature.getClass().getName();//获取请求的方法名String methodName = method.getName();sysLog.setMethod(className + "." + methodName);//请求的参数Object[] args = proceedingJoinPoint.getArgs();//将参数所在的数组转换成jsonString params = JSON.toJSONString(args);sysLog.setParams(params);sysLog.setCreateDate(new Date());//获取用户名//获取用户ip地址// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录下请求内容LOG.info("URL : " + request.getRequestURL().toString());LOG.info("HTTP_METHOD : " + request.getMethod());LOG.info("IP : " + request.getRemoteAddr());sysLog.setIp(request.getRemoteAddr());//开始调用时间// 计时并调用目标函数long start = System.currentTimeMillis();Long time = System.currentTimeMillis() - start;sysLog.setTotalTime(time);//打印日志LOG.info(sysLog.toString());//调用service保存SysLog实体类到数据库//sysLogService.save(sysLog);try {//proceedingJoinPoint.proceed();的作用就是动态代理中的invocationHandler中method.invoke()的方法的作用,// 执行原方法,proceedingJoinPoint本身就是一个拦截到的方法的签名Object result = proceedingJoinPoint.proceed();System.out.println("接口返回信息:"+result.toString());System.out.println("环绕通知结束。。。。。");return result;} catch (Throwable throwable) {throwable.printStackTrace();}return new HashMap<String,Object>(){{put("msg","error");}};}
}

proceedingJoinPoint.proceed();的作用就是动态代理中的invocationHandler中method.invoke()的方法的作用: 执行原方法,proceedingJoinPoint本身就是一个拦截到的方法的签名。

所以切面类就类似于动态代理中的InvocationHandler匿名内部类,可以在方法执行的前后去执行一些操作。但是SpringAOP更强大,可以通过切点指定多个需要增强的方法,进而创建代理对象,通过代理对象去执行增强后的方法。可以任意的对某个对象实现代理而进行统一的增强处理。

运行项目以后,在浏览器访问localhost:8888/test,查看控制台打印的信息:

可以看到,和动态代理是一样的,在原方法执行的前后增加了一些业务实现。

需要注意的是,本案例中创建代理对象时使用的是Cjlib的方式,因为Controller类没有实现任何接口,所以无法使用反射的方式,如果将注解加到service层的方法上(实现类中),就会发现使用反射的方式创建代理对象。读者可以下载源码后自行试一试。

如有不足,欢迎指正。

更多推荐

实用的设计模式06

本文发布于:2024-02-13 09:55:44,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1758058.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!