详解SpringBoot之集成Spring AOP

在开始之前,我们先把需要的jar包添加到工程里。新增Maven依赖如下:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency> 

接下来,我们进入正题。这里的涉及的通知类型有:前置通知、后置最终通知、后置返回通知、后置异常通知、环绕通知,下面我们就具体的来看一下怎么在SpringBoot中添加这些通知。

首先我们先创建一个Aspect切面类:

@Component
@Aspect
public class WebControllerAop { 

} 

指定切点:

//匹配com.zkn.learnspringboot.web.controller包及其子包下的所有类的所有方法
@Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")
public void executeService(){ 

}

接着我们再创建一个Controller请求处理类:

package com.zkn.learnspringboot.web.controller; 

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; 

/**
 * Created by zkn on 2016/11/19.
 */
@RestController
@RequestMapping("/aop")
public class AopTestController { 

}

前置通知

配置前置通知:

/**
 * 前置通知,方法调用前被调用
 * @param joinPoint
 */
@Before("executeService()")
public void doBeforeAdvice(JoinPoint joinPoint){
  System.out.println("我是前置通知!!!");
  //获取目标方法的参数信息
  Object[] obj = joinPoint.getArgs();
  //AOP代理类的信息
  joinPoint.getThis();
  //代理的目标对象
  joinPoint.getTarget();
  //用的最多 通知的签名
  Signature signature = joinPoint.getSignature();
  //代理的是哪一个方法
  System.out.println(signature.getName());
  //AOP代理类的名字
  System.out.println(signature.getDeclaringTypeName());
  //AOP代理类的类(class)信息
  signature.getDeclaringType();
  //获取RequestAttributes
  RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  //从获取RequestAttributes中获取HttpServletRequest的信息
  HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
  //如果要获取Session信息的话,可以这样写:
  //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
  Enumeration<String> enumeration = request.getParameterNames();
  Map<String,String> parameterMap = Maps.newHashMap();
  while (enumeration.hasMoreElements()){
    String parameter = enumeration.nextElement();
    parameterMap.put(parameter,request.getParameter(parameter));
  }
  String str = JSON.toJSONString(parameterMap);
  if(obj.length > 0) {
    System.out.println("请求的参数信息为:"+str);
  }
}

注意:这里用到了JoinPoint和RequestContextHolder。通过JoinPoint可以获得通知的签名信息,如目标方法名、目标方法参数信息等。通过RequestContextHolder来获取请求信息,Session信息。

接下来我们在Controller类里添加一个请求处理方法来测试一下前置通知:

@RequestMapping("/testBeforeService.do")
public String testBeforeService(String key,String value){ 

  return "key="+key+" value="+value;
} 

前置通知拦截结果如下所示:

后置返回通知

配置后置返回通知的代码如下:

/**
 * 后置返回通知
 * 这里需要注意的是:
 *   如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
 *   如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
 * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
 * @param joinPoint
 * @param keys
 */
@AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){ 

  System.out.println("第一个后置返回通知的返回值:"+keys);
} 

@AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys",argNames = "keys")
public void doAfterReturningAdvice2(String keys){ 

  System.out.println("第二个后置返回通知的返回值:"+keys);
}

Controller里添加响应的请求处理信息来测试后置返回通知:

@RequestMapping("/testAfterReturning.do")
public String testAfterReturning(String key){ 

  return "key=: "+key;
}
@RequestMapping("/testAfterReturning01.do")
public Integer testAfterReturning01(Integer key){ 

  return key;
}

当发送请求为:http://localhost:8001/aop/testAfterReturning.do?key=testsss&value=855sss时,处理结果如图所示:

当发送请求为:http://localhost:8001/aop/testAfterReturning01.do?key=55553&value=855sss时,处理结果如图所示:

后置异常通知

后置异常通知的配置方式如下:

/**
 * 后置异常通知
 * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
 * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
 *   对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
 * @param joinPoint
 * @param exception
 */
