AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等。其实说起aop大家都对其有一定的概念。今天主要是总结一下他代码中的实现方式,以方便后边查看使用。
说起AOP,首先我们需要先了解其中的几个概念:
1.通知(Advice)
AOP在特定的切入点上执行的增强处理,就是你想要的功能,也就是上面说的日志、事务、权限等。有before(前置),after(后置),afterReturning(最终),afterThrowing(异常),around(环绕),就是在上面这几中类型所实现的功能。比如在使用redis操作前打印日志。
2.连接点(JoinPoint)
就是spring允许你使用通知的地方,例如每个方法的前,后(两者都有也行),或抛出异常时,也就是说和方法有关的前后(抛出异常),都是连接点。Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器,如aspectJ。
3.切入点(Pointcut)
就是带有通知的连接点,在连接点的基础上来定义切入点,假如一个类里面有5个方法,那就有5个连接点,但是并不想在所有方法上都实现切面的功能,只是想让其中的几个方法在调用之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。在程序中主要体现为书写切入点表达式。
4.切面(Aspect)
通常是一个类,里面可以定义切入点和通知。通知说明了干什么和什么时候干(之前,之后,还是异常),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
5.引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。就是把我们定义的功能用到目标类中。
6.目标(target)
包含连接点的对象。也被称作被通知或被代理对象。就是上面引入中所提到的目标类。
7.代理(proxy)
AOP框架创建的对象,代理就是目标对象的加强版本。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。
8.织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。
下面我们用注解的方式实现下AOP的功能。
增加AOP所需要的依赖包
<!--AOP依赖包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
定义一个切面类。
package com.example.demo.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component //让spring能够扫描到 @Aspect //定义这是一个切面类 public class LogAspect { /** * modifier-pattern:用于匹配public、private等访问修饰符 * ret-type-pattern:用于匹配返回值类型,不可省略 * declaring-type-pattern:用于匹配包类型 * modifier-pattern(param-pattern):用于匹配类中的方法,不可省略 * throws-pattern:用于匹配抛出异常的方法 * * * 多个表达式之间使用连接符匹配多个条件, 如使用||表示“或”,使用 &&表示“且” */ @Pointcut("@annotation(com.example.demo.annotation.LogAop) &&" + "execution(public * com.example.demo.service.impl.AopTestServiceImpl.get*(..))") public void log(){ } /** * 匹配com.example.demo.service.impl包下所有类下的方法名以update结尾、参数类型不限的public方法 */ @Pointcut("execution(public * com.example.demo.service.impl.*.*update(..))") public void say(){ } @Order(2) @Before("log()") public void beforeLog2(){ System.out.println("后执行,增加log()方法..."); } @Order(1) // Order 代表优先级,数字越小优先级越高 @Before("log()||say()") //多个的话用 @Before("log()||say()") public void beforeLog1(){ System.out.println("先执行,增加log()方法..."); } /** * Advice注解一共有五种,分别是: * 1.@Before前置通知 * 前置通知在切入点运行前执行,不会影响切入点的逻辑 * 2.@After后置通知 * 后置通知在切入点正常运行结束后执行,如果切入点抛出异常,则在抛出异常前执行 * 3.@AfterThrowing异常通知 * 异常通知在切入点抛出异常前执行,如果切入点正常运行(未抛出异常),则不执行 * 4.@AfterReturning返回通知 * 返回通知在切入点正常运行结束后执行,如果切入点抛出异常,则不执行 * 5.@Around环绕通知 * 环绕通知是功能最强大的通知,可以在切入点执行前后自定义一些操作。环绕通知需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行 */ @Before("say()") public void beforeSay(){ System.out.println("增加say()方法..."); } }
此类就是一个切面,其中一共定义了两个切入点,一个是log(),一个是say()。在满足切入点要求的方法前会做三中类型的通知,也就是beforeLog21(),beforeLog2(),beforeSay()。为什么是方法前呢,因为用的注解是@Before。其他具体的信息,可以看代码里面的注释。里面用到了一个我们自定义的注解,其代码如下:
package com.example.demo.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.CLASS) public @interface LogAop { /** * 使用@annotation、@within、@target、@args匹配注解 * //匹配标注有LogAop注解的方法 * @Pointcut("@annotation(com.example.demo.annotation.LogAop)") * public void matchAnno() {} * * //匹配标注有LogAop的类底下的方法,要求annotation的Retention级别为CLASS * @Pointcut("@within(com.example.demo.annotation.LogAop)") * public void matchWithin() {} * * //匹配标注有LogAop的类底下的方法,要求annotation的Retention级别为RUNTIME * @Pointcut("@target(com.example.demo.annotation.LogAop)") * public void matchTarget() {} * * //匹配传入的参数类标注有LogAop注解的方法 * @Pointcut("@args(com.example.demo.annotation.LogAop)") * public void matchArgs() {} */ }
然后就是我们的业务处理代码,也就是目标对象。
package com.example.demo.service.impl; import com.example.demo.annotation.LogAop; import com.example.demo.model.Student; import com.example.demo.service.GetStudentService; import org.springframework.stereotype.Service; /** * @Description: AOP测试 * @Author: haoqiangwang3 * @CreateDate: 2020/1/13 */ @Service public class AopTestServiceImpl implements GetStudentService { @LogAop @Override public Student getStudentInfo() { System.out.println("调用业务处理中的get()方法..."); return null; } @Override public int update() { System.out.println("调用业务处理中的update()方法..."); return 0; } }
再就是程序的入口,controlle方法
package com.example.demo.controller; import com.example.demo.service.impl.AopTestServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Description: AOP测试controller * @Author: haoqiangwang3 * @CreateDate: 2020/1/13 */ @RestController public class AopController { @Autowired private AopTestServiceImpl aopTestServiceImpl; @RequestMapping("/boot/getAop") public String aopGet(){ aopTestServiceImpl.getStudentInfo(); return "success"; } @RequestMapping("/boot/updateAop") public String aopUpdate(){ aopTestServiceImpl.update(); return "success"; } }
上面的代码是在我之前学习spring boot程序的基础上增加的。以上完成后,就可以测试我们的aop功能了,测试结果就不粘贴了,亲测是可以生效的。