AOP开发详解
这里我们主要采用@AspectJ的注解方式讨论AOP的开发,因为Spring AOP只能对方法进行拦截,所以I首先要确定需要拦截什么方法,让它能织入约定的流程中。
确定连接点
任何AOP编程,首先确定的是在什么地方需要AOP,也就是需要确定连接点(什么类的什么方法)的问题。现在我们假设有一个UserService接口,它有一个printUser方法。
UserService接口
package com.lay.springboot_aop.aspect.service;
import com.lay.springboot_aop.aspect.pojo.User;
public interface UserService {
public void printUser(User user);
}
UserServiceImpl实现类
package com.lay.springboot_aop.aspect.service.impl;
import org.springframework.stereotype.Service;
import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Override
public void printUser(User user) {
if(user==null) {
throw new RuntimeException("参数为空");
}
System.out.println("id = "+user.getId());
System.out.println("name = "+user.getUserName());
System.out.println("message = "+user.getMessage());
}
}
User实体类
package com.lay.springboot_aop.aspect.pojo;
public class User {
public Integer id;
public String userName;
public String message;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
这样一个普通的服务的接口和实现类就实现了。
下面我们将以printUser方法为连接点,进行AOP编程
开发切面
有了连接点,我们还需要一个切面,通过它可以描述AOP的信息和流程的织入。下面我们创建一个切面类
MyAspect切面类
package com.lay.springboot_aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.validator.UserValidator;
import com.lay.springboot_aop.aspect.validator.impl.UserValidatorImpl;
@Aspect
public class MyAspect {
@Before("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
public void before() {
System.out.println("before-----------");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before-------------");
jp.proceed();
System.out.println("around after-------------");
}
@After("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
public void after() {
System.out.println("after------------");
}
@AfterReturning("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
public void afterReturning() {
System.out.println("afterReturning------------");
}
@AfterThrowing("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
public void afterThrowning() {
System.out.println("afterThrowning------------");
}
}
这里解释一下。首先Spring是以@Aspect注解作为切面声明的,让Spring知道这是一个切面,然后我们就可以通过各类注解来定义各类的通知了,例如@Before、@After 、@AfterReturning和 @AfterThrowing等几个注解。
Around环绕通知
环绕通知(Around)是所有通知中最为强大的通知,强大意味着难以控制。一般而言,使用它的场景是你需要大幅度修改原有目标对象的服务逻辑时,否则都尽量使用其他的通知。
环绕通知:取代原有目标对象方法的通知,提供了回调原有目标对象方法的能力。
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before-------------");
jp.proceed();
System.out.println("around after-------------");
}
它拥有一个ProceedingJoinPoint类型的参数。这个参数的对象有一个proceed()方法,通过这个方法可以回调原有目标对象的方法。
切点定义
在切面的定义中,我们看到例如@Before、@After 、@AfterReturning和 @AfterThrowing等几个注解,它们会定义一个正则表达式,这个正则式的作用就是定义什么时候启用AOP,毕竟不是所有的功能都是需要启动AOP的,也就是Spring会通过这个正则式去匹配确定对应的方法(连接点)是否启动切面编程。上面我们在每个注解后都重复写了同一个正则式,显然是比较冗余。为了克服这个问题,Spring定义了切点(Pointcut)的概念。
切点(Pointcut):向Spring描述哪些类的哪些方法需要启动AOP编程。
有了切点的概念,我们可以做如下修改
MyAspect切面类
package com.lay.springboot_aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.validator.UserValidator;
import com.lay.springboot_aop.aspect.validator.impl.UserValidatorImpl;
@Aspect
public class MyAspect {
@Pointcut("execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before(JoinPoint point ,User user) {
System.out.println("before-----------");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before-------------");
jp.proceed();
System.out.println("around after-------------");
}
@After("pointCut()")
public void after() {
System.out.println("after------------");
}
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("afterReturning------------");
}
@AfterThrowing("pointCut()")
public void afterThrowning() {
System.out.println("afterThrowning------------");
}
}
代码中,使用了注解@Pointcut来定义切点,它标注在pointCut方法上,则在后面的通知注解中就可以使用方法名称来定义了。
正则表达式
我们对正则式简单了解一下,首先我们看下面的正则式
execution(* com.lay.springboot_aop.aspect.service.impl.UserServiceImpl.printUser(..))
其中
- execution:表示在执行的时候,拦截里面的正则匹配的方法;
- *:表示任意返回类型的方法
- com.lay.springboot_aop.aspect.service.impl.UserServiceImpl:指定目标对象的全限定名
- printUser:指定目标对象的方法
- (…):任意参数
这样Spring通过正则式知道你需要对类UserServiceImpl的printUser方法进行AOP增强。
对于这个正则式,我们还可以使用AspectJ的指示器。
项目类型 | 描述 |
---|---|
arg() | 限定连接点方法参数 |
@args() | 通过连接点方法参数上的注解进行限定 |
execution() | 用于匹配连接点的执行方法 |
this() | 限定连接点匹配AOP代理Bean引用为指定的类型 |
target | 目标对象(即被代理对象) |
@target() | 限定目标对象的配置了指定的注解 |
within | 限定连接点匹配指定的类型 |
@within() | 限定连接点带有匹配注解类型 |
@annotation() | 限定带有指定注解的连接点 |
测试AOP
完成了连接点、切面和切点定义,接下来我们可以进行测试AOP。
我们创建一个用户控制器(UserController)
UserController
package com.lay.springboot_aop.aspect.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lay.springboot_aop.aspect.pojo.User;
import com.lay.springboot_aop.aspect.service.UserService;
import com.lay.springboot_aop.aspect.validator.UserValidator;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService=null;
@RequestMapping("/print")
@ResponseBody
public User printUser(Integer id,String userName,String message) {
User user=new User();
user.setId(id);
user.setUserName(userName);
user.setMessage(message);
userService.printUser(user);
return user;
}
}
这里通过自动注入UserService服务接口,然后使用它进行用户信息的打印。方法标注了@ResponseBody,所以最后Spring MVC会将其转换为JSON响应请求(如果控制器是注解@RestController包含了@ResponseBody)。
启动类SpringbootAopApplication
package com.lay.springboot_aop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.lay.springboot_aop.aspect.MyAspect;
@SpringBootApplication(scanBasePackages= {"com.lay.springboot_aop.aspect"})
public class SpringbootAopApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAopApplication.class, args);
}
@Bean(name="myAspect")
public MyAspect initMyAspect() {
return new MyAspect();
}
}
然后启动程序,打开浏览器输入请求地址:http://localhost:8080/user/print?id=1&userName=username&message=men
查看控制台打印,就可以看到方法被织入到对应的流程中了。
转载自 https://blog.youkuaiyun.com/Sadlay/article/details/83478087