@AfterThrowing(value = "executeService()",throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
  //目标方法名:
  System.out.println(joinPoint.getSignature().getName());
  if(exception instanceof NullPointerException){
    System.out.println("发生了空指针异常!!!!!");
  }
}

Controller里配置响应的请求处理类:

@RequestMapping("/testAfterThrowing.do")
public String testAfterThrowing(String key){ 

  throw new NullPointerException();
} 

后置异常通知方法的处理结果如下所示:

后置最终通知

后置最终通知的配置方式如下:

/**
 * 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
 * @param joinPoint
 */
@After("executeService()")
public void doAfterAdvice(JoinPoint joinPoint){ 

  System.out.println("后置通知执行了!!!!");
}

Controller类配置相应的请求处理类:

@RequestMapping("/testAfter.do")
public String testAfter(String key){ 

  throw new NullPointerException();
}
@RequestMapping("/testAfter02.do")
public String testAfter02(String key){ 

  return key;
} 

当发送请求为:http://localhost:8001/aop/testAfter.do?key=55553&value=855sss

当发送请求为:http://localhost:8001/aop/testAfter02.do?key=55553&value=855sss

环绕通知

环绕通知的配置方式如下:

/**
 * 环绕通知:
 *  环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
 *  环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
 */
@Around("execution(* com.zkn.learnspringboot.web.controller..*.testAround*(..))")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
  System.out.println("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());
  try {
    Object obj = proceedingJoinPoint.proceed();
    return obj;
  } catch (Throwable throwable) {
    throwable.printStackTrace();
  }
  return null;
}

Controller对应的请求处理类如下:

@RequestMapping("/testAroundService.do")
public String testAroundService(String key){ 

  return "环绕通知:"+key;
}

当发送请求为:http://localhost:8001/aop/testAroundService.do?key=55553

当发送请求为:http://localhost:8001/aop/testAfter02.do?key=55553&value=855sss时,不符合环绕通知的切入规则,所以环绕通知不会 执行。

完整的AOP配置代码如下:

package com.zkn.learnspringboot.aop; 

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; 

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Enumeration;
import java.util.Map; 

/**
 * Created by zkn on 2016/11/18.
 */
@Component
@Aspect
public class WebControllerAop { 

  //匹配com.zkn.learnspringboot.web.controller包及其子包下的所有类的所有方法
  @Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")
  public void executeService(){ 

  } 

  /**
   * 前置通知,方法调用前被调用
   * @param joinPoint
   */
  @Before("executeService()")
  public void doBeforeAdvice(JoinPoint joinPoint){
    System.out.println("我是前置通知!!!");
    //获取目标方法的参数信息
    Object[] obj = joinPoint.getArgs();
    //AOP代理类的信息
    joinPoint.getThis();
    //代理的目标对象
    joinPoint.getTarget();
    //用的最多 通知的签名
    Signature signature = joinPoint.getSignature();
    //代理的是哪一个方法
    System.out.println(signature.getName());
    //AOP代理类的名字
    System.out.println(signature.getDeclaringTypeName());
    //AOP代理类的类(class)信息
    signature.getDeclaringType();
    //获取RequestAttributes
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    //从获取RequestAttributes中获取HttpServletRequest的信息
    HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
    //如果要获取Session信息的话,可以这样写:
    //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
    Enumeration<String> enumeration = request.getParameterNames();
    Map<String,String> parameterMap = Maps.newHashMap();
    while (enumeration.hasMoreElements()){
      String parameter = enumeration.nextElement();
      parameterMap.put(parameter,request.getParameter(parameter));
    }
    String str = JSON.toJSONString(parameterMap);
    if(obj.length > 0) {
      System.out.println("请求的参数信息为:"+str);
    }
  } 

