您现在的位置是:首页 > 文章详情

SpringBoot使用AOP

日期:2019-07-13点击:387

众所周知AOP(Aspect Oriented Programming)是Spring的核心之一,是OOP面向对象编程的延续和补充,是面向切面编程,他的底层实现是代理模式,简单来说,代理模式分为静态代理模式和动态代理模式,而代理模式又分为JDK动态代理和CGLib代理,AOP则是基于动态代理实现,默认是使用JDK动态代理,若没有接口则会使用CGLib代理,前者基于接口,后者基于子类,若兴趣深入了解代理模式的,可参考Java代理模式一文,下面简单说下AOP的基本概念.

AOP的适用场景:

  • 日志记录
  • 事务处理
  • 异常处理
  • 性能统计
  • 拦截鉴权
  • 缓存
  • 等等..

AOP的组成

  • Aspect(切面):通常是一个类,存放公共功能,可以在里面定义切入点和通知
  • JoinPoint(连接点):程序执行过程中可以插入的切面的点,一般是方法调用,异常抛出
  • Advice(通知):是切面的具体实现,在切入点上执行的逻辑处理,以目标方法为参照点,根据放置位置的不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around).
  • PointCut(切入点):带有通知的连接点

AOP通知类型(Advice)的介绍

  • @before(前置通知):在目标方法执行前先执行此方法
  • @after(后置):在目标方法执行后执行
  • @AfterReturning(最终返回):在目标方法正常完成之后
  • @AfterThrowing(异常通知):在目标方法抛出异常时执行
  • @Around(环绕通知):在目标方法执行前后都执行

下面是SpringBoot使用AOP实现的鉴权案例

先贴代码

 * @author :zoe * Target 的注解类型 适用场景 * TYPE 类(包括Enum)接口 * PACKAGE 包 * METHOD 方法 * FIELD 成员域(包括Enum常量) * CONSTRUCTOR 构造器 * PARAMETER 方法或构造器参数 * LOCAL_VARIABLE 本地变量 * ANNOTATION_TYPE 注解类型声明 * java 8 新加 * TYPE_PARAMETER 类型参数声明 * TYPE_USE 类型的使用 * Retention的保留策略 * 保留规则 描述 * SOURCE 注释将被编译器丢弃,不包括在类文件中 * CLASS 注释由编译器记录在类文件中,但是不需要在运行时被虚拟机(VM)保留。默认策略 * RUNTIME 注释由编译器记录在类文件中,并在运行时由VM保存,因此可以反射可读取它们 * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Login { }

 * @author zoe **/ @Component @Aspect @Slf4j public class LoginAop { @Pointcut("@annotation(com.zoe.aop.Login))") public void cut() { } @Before("cut()") public void before(){ log.info("==================== 进入登录验证 =================="); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); assert servletRequestAttributes != null; HttpServletRequest request = servletRequestAttributes.getRequest(); String token = request.getHeader("token"); if (ParamUtil.isEmpty(token)){ throw new HttpException("当前未登录!"); } } /** * */ @After("cut(),execution(*com.zoe.aop.TestController.test(..))") public void after(){ log.info("========================= 方法执行完毕,开始打印========================="); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); assert servletRequestAttributes != null; HttpServletRequest request = servletRequestAttributes.getRequest(); String method = request.getMethod();//请求方式 log.info("请求方式: "+method); //获取请求资源 String requestURI = request.getRequestURI(); log.info("请求资源requestURI : "+requestURI); StringBuffer requestURL = request.getRequestURL(); log.info("该方法的请地址是 :"+requestURL); //获取get请求参数 String queryString = request.getQueryString(); log.info("get请求参数 :"+queryString); //获取当前web应用名称 String contextPath = request.getContextPath(); log.info("当前web应用名称 :"+contextPath); //获取所有请求头名称 Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String string = headerNames.nextElement(); log.info("请求头参数:"+string); } log.info("请求的IP地址为: " + getRealIp(request)); } private static String getRealIp(HttpServletRequest request) { // 这个一般是Nginx反向代理设置的参数 String ip = request.getHeader("X-Real-IP"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } // 处理多IP的情况(只取第一个IP) if (ip != null && ip.contains(",")) { String[] ipArray = ip.split(","); ip = ipArray[0]; } return ip; } } 

public class LoginController { @GetMapping("/login") @Login public ResponseEntity login(){ return ResponseEntity.ok("login success"); } } 

以上案例是使用注解的方式用AOP实现的登录拦截,发起请求需要带token的请求头,若不带token的请求头,就会被拦截,提示当前未登录.
程序运行结果

1.不带token请求

image
image

2.带token请求

image
image

以上是使用注解的方式实现AOP,AOP也可用Execution表达式实现,注解方式适合切割较为分散的,如果一大片还是需要用Excution表达式来实现.,有兴趣,可自行实现.

分享在写案列时候的一个Java的坑

在判断请求头时,最先使用了java中的isEmpty()判空方法,结果异常的时候直接报500,不抛出定义的异常信息,最后发现,Java的isEmpty()方法判断是length长度,而判断的值可能是null.
image
image

isEmpty()方法的源码

image

我在以上案例使用判空方法是一个开源的工具包,有兴趣可以了解一下,附上链接tools-common.

原文链接:https://yq.aliyun.com/articles/708894
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章