AOP(Aspect Oriented Programming)
1.何为AOP(面向切面编程)?
是一种编程思想,通过预编译方式和动态代理实现程序功能的统一维护技术,是spring框架中的一个重要内容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑的耦合度降低,可以提高程序的可重用性,同时提高开发效率,是OOP思想的扩展。
应用场景:主要使用在对一些公共操作进行统一的处理,例如登录校验、权限控制、统一日志记录、统一返回格式、统一一次处理等。
2.AOP底层原理?
AOP底层使用的是动态代理,若类为接口实现类,默认使用JDK动态代理,否则使用CGLIB动态代理。
关于代理类的生成时期:
- 编译期:在代码编译时,将代理代码织入到目标类中。
- 类加载期:当目标类被加载时,修改目标类的字节码。
- 运行期:当程序运行的某一时刻时,为目标对象创建一个代理对象。Spring AOP采用此种方式织入切面。
3.AOP的组成
- 连接点(JoinPoint):目标业务代码代码,可以是方法、异常处理等。
- 切点(Pointcut):定义AOP拦截规则(拦截哪些方法),对连接点进行筛选。
- 通知/增强(Advice):增强目标的业务逻辑部分(定义在连接点上应用的额外代码)。
- 切面(Aspect):定义AOP的业务类型(比如用于登录校验)。
3.1通知的种类
- 前置通知(前置通知会在执行目标方法之前调用)
- 后置通知(在执行正常方法返回后或抛出异常执后调用)
- 异常通知(当出现异常时,才会执行)
- 最终通知(在方法返回之后执行)
- 环绕通知(可在目标方法前/后执行,可获取修改目标方法的参数,返回值,还可以决定目标方法是否执行)
3.2案例演示
使用AOP实现对用户登录进行校验。
3.2.1引入框架
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.2.2定义连接点
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@RequestMapping("/login")
public Object login(HttpServletRequest request){
//生成session
HttpSession session = request.getSession(true);
log.info("用户session生成成功 id:"+session.getId());
return "登录成功";
}
//连接点
@RequestMapping("/udetail")
public Object getUser(HttpServletRequest request){
//验证登录~~~使用AOP
log.info("获取用户详细成功");
return "获取用户详细信息成功";
}
}
3.2.3定义切面
/**
* 定义切面,登录拦截
*/
@Component
@Aspect
@Slf4j
public class LoginAOP {
/**
* @Pointcut:定义切点
* 对UserController使用规则
* execution<修饰符>(返回类型 包.类.方法(参数)<异常>)
*/
@Pointcut("execution(* com.example.demo.controller.UserController.getUser(..))")
public void pointCut(){
}
//环绕通知
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint){
//判定用户是否登录
log.info("验证用户是否登录中···");
Object[] args = joinPoint.getArgs();
HttpServletRequest request = (HttpServletRequest) args[0];
HttpSession session = request.getSession(false);
if(session == null){
log.info("登录验证失败返回");
return "登录失败";
}
log.info("登录验证成功");
Object obj = null;
try {
//执行目标方法
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}
其他通知:@After(前置通知)、@Before(后置通知)、@AterReturning(最终通知)、@AfterThrowing(异常通知)
4.Spring MVC统一功能处理
使用传统的AOP来实现登录校验的功能还是比较繁琐的,为了简化开发,Spring MVC对AOP进行了进一步的封装,产生了拦截器。
使用Spring MVC拦截器的基本规则如下:
- 拦截器是基于Java的反射机制进行工作的,所以它能够拦截到声明了特定注解(如@Controller、@Service等)或特定路径(@RequestMapping等)的方法。
- 拦截器的执行顺序是按照配置文件中的顺序进行的,可以在配置文件中设定多个拦截器的执行顺序。例如,我们可以先执行一个权限验证的拦截器,然后再执行一个日志记录的拦截器。
- 拦截器可以在请求到达控制器之前和之后进行操作。拦截器是在DispatcherServlet之前执行的。当请求到达时,拦截器会先进行处理,然后再将请求传递给DispatcherServlet。所以,拦截器可以在请求到达控制器之前和之后进行操作。
4.1拦截器
4.1.1自定义拦截器
@Slf4j
@Component
public class LoginHandler implements HandlerInterceptor {
/**
* preHandle表示在请求到达控制器之前执行
* @param request
* @param response
* @param handler
* @return true:表示不需要拦截控制器的执行,后续执行控制器的业务逻辑;false:表示需要拦截控制器的执行
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if(session != null){
log.info("已经登录,运行执行控制层业务逻辑");
return true;
}
//未登录、权限验证失败
log.info("未登录,不允许执行控制层业务逻辑");
response.setStatus(401);
return false;
}
}
4.1.2将自定义拦截器添加到配置中
/**
* 添加Spring MVC拦截器
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginHandler loginHandler;
/**
* 将拦截器添加到系统配置中
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptorRegistration = registry.addInterceptor(loginHandler);
//添加URL拦截规则,相当于AOP的切点
//拦截所有的URL
interceptorRegistration.addPathPatterns("/**");
//排除某一个URL
interceptorRegistration.excludePathPatterns("/admin/login");
}
}
4.2统一异常处理
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e){
HashMap<String,Object> result = new HashMap<>();
result.put("code","-1");
result.put("message", "异常信息:"+e.getMessage());
return result;
}
}
@ControllerAdvice:用于为Controller提供一些全局的处理。
@ExceptionHandler:@ExceptionHandler注解可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常。
@ControllerAdvice+@ExceptionHandler:表示用于捕获Controller中抛出指定类型的异常,从而达到统一异常的处理。@ResponceBody,表示异常处理后返回的是数据,非视图。
4.3统一返回结果封装
@ControllerAdvice
public class ResultAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//如果结果已经被封装了,就不需要再进行处理了
if(body instanceof HashMap){
return body;
}
//没有则对响应进一步封装
HashMap hashMap = new HashMap();
hashMap.put("status", "1");
hashMap.put("msg", "");
hashMap.put("data",body);
return hashMap;
}
}
@ControllerAdvice+ResponseBodyAdvice接口,表示在Controller执行完成之后,response返回给客户端之前,执行的对response的一些处理,可以实现对response数据的一些统一封装或者加密等操作。
5.总结
AOP面向切面编程是一种编程思想,和OOP面向对象编程类型,主要实现的功能是对统一功能进行统一处理,提高了代码的复用率(开发效率提示)、业务解耦合。