  /**
   * 后置返回通知
   * 这里需要注意的是:
   *   如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
   *   如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
   * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
   * @param joinPoint
   * @param keys
   */
  @AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys")
  public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){ 

    System.out.println("第一个后置返回通知的返回值:"+keys);
  } 

  @AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys",argNames = "keys")
  public void doAfterReturningAdvice2(String keys){ 

    System.out.println("第二个后置返回通知的返回值:"+keys);
  } 

  /**
   * 后置异常通知
   * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
   * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
   *   对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
   * @param joinPoint
   * @param exception
   */
  @AfterThrowing(value = "executeService()",throwing = "exception")
  public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
    //目标方法名:
    System.out.println(joinPoint.getSignature().getName());
    if(exception instanceof NullPointerException){
      System.out.println("发生了空指针异常!!!!!");
    }
  } 

  /**
   * 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
   * @param joinPoint
   */
  @After("executeService()")
  public void doAfterAdvice(JoinPoint joinPoint){ 

    System.out.println("后置通知执行了!!!!");
  } 

  /**
   * 环绕通知:
   *  环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
   *  环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
   */
  @Around("execution(* com.zkn.learnspringboot.web.controller..*.testAround*(..))")
  public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
    System.out.println("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());
    try {//obj之前可以写目标方法执行前的逻辑
      Object obj = proceedingJoinPoint.proceed();//调用执行目标方法
      return obj;
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
    return null;
  }
}

完整的Controller类代码如下:

package com.zkn.learnspringboot.web.controller; 

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; 

/**
 * Created by zkn on 2016/11/19.
 */
@RestController
@RequestMapping("/aop")
public class AopTestController { 

