(四)Spring-AOP以及线程本地化ThreadLocal的使用

定义       

   AOP(Aspect Orenited Programming : 面向切面(方面)编程)是OOP的补充

    把多个类中共有的代码,抽取到一个类中成为切面类(此类用@Abpect修饰),抽取出来的代码成为切面,通过切入点表达式切入到指定的类中,用于实现功能的扩展,将目标代码(业务代码)和扩展代码(非业务代码)分离 ,降低耦合性。   比如 每个方法 打新日志,service层使用的事物就是通过aop实现的

 

AOP相关概念

  1.横切关注点
        目标方法中执行的扩展代码(例如:日志 事物处理等),将这些代码从业务代码中分离出去,形成一个个通知
  2.切面类
        用@Aspect注解,独立地进行功能扩展的类,包含通知方法
  3.通知
        前置通知:@Before 在目标方法执行前进行功能扩展
        返回通知:@AfterRunning 在目标方法后执行功能的扩展
        异常通知:@AfterThrowing 目标方法出现异常才执行功能的扩展
        最终(后置)通知:After 目标方法中不管是否出现异常都要执行功能的扩展
        环绕通知:@Around 相当于前四种通知的组合使用,类似于拦截器,类似于InvocationHandler的invoke()方法
   4.连接点
        连接点就i是目标方法需要执行扩展的位置,一般连接点就i是一个方法(目标方法),在这个方法前后等完成功能的扩展

   5.切入点
         切入点需要通过切入点表达式来查找的,找到的可能是一个或多个连接点

 

一、基于注解的AOP

启用基于注解AOP功能

准备目标类接口和目标类

//目标接口
public interface MathCalculator {
   int add( int i,int j);
   int sub( int i,int j);
 }

//目标类:目标对象(编写业务代码)
@Component
public class EazyImpl implements MathCalculator{

     //目标对象中的方法,可以看做是连接点(实施扩展的代码位置)
     public int add(int i, int j) { 
          int result = i + j;
          System. out.println("方法内部打印:result=" +result);
          return result;
     }

     public int sub(int i, int j) {
        
          int result = i - j;
          System. out.println("方法内部打印:result=" +result);    
          return result;
     }    
 }

切面类:完成日志功能扩展。 一个切面类中,应该包含多个通知的方法。通知方法中编写的是非业务代码

@Component
@Aspect
@Order(value = 10) //多个切面执行扩展时,使用@Order注解的value来指定执行顺序,值越小,优先级越高   
public class LogAspect {

    // 可以被重复利用
    @Pointcut(value = "execution(* *.*(..))")
    private void pointcut() {}

     // 前置通知
     // value值:需要设置一个切入点表达式,将连接点(目标方法)找到,在连接点上执行前置通知。
     // @Before(value="execution(* *.*(..))")
     @Before(value = "pointcut()")
     public void startLog(JoinPoint joinPoint) {

          // 通过JoinPoint(切入点对象)获取方法参数列表 为数组
          Object[] args = joinPoint.getArgs();
          // 将数组转化为集合
          List asList = Arrays.asList(args);

          // 通过JoinPoint(切入点对象)获取方法对象
          Signature signature = joinPoint.getSignature();
          String methodName = signature.getName();

          System. out.println("[☆日志][" + methodName + "方法开始][参数值:" + asList + "]");
     }

     // 返回通知
     // @AfterReturning(value="execution(public int com.atguigu.spring.inter.EazyImpl.*( int, int))")
     @AfterReturning(value = "pointcut()", returning = "result" )
     public void endLog(JoinPoint joinPoint, Object result) {

          Object[] args = joinPoint.getArgs();
          List asList = Arrays.asList(args);

          Signature signature = joinPoint.getSignature();
          String methodName = signature.getName();

          System. out.println("[☆日志][" + methodName + "方法结束][参数值:" + asList + "][结果:" + result + "]" );
      }

      // 异常通知
      @AfterThrowing(value = "execution(public int com.atguigu.spring.inter.EazyImpl.*(int, int))", throwing = "e")
      public void throwLog(JoinPoint joinPoint, Throwable e) {

           Object[] args = joinPoint.getArgs();
           List asList = Arrays.asList(args);

           Signature signature = joinPoint.getSignature();
           String methodName = signature.getName();
           System. out.println("[☆日志][" + methodName + "方法出现异常][参数值:" + asList + "]");
           System. out.println("e------>>>>>>>" + e.getMessage());
       }

       // 最终通知(后置通知)
       @After(value = "execution(public int com.atguigu.spring.inter.EazyImpl.*(int, int))")
       public void finallyLog(JoinPoint joinPoint) {

            Object[] args = joinPoint.getArgs();
            List asList = Arrays.asList(args);

            Signature signature = joinPoint.getSignature();
            String methodName = signature.getName();
            System. out.println("[☆日志][" + methodName + "方法最终执行][参数值:" + asList + "]");
        }

