目录
一、什么是AOP
AOP(Aspect Oriented Programming)面向切面思想,是Spring的三大核心思想之一(AOP-面向切面、IOC-控制反转、DI-依赖注入)
AOP,一般成为面向切面,作为面向对象OOP的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块之间的耦合度,提高了系统的可维护性。可用于权限认证,日志和事务处理.
二、为什么使用AOP
Java是一个面向对象(OOP)的编程语言,但它有个弊端就是当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为,这样做不便于维护,而且有大量重复代码,AOP的出现弥补了OOP的这点不足。
例如:
有多少个业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然用面向对象的思想,可以把这些重复的代码抽离出来,写成公共方法,就是下面这样
代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。 有没有更好的方式呢?有的,那就是AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中 。
动态代理 :
三、AOP的体系结构
AOP要做的三件事是:在哪里切入,什么时候切入,切入后做什么事
在哪里切入:就是权限校验等非业务操作在哪些业务代码中执行
什么时候切入:就是业务代码执行前还是执行后
切入后做什么事:比如做权限校验,日志记录等等
Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
Aspect:切面,即Pointcut和Advice。
Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
四、AOP原理
之前提到JDK代理和Cglib代理两种动态代理,优秀的Spring框架把两种方式在底层都集成了进去,无需担心自己去实现动态生成代理。那么Spring是如何生成代理对象的?创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib代理。然后从容器获取代理后的对象,在运行期植入"切面"类的方法。如果目标类没有实现接口,且class为final修饰的,则不能进行Spring AOP编程。
封装了JDK代理和Cglib代理。 当被代理的类实现了接口,则使用JDK代理。如果没有实现接口则使用Cglib代理。
类没有实现接口且使用final修饰 不能使用AOP.
五、AOP能做什么
Spring声明式事务管理配置 [事务管理]
Controller层的参数校验
使用Spring AOP实现MySQL数据库读写分离案例分析
在执行方法前,判断是否具有权限。
对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。
信息过滤,页面转发等等功能。
什么是事务?
有一系列动作组成的一个单元,这些动作要么全部执行,要么全部不执行。
例子: 张三给李四转账。[1] 张三减钱 [2]李四加钱。
mysql是支持事务的。
六、AOP案例-- 使用AOP模拟事务管理
6.1 创建一个Spring工程
6.2 引入相关的依赖
4.0.0 com.wjk java-11-11-AOP 1.0-SNAPSHOT org.springframework spring-webmvc 5.2.9.RELEASE org.springframework spring-aspects 5.2.9.RELEASE com.fasterxml.jackson.core jackson-databind 2.13.2.2 com.alibaba fastjson 1.2.75
6.3 写spring的配置文件。
6.4 创建service接口以及实现类
package com.wjk;public interface IUserService { public void save();}
package com.wjk.imp;import com.wjk.IUserService;import org.springframework.stereotype.Service;@Servicepublic class UserService implements IUserService { public void save() { System.out.println("保存用户"); }}
package com.wjk.imp;import com.wjk.IUserService;import org.springframework.stereotype.Service;@Servicepublic class OrderService { public void save() { System.out.println("保存订单"); }}
6.5 定义切面类
@Component //校验spring容器来创建该类对象。@Aspect //标记该类为切面类public class TransactionAop { @Pointcut("execution(* com.ykq.aop01.service.impl.*.*(..))") private void transationAdvice(){} @Before("transationAdvice()") public void before(){ System.out.println("前置通知---开启事务"); }}
6.6 测试
public class Test { public static void main(String[] args) { //读取spring配置文件 ApplicationContext app=new ClassPathXmlApplicationContext("spring.xml"); //该类使用了接口---使用JDK动态代理 IUserService userService = app.getBean(IUserService.class); userService.save(); //该类没有实现接口---切面使用cglib动态代理 OrderService orderService = app.getBean(OrderService.class); orderService.save(); }}
6.7 aop中的通知类型
package com.wjk.aspect;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component //校验spring容器来创建该类对象@Aspect //标记该类为切面类public class TransactionAop { @Pointcut("execution(* com.wjk.imp.*.*(..))") public void transationAdvice(){ }; @Before("transationAdvice()") public void before(){ System.out.println("前置处理--开启事务"); } @After("transationAdvice()") public void after(){ //理解为方法执行完毕 System.out.println("后置处理--提交事务"); } @AfterReturning("transationAdvice()") public void afterReturning(){ System.out.println("后置返回通知"); } @AfterThrowing("transationAdvice()") public void afterThrowing(){ System.out.println("异常处理,事务回滚"); } @Around("transationAdvice()") public void around(ProceedingJoinPoint joinPoint){ System.out.println("环绕通知前"); try { joinPoint.proceed();//回调连接点方法 System.out.println("环绕方法执行完毕"); } catch (Throwable throwable) { System.out.println("环绕通知-异常"); }finally { System.out.println("环绕通知-执行最终"); } }}
七、AOP案例--使用注解切点
在使用GetMapping注解的方法上,使用切面
7.1 创建Controller包
package com.wjk.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/aop")public class HelloController { @GetMapping("/getTest") public String getTest(){ System.out.println("执行了getTest"); return "getTest"; } @PostMapping("/postTest") public String postTest(){ System.out.println("执行了postTest"); return "postTest"; }}
7.2 定义切面
package com.wjk.aspect;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Component@Aspectpublic class LogAdvice { @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)") private void logAdvice(){} @Before("logAdvice()") public void before(){ System.out.println("添加getTest的前置日志"); }}
7.3 修改Spring配置文件
八、AOP案例--使用自定义注解
8.1 自定义注解
package com.wjk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(value = ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface MyPermission { String value() default "";}
8.2 使用自定义注解
package com.wjk.controller;import com.wjk.annotation.MyPermission;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;import java.util.List;@RestController@RequestMapping("/user")public class UserController { @RequestMapping("/list") @MyPermission("list") public String list(){ System.out.println("查询所有用户"); return "list"; } @PostMapping("/save") @MyPermission("save") public String save(){ System.out.println("添加用户"); return "save"; }}
8.3 定义切面
package com.wjk.aspect;import com.wjk.annotation.MyPermission;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.stereotype.Component;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;@Component@Aspectpublic class PermissionAdvice { //定义切点 @Pointcut("@annotation(com.wjk.annotation.MyPermission)") public void permission(){ } private List permission=new ArrayList(); public PermissionAdvice(){ permission.add( "list"); permission.add("delete"); permission.add("update"); } @Around("permission()") public Object before(ProceedingJoinPoint joinPoint) throws Throwable { //得到连接点执行的方法对象 MethodSignature signature= (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); //得到方法上的注解 MyPermission annotation = method.getAnnotation(MyPermission.class); if (annotation!=null){ //获取注解属性的value值 String value = annotation.value(); if (!permission.contains(value)){ return "quanxianbuzu"; } } return joinPoint.proceed(); }}