  @RequestMapping("/testBeforeService.do")
  public String testBeforeService(String key,String value){ 

    return "key="+key+" value="+value;
  }
  @RequestMapping("/testAfterReturning.do")
  public String testAfterReturning(String key){ 

    return "key=: "+key;
  }
  @RequestMapping("/testAfterReturning01.do")
  public Integer testAfterReturning01(Integer key){ 

    return key;
  }
  @RequestMapping("/testAfterThrowing.do")
  public String testAfterThrowing(String key){ 

    throw new NullPointerException();
  }
  @RequestMapping("/testAfter.do")
  public String testAfter(String key){ 

    throw new NullPointerException();
  }
  @RequestMapping("/testAfter02.do")
  public String testAfter02(String key){ 

    return key;
  }
  @RequestMapping("/testAroundService.do")
  public String testAroundService(String key){ 

    return "环绕通知:"+key;
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Boot学习入门之AOP处理请求详解

    前言 面向切面(AOP)Aspect Oriented Programming是一种编程范式,与语言无关,是一种程序设计思想,它也是spring的两大核心之一. 在spring Boot中,如何用AOP实现拦截器呢? 首先加入依赖关系: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId>

  • spring-boot使用AOP统一处理日志

    AOP我想大家都很清楚,有时候我们需要处理一些请求日志,或者对某些方法进行一些监控,如果出现例外情况应该进行怎么样的处理,现在,我们从spring-boot中引入AOP. [开发环境:jdk版本号为1.8,spring boot的版本号为1.4.1]{style="background-color:#FF0000"} 首先,我们先引入jar包, POM文件添加如下内容: <!--引用AOP--> <dependency> <groupId>org.s

  • 详解SpringBoot AOP 拦截器(Aspect注解方式)

    常用用于实现拦截的有:Filter.HandlerInterceptor.MethodInterceptor 第一种Filter属于Servlet提供的,后两者是spring提供的,HandlerInterceptor属于Spring MVC项目提供的,用来拦截请求,在MethodInterceptor之前执行. 实现一个HandlerInterceptor可以实现接口HandlerInterceptor,也可以继承HandlerInterceptorAdapter类,两种方法一样.这个不在本文

  • spring boot如何使用spring AOP实现拦截器

    在spring boot中,简单几步,使用spring AOP实现一个拦截器: 1.引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 2.创建拦截器类(在该类中,定义了拦截规则:拦截com.xjj.web.controller包下面的所

  • 详解Spring Boot中使用AOP统一处理Web请求日志

    在spring boot中,简单几步,使用spring AOP实现一个拦截器: 1.引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframewo

  • 详解SpringBoot之集成Spring AOP

    在开始之前,我们先把需要的jar包添加到工程里.新增Maven依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 接下来,我们进入正题.这里的涉及的通知类型有:前置通知.后置最终通知.后置返回通知.后置异常通知.环绕通知,下面我们就具体的

  • 详解Springboot2.3集成Spring security 框架(原生集成)

    0.pom <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0

  • 详解SpringBoot+Dubbo集成ELK实战

    前言 一直以来,日志始终伴随着我们的开发和运维过程.当系统出现了Bug,往往就是通过Xshell连接到服务器,定位到日志文件,一点点排查问题来源. 随着互联网的快速发展,我们的系统越来越庞大.依赖肉眼分析日志文件来排查问题的方式渐渐凸显出一些问题: 分布式集群环境下,服务器数量可能达到成百上千,如何准确定位? 微服务架构中,如何根据异常信息,定位其他各服务的上下文信息? 随着日志文件的不断增大,可能面临在服务器上不能直接打开的尴尬. 文本搜索太慢.无法多维度查询等 面临这些问题,我们需要集中化的

  • 详解springboot+aop+Lua分布式限流的最佳实践

    一.什么是限流?为什么要限流? 不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了限流!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤.列车的超载,存在一定的安全隐患.同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃.为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间. 限流是保证系统高可用的重要手段!!! 由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,

  • 详解SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)

    引言 ​前两天在写一个实时数据处理的项目,项目要求是 1s 要处理掉 1k 的数据,这时候显然光靠查数据库是不行的,技术选型的时候老大跟我提了一下使用 Layering-Cache 这个开源项目来做缓存框架. ​之间问了一下身边的小伙伴,似乎对这块了解不多.一般也就用用 Redis 来缓存,应该是很少用多级缓存框架来专门性的管理缓存吧. ​趁着这个机会,我多了解了一些关于 SpringBoot 中缓存的相关技术,于是有了这篇文章! 在项目性能需求比较高时,就不能单单依赖数据库访问来获取数据了,必

  • 详解Springboot集成sentinel实现接口限流入门

    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面. 在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数:或者按照某种规则进行限流,如限制ip的单位时间访问次数等. 之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善.来看一下sentinel的简介: https://github.com/spring-cloud-incubator/spring-cloud-alibab

  • 详解SpringBoot集成消息队列的案例应用

    目录 背景 方案规划 统一设计 集成Redis消息队列 集成ActiveMQ消息队列 使用示例 背景 最近在对公司开发框架进行优化,框架内涉及到多处入库的日志记录,例如登录日志/操作日志/访问日志/业务执行日志,集成在业务代码中耦合度较高且占用业务操作执行时间,所以准备集成相关消息队列进行代码解耦 方案规划 现有的成熟消息队列组件非常多,例如RabbitMQ,ActiveMQ,Kafka等,考虑到业务并发量不高且框架已经应用于多个项目平稳运行,准备提供基于Redis的消息队列和集成ActiveM

  • 详解springboot整合ehcache实现缓存机制

    EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. ehcache提供了多种缓存策略,主要分为内存和磁盘两级,所以无需担心容量问题. spring-boot是一个快速的集成框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 由于spring-boot无需任何样板化的配置文件,所以spring-boot集成一些其他框架时会有略微的

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • 详解SpringBoot基于Dubbo和Seata的分布式事务解决方案

    1. 分布式事务初探 一般来说,目前市面上的数据库都支持本地事务,也就是在你的应用程序中,在一个数据库连接下的操作,可以很容易的实现事务的操作. 但是目前,基于SOA的思想,大部分项目都采用微服务架构后,就会出现了跨服务间的事务需求,这就称为分布式事务. 本文假设你已经了解了事务的运行机制,如果你不了解事务,那么我建议先去看下事务相关的文章,再来阅读本文. 1.1 什么是分布式事务 对于传统的单体应用而言,实现本地事务可以依赖Spring的@Transactional注解标识方法,实现事务非常简

随机推荐