        // 环绕通知
        @Around(value = "execution(* *.*(..))")
        public Object aroundLog(ProceedingJoinPoint proceedingJoinPoint) {

             Object result = null;

             Signature signature = proceedingJoinPoint.getSignature();
             String methodName = signature.getName();

             Object[] args = proceedingJoinPoint.getArgs();
             List asList = Arrays.asList(args);
              try {
                  // 前置通知:
                  System. out.println("[around☆日志][" + methodName + "方法开始][参数值:" + asList + "]");
            
                  // 执行目标
                  result = proceedingJoinPoint.proceed();

                  // 返回通知
                  System. out.println("[around☆日志][" + methodName + "方法结束][参数值:" + asList + "]");

             } catch (Throwable e) {
                  // 异常通知
                 System. out.println("[around☆日志][" + methodName + "方法出现异常][参数值:" + asList + "]");
                 e.printStackTrace();
                 throw new RuntimeException(e); // 目标方法如何发生异常,捕获后一定要抛出
             } finally {
                 // 最终通知(后置通知)
                 System. out.println("[around☆日志][" + methodName + "方法最终执行][参数值:" + asList + "]");
             }
             // 目标对象方法的返回结果。
             return result;
        }
}
 
  

测试

@Test
public void test() {
      //获取代理对象
      //目标类有接口:那么,框架采用的是JDK动态代理来进行功能扩展的。
      MathCalculator obj = ioc.getBean(MathCalculator.class);
      //目标类没有实现任何接口:那么,框架采用的是 Cglib动态代理来进行功能扩展的。
      //EazyImpl obj = ioc.getBean(EazyImpl.class);
        
      //目标类如果实现某个特定的接口,那么,就会产生JDK动态代理,代理类不是目标类的子类,所以,强制转换存在异常。
      //EazyImpl obj = (EazyImpl)ioc.getBean("eazyImpl");
      System. out.println(obj.getClass());
      obj.add(10, 5);
}

二、基于XMl的AOP

以AOP技术为基础,记录项目中的异常日志信息

public class LogRecordor {
    @Autowired
    private LogService logService;
    // 记录日志的通知方法,环绕通知
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法签名
        Signature signature = joinPoint.getSignature();
        // 通过方法签名获取方法名
        String methodName = signature.getName();  
        //通过方法签名获取方法所在的类的类型
        String className = signature.getDeclaringTypeName();   
        //获取目标方法执行时的参数的数组
        Object[] args = joinPoint.getArgs();  
        Object result = null;
        String exceptionType = null;
        String exceptionMessage = null;
        try{
            //调用目标方法
            joinPoint.proceed();
        }catch(Throwable e){
            //获取异常信息的初始化操作
            exceptionType = e.getClass().getName();
            exceptionMessage = e.getMessage();
            //尝试获取当前异常的原因:原因可能有也可能没有
            Throwable cause = e.getCause();        
            while(cause!=null){
                //如果有则,找到更深一层的原因
                exceptionType = cause.getClass().getName();
                exceptionMessage = cause.getMessage();
                //进一步获取,原因的原因,知道最底层的原因位置,才能结束while循环
                cause = cause.getCause();
            }    
            //这里收集异常信息后,还需要再抛出,否则框架将捕获不到异常,声明式异常将不起作用
            throw e;
        }finally{    
            //为记录日志收集信息并将其保存到数据库
            //操作时间(对时间进行格式化)
            String operateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());;
            //将参数的数组转化为list以便于打印toString();
            List argList = Arrays.asList(args);
            //返回值
            String returnValue = (result==null)?"【无】":result.toString();
            //从当前线程获取request对象(在这里不能注入requset对象因为request对象不是有IOC创建的,
            //也不能new HttpServletRequest(),因为本身要的是request是用户的请求里面有请求参数,如果new一个新的request对象的话是一个新的request空对象)
            HttpServletRequest request = RequestThreadBinder.getRequest();
            HttpSession session = request.getSession();
            User user = (User) session.getAttribute(GlobalNames.LOGIN_USER);
            Admin admin = (Admin) session.getAttribute(GlobalNames.LOGIN_ADMIN);  
            String operator = ((user==null)?"":user.getUserName())+"/"+((admin==null)?"":admin.getAdminName());
            Log log = new Log(null, operateTime, operator, methodName, className, argList.toString(), returnValue, exceptionType,exceptionMessage);
            //绑定数据源对应的键以便选择数据源
            RouterToken.bindToken(RouterToken.DATASOURCE_LOG);
            //专门定义一个方法,将日志信息保存到‘当月的’数据表中
            logService.saveLog(log);
        }
        return result;
    }
}
 
  

配置切面日志类bean


    
    
       
        
        s
            
            
        
    

线程本地化-ThreadLocal的使用

(某个方法需要使用HttpServletRequest,但是方法中没有传入当前的request对象)

自定义线程本地化绑定的类 

public class RequestThreadBinder {
    // 声明当前线程 指定泛型为request
    private static ThreadLocal local = new ThreadLocal();
    // 将request绑定到当前线程的方法
    public static void bindRequest(HttpServletRequest request) {
        local.set(request);
    }
    // 从当前线程获取request的方法
    public static HttpServletRequest getRequest() {
        return local.get();
    }
    // 从当前线程移除request的方法
    public static void removeRequest() {
        local.remove();
    }
}

 

自定义拦截器(可考虑重新参考Spring-MVC的自定义拦截器),将HttpServletRequest绑定到本地线程上

 

public class RequestBindingInterceptor extends HandlerInterceptorAdapter{
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) sthrows Exception {
        //在调用目标handler方法之前将request对象绑定到当前线程
        RequestThreadBinder.bindRequest(request);
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception { 
        //在执行完目标handler方法之后将request对象从当前线程移除
        RequestThreadBinder.removeRequest();
    }
}

你可能感兴趣的:(Spring)