本小节对Spring中的AOP技术进行相应的总结与介绍。
Spring是一个轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。
日志记录,性能统计,安全控制,事务处理,异常处理等等。
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
采用横向抽取机制,取代了传统纵向继承体系重复性代码。
切入点就是在类里边可以有很多方法被增强,比如实际操作中,只是增强了个别方法,则定义实际被增强的某个方法为切入点。
通知/增强就是指增强的逻辑,比如扩展日志功能,这个日志功能称为增强。
切面就是把增强应用到具体方法上面的过程称为切面。
Spring的AOP技术底层使用了动态代理来实现,在SpringBoot中,针对AOP技术的实现有 Aspect,拦截器和过滤器 。本篇博文,我们主要介绍常用的Aspect和拦截器实现方式。
在 再谈Spring(一):Bean的作用域 文章中我们搭建了一个简单的SpringBoot项目,这里我们继续复用该项目来进行AOP技术的演示。
在上一小节的SpringBoot项目中,我们首先是添加aop所需的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
复制代码
然后创建一个切面类,如下所示:
WebAcpect:
package com.ywq.interceptor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* created by yanwenqiang on 2019-11-30
* 定义一个切面类
*/
@Aspect
@Component
public class WebAcpect {
/**
* 定义切入点,切入点为com.ywq.controller.TestController下的所有函数
*/
@Pointcut("execution(* com.ywq.controller.TestController.*(..))")
public void acpectMethod(){}
/**
* 前置通知:在连接点之前执行的通知
* @param joinPoint
* @throws Throwable
*/
@Before("acpectMethod()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
System.out.println("URL:" + request.getRequestURL().toString());
System.out.println("方法:" + request.getMethod());
System.out.println("类方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("参数 : " + Arrays.toString(joinPoint.getArgs()));
}
}
复制代码
在切面类中,我们需要通过注解@Aspect来标注这是一个切面类,然后通过注解@Component来表明这是一个Bean,然后我们需要配置切入点。配置方式如下:
使用表达式配置切入点,常用的表达式:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
比如说,匹配所有save开头的方法可以这么配置 execution(* save*(..))
本例中,我们仅仅展示前置通知,其余通知也是一样的。
package com.ywq.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* created by yangwenqiang on 2019-11-30
*/
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping(value = "/scopeTest", method = RequestMethod.GET)
public String testScope(HttpServletRequest request,
@RequestParam(value = "name") String name) {
return "Hello, " + name;
}
}
复制代码
接下来,我们启动SpringBoot服务,如下所示:
结果如下:
这样,我们就使用Aspect来完成了面向切面编程,在业务逻辑之外增加了一些功能模块逻辑。
但是,使用Aspect来实现AOP,尤其是对Controller层的接口进行拦截,看起来有点怪怪的。其实,在SpringBoot中,我们一般使用拦截器Interceptor来实现面向接口编程,实现拦截请求,增加相应的功能模块。
SpringBoot中的拦截器可以通过继承抽象类HandlerInterceptorAdapter 或者实现接口HandlerInterceptor 来实现。
在抽象类和接口中,有如下的三个方法:
接下来,我们看下JDK中HandlerInterceptor 的介绍:
接下里,我们首先看使用抽象类 HandlerInterceptorAdapter 来实现拦截器:
AuthInterceptor:
package com.ywq.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* created by yangwenqiang on 2019-11-30
* 继承抽象类 HandlerInterceptorAdapter 实现拦截器
*/
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 接口执行前先执行该方法,false表示拦截,true表示放行
String name = request.getParameter("name");
System.out.println("参数中的名字:" + name);
return true;
}
}
复制代码
然后还需要一个配置类 AuthConfig 如下:
package com.ywq.config;
import com.ywq.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AuthConfig implements WebMvcConfigurer {
// 注入拦截器
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(authInterceptor).addPathPatterns("/test/scopeTest");
}
}
复制代码
这样,我们就使用 HandlerInterceptorAdapter 实现了一个拦截器,启动SpringBoot 服务,在浏览器中输入http://localhost:8632/test/scopeTest?name=ywq,就可以看到结果如下:
接下来,我们再来看下使用接口HandlerInterceptor来实现拦截器:
package com.ywq.interceptor;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class CheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 接口执行前先执行该方法,false表示拦截,true表示放行
String name = request.getParameter("name");
System.out.println("[CheckInterceptor] - 参数中的名字:" + name);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
复制代码
package com.ywq.config;
import com.ywq.interceptor.CheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* *
* * created by yangwenqiang on 2019-11-30
* * 这是一个配置类
*/
@Configuration
public class CheckConfig extends WebMvcConfigurationSupport {
// 注入拦截器
@Autowired
private CheckInterceptor checkInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(checkInterceptor);
super.addInterceptors(registry);
}
}
复制代码
启动SpringBoot 服务,在浏览器中输入http://localhost:8632/test/scopeTest?name=yangwenqiang,就可以看到结果如下:
一般情况下,我们会首选抽象类 HandlerInterceptorAdapter 来实现拦截器,因为可以按需进行方法的覆盖。如上我们就完成了拦截器的创建,当然如果你嫌弃AuthConfig和CheckConfig比较麻烦的话,可以在启动类上完成拦截器的注册,如下所示:
package com.ywq;
import com.ywq.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* created by yangwenqiang on 2019-11-30
*/
@SpringBootApplication
@ServletComponentScan
public class StartApplication implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
// SpringBoot服务的启动入口
public static void main(String[] args) {
SpringApplication.run(StartApplication.class,args);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(authInterceptor).addPathPatterns("/test/scopeTest");
}
}
复制代码
本小节中,我们主要阐述了AOP面向切面编程的思想以及其具体实现方式。总结下来就是 Aspect方式 实现的AOP一般用在 给Service方法或者业务逻辑方法 上进行相应的增加; 拦截器主要是对Web层Controller接口 进行拦截。
本小节涉及到的 完整项目代码 详见: 我的GitHub
在后续的章节中,我会持续更新在使用Spring过程中使用到的一些小知识点,希望大家可以关注~
如果对你有帮助,记得点赞哈,欢迎大家关注我的博客,关注公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~