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通知结束
环绕通知里不仅可以获取到目标对象的所有信息,还能改变目标对象的入参与返回值,功能非常强大。