AOP - 2 实例(SpringBoot 注解方式)
1、创建Spring Boot项目
创建一个Spring Boot 项目,然后pom中引入web 模块与AOP相关依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
其中:
aspectjweaver是与aspectj相关的包,用来支持切面编程的;
aspectjweaver是aspectj的织入包;
2、实现一个web请求,数据通过接口获取(使用POJO类传参与返回值)
@RestController @RequestMapping("/aop") public class AopController { @Autowired private AopService aopService; @GetMapping(value = "getResult") public ResultVO sayHello(ParamVO paramVO) { ParamDTO paramDTO = new ParamDTO(); BeanUtils.copyProperties(paramVO, paramDTO); ResultDTO resultDTO = aopService.getResult(paramDTO); ResultVO resultVO = new ResultVO(); BeanUtils.copyProperties(resultDTO, resultVO); return resultVO; } }
列出一个POJO类,其他类似。返回给前端的统一使用VO,业务逻辑层之间的传递使用DTO,映射数据库的使用Domain:
public class ParamVO { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "ParamVO{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
3、web请求对应的接口
import cn.latiny.modules.aopone.model.ParamDTO; import cn.latiny.modules.aopone.model.ResultDTO; public interface AopService { ResultDTO getResult(ParamDTO param); }
@Service public class AopServiceImpl implements AopService { @Override public ResultDTO getResult(ParamDTO param) { ResultDTO result = new ResultDTO(); result.setId(1001); result.setMessage(param.toString()); result.setData(Arrays.asList("Latiny", "30", "20000")); return result; } }
4、定义一个切面类,实现对Service方法进行切面
把一个类变成切面类,需要两步:
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
/** * @description AopService切面类 */ @Aspect @Component public class AopServiceAspect { private final Logger logger = LoggerFactory.getLogger(AopService.class); }
(1) 定义一个切入点
这里我们对AopService的getResult() 方法进行切面编程。
/** * 定义一个切入点 */ @Pointcut("execution(* cn.latiny.modules.aopone.service.AopService.getResult(..))") public void pointCut() { }
(2) 定义一个前置通知
/** * 前置通知 * @param joinPoint * @param name */ @Before("pointCut() && args(name)") public void before(JoinPoint joinPoint, String name) { Object[] parameters = joinPoint.getArgs(); String methodName = joinPoint.getSignature().getName(); System.out.println(methodName + "方法 Before通知,方法的参数: " + name); }
我们通过注解@Before结合切入点完成对getResult() 方法的切面,在before() 方法里,我们可以在getResult() 执行前做任何想做的事。在这个方法里,我们通过JoinPoint可以得到对应切入点的目标对象的所有信息,类名,方法名,方法参数名以及传递的值。
(3) 其他通知的定义
/** * 后置通知,异常时不执行 * @param joinPoint * @param name */ @AfterReturning(value = "pointCut() && args(name)", returning = "result") public void afterReturning(JoinPoint joinPoint, String result, String name) { String methodName = joinPoint.getSignature().getName(); System.out.println(methodName + "方法 AfterReturning通知,方法的参数: " + name + ", 方法返回值:" + result); } /** * 后置通知,不管对应的连接点方法是否正常执行,都会执行此通知 * @param joinPoint * @param name */ @After(value = "pointCut() && args(name)") public void after(JoinPoint joinPoint, String name) { String methodName = joinPoint.getSignature().getName(); System.out.println(methodName + "方法 After通知,方法的参数: " + name); } /** * 环绕通知,在不修改原来方法的前提下,可以在方法执行前修改方法的入参,也可以在方法执行之后修改方法的返回值。 * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 * @param joinPoint * @return */ @Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) { System.out.println(joinPoint.getSignature().getName() + "方法 Around通知开始"); processInputArg(joinPoint.getArgs()); try { Object obj = joinPoint.proceed(); processOutputObj(obj); if(obj instanceof ResultDTO) { ResultDTO resultDTO = (ResultDTO) obj; System.out.println("修改后的返回值:" + resultDTO.toString()); } System.out.println(joinPoint.getSignature().getName() + "方法 Around通知结束"); return obj; } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println(joinPoint.getSignature().getName() + "方法 Around通知结束"); return null; } /** * 处理输入参数 */ private void processInputArg(Object[] args) { for(Object arg: args) { System.out.println("参数原来的值为:" + arg.toString()); if (arg instanceof ParamDTO) { ParamDTO param = (ParamDTO)arg; param.setAge(18); param.setName("Nowesiki"); } System.out.println("参数修改的值为:" + arg.toString()); } } /** * 返回值处理 * @param obj */ private void processOutputObj(Object obj) { if(obj instanceof ResultDTO) { ResultDTO result = (ResultDTO) obj; result.setId(1002); result.setMessage(result.getMessage()); result.setData(Arrays.asList("Nowesike", "40", "1000000000")); } }
5、测试
这里测试一下环绕通知,其他的通知暂时注释掉,启动项目之后,直接在浏览器访问AopController接口即可。
这里前端传的参数是,name = 马云,age = 44
最后的结果是:
getResult方法 Around通知开始 参数原来的值为:ParamVO{name=马云, age=44} 参数修改的值为:ParamVO{name=Nowesiki, age=18} 修改后的返回值:ResultDTO{id=1002, message='ParamVO{name=Nowesiki, age=18}', data=[Nowesike, 40, 1000000000]} getResult方法 Around通知结束
环绕通知里不仅可以获取到目标对象的所有信息,还能改变目标对象的入参与返回值,功能非常强大。