一、概述
AOP是Spring框架的重要特性。
通知类型有:前置通知、后置最终通知、后置返回通知、后置异常通知、环绕通知
二、添加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
三、创建Aspect切面类
在com.example.aop包下创建切面类WebAspect
package com.example.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; @Component @Aspect public class WebAspect { /** * 切入点 * 匹配com.example.controller1包及其子包下的所有类的所有方法 */ @Pointcut("execution(* com.example.controller1..*.*(..))") public void executePackage(){ } /** * 前置通知,目标方法调用前被调用 * @param joinPoint */ @Before("executePackage()") public void beforeAdvice(JoinPoint joinPoint){ System.out.println("- - - - - 前置通知 - - - - -"); Signature signature = joinPoint.getSignature(); System.out.println("返回目标方法的签名:"+signature); System.out.println("代理的是哪一个方法:"+signature.getName()); Object[] obj = joinPoint.getArgs(); System.out.println("获取目标方法的参数信息:"+Arrays.asList(obj)); } /** * 后置最终通知,目标方法执行完执行 */ @After("executePackage()") public void afterAdvice(){ System.out.println("- - - - - 后置最终通知- - - - -"); } /** * 后置返回通知 * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息 * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数 * returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行 * @param joinPoint * @param keys */ @AfterReturning(value = "execution(* com.example.controller1..*.*(..))",returning = "keys") public void afterReturningAdvice(JoinPoint joinPoint,String keys){ System.out.println("- - - - - 后置返回通知- - - - -"); System.out.println("后置返回通知 返回值:"+keys); } /** * 后置异常通知 * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法; * throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行, * @param joinPoint * @param exception */ @AfterThrowing(value = "executePackage()",throwing = "exception") public void afterThrowingAdvice(JoinPoint joinPoint,NullPointerException exception){ System.out.println("- - - - - 后置异常通知 - - - - -"); } /** * 环绕通知: * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 */ @Around("execution(* com.example.controller1.AopController1.testAround(..))") public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("- - - - - 环绕通知 - - - -"); System.out.println("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName()); try {//obj之前可以写目标方法执行前的逻辑 Object obj = proceedingJoinPoint.proceed();//调用执行目标方法 return obj; } catch (Throwable throwable) { throwable.printStackTrace(); }finally { System.out.println("- - - - - 环绕通知 end - - - -"); } return null; } }
1.@Pointcut是创建切入点
切入点方法不用写代码,返回类型为void
execution:用于匹配表达式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
- 修饰符匹配(modifier-pattern?)
- 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
- 类路径匹配(declaring-type-pattern?)
- 方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
- 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
- 异常类型匹配(throws-pattern?) 其中后面跟 着“?”的是可选项
1)execution(* *(..)) //表示匹配所有方法 2)execution(public * com. example.controller.*(..)) //表示匹配com. example.controller中所有的public方法 3)execution(* com. example.controller..*.*(..)) //表示匹配com. example.controller包及其子包下的所有方法
2.JoinPoint
除@Around外,每个方法里都可以加或者不加参数JoinPoint。JoinPoint包含了类名、被切面的方法名、参数等属性。@Around参数必须为ProceedingJoinPoint。
四、创建Controller类
在com.example.controller1包下创建Controller类AopController1。
package com.example.controller1; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/aop1") public class AopController1 { @RequestMapping("/test") public String testAop(String key){ return "key="+key; } @RequestMapping("/testAfterThrowing") public String testAfterThrowing(String key){ throw new NullPointerException(); } @RequestMapping("/testAround") public String testAround(String key){ return "key="+key; } }
五、创建测试类
当然,也可以启动项目,直接在浏览器访问路径看效果,不用创建测试类。
在src/test/java下建测试类
在com.example.controller1下建AopController1Test测试类
package com.example.controller1; import com.example.SpringWebThyApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringWebThyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @WebAppConfiguration public class AopController1Test { @Autowired private AopController1 aopController1; @Test public void testAop() { this.aopController1.testAop("mykey"); } @Test public void testAfterThrowing() { this.aopController1.testAfterThrowing("mykey"); } @Test public void testAround() { this.aopController1.testAround("mykey"); } }
六、测试结果
1.执行第一个测试方法testAop()
2.执行第二个测试方法testAfterThrowing()
3.执行第三个测试方法testAround()
七、自定义注解
对于特定的方法执行时需要经过通知,可以在方法上加注解切面。
1.创建WebDesc注解
package com.example.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface WebDesc { String describe() default "mydesc"; }
2.创建Aspect切面类
在com.example.aop包下创建切面类WebAspectAnno
package com.example.aop; 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.springframework.stereotype.Component; @Component @Aspect public class WebAspectAnno { /** * 切入点 * 用注解 */ @Pointcut("@annotation(com.example.aop.WebDesc)") public void executeAnnotation(){ } @Before("executeAnnotation()") public void beforeAdviceAnnotation(){ System.out.println("- - - - - 前置通知 annotation - - - - -"); } @Around("@annotation(webDesc)") public Object aroundAnnotation(ProceedingJoinPoint proceedingJoinPoint, WebDesc webDesc){ System.out.println("- - - - - 环绕通知 annotation - - - -"); //获取注解里的值 System.out.println("注解的值:" + webDesc.describe()); try {//obj之前可以写目标方法执行前的逻辑 Object obj = proceedingJoinPoint.proceed();//调用执行目标方法 System.out.println("- - - - - 环绕通知 annotation end - - - -"); return obj; } catch (Throwable throwable) { throwable.printStackTrace(); } return null; } }
3.创建Controller
在com.example.controller2包下创建Controller类AopController1。
package com.example.controller2; import com.example.aop.WebDesc; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/aop2") public class AopController2 { @RequestMapping("/testAnnotation") @WebDesc(describe = "This is testAnnotation Controller") public String testAnnotation(String key){ return "key="+key; } }
4.创建测试类
package com.example.controller2; import com.example.SpringWebThyApplication; import com.example.controller1.AopController1; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringWebThyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @WebAppConfiguration public class AopController2Test { @Autowired private AopController2 aopController2; @Test public void testAnnotation() { this.aopController2.testAnnotation("mykey"); } }