【模式】AOP{更新中}

 

代理模式

代理模式的产生:明星A会唱歌,很多人都想找A商演,A自己不会。经纪人B,作为明星A的代理,负责接收邀请,筛选邀请,安排演唱前后接送,粉丝公关等事宜。明星A只负责唱歌就可以了。

代理模式:抽象一下,经纪人B 这群人就是ProxyClass-代理类,而明星A 这群人就是Class。ProxyClass类向外提供 Class 所有的方法(功能)。外部只需调用ProxyClass的方法,就可以让Class完成对应功能。ProxyClass 可以对调用前后进行控制。比如当有人调用ProxyClass的m1让明星A唱歌的时候,其对参数进行筛选,再调用 Class 的 m1 让明星A去赴演。

代理模式的设计类图:可以看到ProxyClass拥有Class的所有方法,并调用Class的方法。我想肯定有人会问,为什么还需要画蛇添足让两个类实现同一个接口。一是一种对外申明,Class有的接口ProxyClass全能代理。二是Class可以有很多个代理类,代理类都具有共同的代理接口,其次不需要修改现有依赖Class/ProxyClass的代码。

 

代理模式
代理模式设计类图

 

 

JDK动态代理

JDK动态代理的产生:

  • 被代理类方法特别多,代理类要做的事情雷同:假设 明星A 十项全能,有很多才艺。经纪人B 在接收邀请后做的事情却都是筛选。那么需要完全手写一个新的类,然后定义所有明星A拥有的方法。
  • 被代理类内容更改:再者随着时间的推移,A的技能在不断变化,B也又要随之修改,非常繁杂。
  • 代理类更换被代理类:假设 经纪人B 不想再给 明星A 代理了,换了另一个 运动明星C,则又得根据 明星C 修改 经纪人B 的代理方法。

本质想说明两点,普通的静态代理,也就是自己编写代理类:一个是代理类编写时的繁杂的重复,另一个是代理类ProxyClass 受到 被代理类Class 的变更的影响很大。

 

JDK动态代理的设计:

  • 供自定义方法模板:因为通常代理类的所有方法体都类似,因此不再定义一个代理类,只定义一个如下的方法模板即可。Java提供了一个java.lang.reflect.InvacationHandler接口,实现该接口的invoke方法,在方法体中写下述自定义模板。
do proxy stuff
call origin class's method like sing
do proxy stuff
  • 生成代理类:Java不是在编译时生成的类,而是在运行时生成类,Java提供了一个java.lang.reflect.Proxy类,类中有一个newInstance方法:Proxy.newInstance(类加载器ClassLoader,被代理的接口列表Class<?>[],自定义的方法模板InvocationHandler),返回一个为指定接口代理的代理对象。

 

JDK动态代理的使用:代码如下,我代理的是java.lang.Runnable接口。

package basics.proxy;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 100101001
 * @date 12/23/2019 10:18 AM
 */
public class jdk {

    private static int a = 123;    

    public static void main(String[] args) {
        Target t =new Target();
        //运行时生成类 Proxy$0 extends Proxy implements Runnable
        Runnable a = (Runnable) Proxy.newProxyInstance(Runnable.class.getClassLoader(), new Class[]{
                Runnable.class
        }, new InvocationHandler() { //这里采用了匿名类实现接口
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("jdk proxy before method..");
                method.invoke(t, args);
                System.out.println("jdk proxy after method..");
                //Runnable接口的run方法无返回值
                return null;
            }
        });
        new Thread(a).start();
    }

    //静态内部类等效于外部类,只不过类名jdk$Target
    //能访问jdk的所有静态变量和成员
    static class Target implements Runnable{
        @Override
        public void run() {
            System.out.println("target run"+a);
        }
    }
}

运行结果:

jdk proxy before method..
target run123
jdk proxy after method..

运行局部变量:a就是代理对象$Proxy0,其内部有一个h就是InvocationHnadler,因为它是jdk的一个匿名内部类,所以取类名 jdk$1,而t也是jdk的内部类Target(定义时)对象,所以取名 jdk$Target(可以和外部Target类区分,外部通过jdk.Target来访问该类)。

动态生成的代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
 
