SpringBoot使用AOP
众所周知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请求
2.带token请求
以上是使用注解的方式实现AOP,AOP也可用Execution表达式实现,注解方式适合切割较为分散的,如果一大片还是需要用Excution表达式来实现.,有兴趣,可自行实现.
分享在写案列时候的一个Java的坑
在判断请求头时,最先使用了java中的isEmpty()判空方法,结果异常的时候直接报500,不抛出定义的异常信息,最后发现,Java的isEmpty()方法判断是length长度,而判断的值可能是null.
isEmpty()方法的源码
我在以上案例使用判空方法是一个开源的工具包,有兴趣可以了解一下,附上链接tools-common.
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java实现的公网映射内网端口的开源工具
What is holer Holer exposes local servers behind NATs and firewalls to the public internet over secure tunnels. Support forwarding message based on TCP protocol. Holer是一个将局域网中的应用映射到公网访问的端口映射软件,支持转发基于TCP协议的报文。 How it works 1. Holer使用 1.1. 安装 Java 安装Java 1.7或者更高版本;执行命令 java -version 检查Java是否可用。 1.2. 安装Web服务端 以Tomcat为例,安装并启动Tomcat 在浏览器里输入如下URL来检查Tomcat服务是否可以正常访问: http://127.0.0.1:8080 1.3. 配置Holer 下载并解压软件包holer-client.zip 修改配置文件:holer-client/conf/holer.conf 设置HOLER_ACCESS_KEY如下: HOLER_ACCESS_KEY=HO...
- 下一篇
自己实现一个JDK动态代理
回顾JDK代理 Spring AOP 用到了两种动态代理模式:JDK动态代理和CGLIB动态代理,两种动态代理形成互补。今天我们来尝试纯手写一个简版的JDK动态代理,来了解它的底层实现原理。我们先来回顾一下JDK动态代理 动态代理的条件 两个角色: 代理对象,被代理对象 代理对象需要完成被代理对象的需要完成的业务操作 代理对象持有被代理对象的引用 JDK动态代理 被代理对象必须实现接口,CGLIB动态代理被代理类和方法不能用final修饰 实现代码 被代理对象实现的目标接口 package com.nqmysb.proxy.jdk; /** * 目标接口 * @author liaocan * */ public interface Subject { /* * 抽象业务方法 */ void businessMethod(); } 被代理的目标对象 package com.nqmysb.proxy.jdk.impl; import com.nqmysb.proxy.jdk.Subject; /** * 具体的目标对象,实现目标接口的方法 * @author liaocan * */ p...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Hadoop3单机部署,实现最简伪集群