public final class Proxy0
  extends Proxy
  implements Runnable
{
  // 在静态初始化代码块中使用反射获取Method
  // 比如 m3=Class.forName(java.lang.Runnable).getMethod("run", new Class[0])
  // h是构造Proxy时,传入的InvocationHandler
  // 在run方法体中直接 this.h.invoke(this, m3, null);
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public ProxySubject(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void run()
  {
    try
    {
      this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("java.lang.Runnable").getMethod("run", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

 

AOP: Aspect-Oriented Programming

面向切面编程的产生:在OOP(Object-Oriented Progamming)中,方法都需要做一些共同的非功能性操作,比如对访问加锁,记录调用日志,异常处理。通过继承提供共同的操作,有单继承限制。因此,AOP产生了。我们把这些公共操作一一提炼成对应的代理模板(方法),组合到一个类中,这个类被称为切面(与继承垂直共享相对的一种横向共享的概念)。比如写一个日志切面类,定义实现两个代理模板方法,分别是开始计时(调用原方法前)和结束计时(调用原方法前)。这个日志切面类,记录了方法执行时间,方便优化分析,这些使用了切面功能的方法就是切入点切面类似之前动态代理中的实现InvocationHandler的类切入点类似之前的动态代理中的被代理的接口中的方法类似这样的提炼公共操作,编写切面类,定义切入点的过程就是AOP。

 

面向切面编程的应用:这一部分参考了 简书作者:糖纸疯了 的文章 006--【SpringBoot】AOP使用集锦,web应用框架往往会使用AOP实现功能模块(比如框架提供对Controller类所有方法返回值序列化这一公共操作,开发者无需自行编写),向外提供AOP工具(比如在SpringBoot中提供AOP注解,允许用户自定义切面)。

这里自定义接口日志切面效果如下。在接口执行前,记录了接口方法,接口所在类,接口参数,接口URL等信息,执行后,记录了返回值类型。

日志效果图

在SpringBoot中,自定义Aspect的方法如下。

  • Aspect注解切面类
  • 切面类内PointCut注解方法指明代理的方法
  • Before,After,AfterReturning,AfterThrowing,Around等指明切面在方法执行的流程中代做的事情
@Aspect
@Component
public class LogWebAspect {
    //统计请求的处理时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    /**
     * 指定切点
     * 匹配 com.enzoism.controller包及其子包下的所有类的所有方法
     */
    @Pointcut("execution(public * com.enzoism.controller.*.*(..))")
    public void webLog(){
    }

    /**
     * 前置通知,方法调用前被调用
     * @param joinPoint
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("我是前置通知!!!");
        startTime.set(System.currentTimeMillis());
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        System.out.println("请求参数:"+obj);
        // 代理
        Signature signature = joinPoint.getSignature();
        //代理的是哪一个方法
        System.out.println("方法:"+signature.getName());
        //AOP代理类的名字
        System.out.println("方法所在包:"+signature.getDeclaringTypeName());
        //AOP代理类的类(class)信息
        signature.getDeclaringType();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] strings = methodSignature.getParameterNames();
        System.out.println("参数名:"+ Arrays.toString(strings));
        System.out.println("参数值ARGS : " + Arrays.toString(joinPoint.getArgs()));
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = attributes.getRequest();
        // 记录下请求内容
        System.out.println("请求URL : " + req.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + req.getMethod());
        System.out.println("IP : " + req.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

    }

    /**
     * 处理完请求返回内容
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     * @param jp
     */
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }

    /**
     * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
     * @param jp
     */
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法执行时间:"+ (System.currentTimeMillis() - startTime.get()));
        System.out.println();
    }

    /**
     * 环绕通知,环绕增强,相当于MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        try {
            Object o =  pjp.proceed();
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

切面除了可以应用到某个包下的所有类中的所有方法,还可以指定应用于某个注解??待更新

@Aspect
@Component
public class LogAnnotationAspect {
    /**
     * 指定切点
     * 匹配 被添加LogAnnotation标注的方法
     */
    @Pointcut("@annotation(logAnnotation)")
    public void pointCut(LogAnnotation logAnnotation) {
    }

    /**
     * 环绕通知,环绕增强,相当于MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("pointCut(logAnnotation)")
    public Object arround(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) {
        try {
            Object o = pjp.proceed();
            System.out.println("---------------直接指定到logAnnotation位置");
            System.out.println("actionType:"+logAnnotation.actionType());
            System.out.println("controllerName:"+logAnnotation.controllerName());
            System.out.println("module:"+logAnnotation.module());
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值