spring boot之自定义的注解的切面编程


1. 前言

  • 在上一章中我们知道了,注解的本质其实就是一个标签。但是我们可以通过反射或其他方式从注解的宿主对象上获取到注解对象信息,这就让注解的可玩性变得很强。可以通过注解来描述宿主的信息,可以通过拦截器为拥有特定的注解的方法、域做特殊处理,可以用来做切面编程。
  • 这也就是我们今天的重点,自定义的注解的切面编程。通过简单的例子来看一下,我们可以通过注解来更进一步的做什么。
  • 什么是切面编程我这里就不详述了,下面在使用时会根据需要简单介绍。

2. spring boot下自定义的注解的切面编程实现

  1. 创建一个springboot项目,项目结构如下。
    在这里插入图片描述
  2. 引入aop依赖,这是切面编程的核心包
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
  1. 创建一个自定义注解SercurValidate
/**
 * 如果用户名,不合法,改为访客账户登录
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SercurValidate {
    String name() default  "visitor";
    String pwd() default "123456";
}

  1. 创建service方法,并添加SercurValidate注解。由于我们只是做个小demo,如果验证不合法,我们直接把访客的用户名密码返回到前台。
@Service("loginServiceImpl")
public class LoginServiceImpl implements LoginService {

    @SercurValidate(name="visitor",pwd = "123456")
    @Override
    public String login(String name,String password) {
        return "用户名:"+name+";密码:"+password;
    }
}
  1. controller
@RestController
@RequestMapping("/index")
public class IndexController {

    @Autowired
    LoginService loginService;

    @RequestMapping("/login")
    public String login(String name,String password){
        String info= loginService.login(name,password);
        System.out.println(info);
        return info;
    }

}
  1. 现在到了最重要的一步了,创建一个切面类实现我们的功能,直接上代码
@Aspect
@Component
public class SerurAspect {

    @Pointcut("execution(public * com.example.springboot.annotation.service..*(..))")
    public void login() {
    }



    @Before("login() && @annotation(sv)")
    public void demoBefore(JoinPoint joinPoint, SercurValidate sv) {
        System.out.println("@before 开始");
        System.out.println("注解参数:"+sv.name()+":"+sv.pwd());
        System.out.println("目标方法的参数对象:" + joinPoint.getArgs().toString());
        System.out.println("被代理的对象:" + joinPoint.getTarget());
        System.out.println("代理对象:" + joinPoint.getThis());
        System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
        System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
        System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
    }

    @After("login() && @annotation(sv)")
    public void demoAfter(JoinPoint joinPoint, SercurValidate sv) {
        //....
    }

    @AfterThrowing("login() && @annotation(sv)")
    public void demoAfterThrowing(JoinPoint joinPoint, SercurValidate sv) {
        //....
    }

    @Around("login() && @annotation(sv)")
    public Object demoAround(ProceedingJoinPoint pjp, SercurValidate sv) throws Throwable {

        System.out.println("@Around 开始校验。。。");
        Object[] args = pjp.getArgs();
        String username = (String) args[0];
        //其他校验太复杂了,就做点长度校验
        if (username != null && username.length() > 4 && username.length() < 20) {
            System.out.println("校验成功");
            return pjp.proceed();
        }
        System.out.println("校验失败,以访客登录");
        args[0] = sv.name();
        args[1] = sv.pwd();
        return pjp.proceed(args);
    }

}

6.1 什么是切面编程

  • 简单来说就是在我们正常的代码运行流程中动态的加塞一段代码。如上的我想为login方法添加日志,我可以直接把代码写在login方法里面,但我就要为所有需要日志的方法添加代码,那样太low了。但是通过切面编程我只需要把日志代码写在切面类里面,然后告诉切面类在什么时候(how)哪些方法(where) 添加这些代码。

6.2. @Aspect注解

  • 作用是表明这是一个切面类,springboot便会将SerurAspect 类实现为切面类。

6.2 @Pointcut注解与方法

  • 这涉及到@Pointcut的表达式,这边我只说这里作用。含义是:限定com.example.springboot.annotation.service包及其子包,只会为这些包下的方法添加切面方法。
  • 注解下面的login()方法没有实质的意思,是用来做@Pointcut的表达式的代称。具体用法下面会说明。

6.3

  • spring aop的切面编程为我们提供了四种切入的时机,在执行方法前,在执行方法后,在方法报出异常后,还有环绕方法执行。这在切面编程里的术语称之为通知,也就是我们上面说的how,在什么时候通知切面类执行切面方法。这四个时机spring aop提供了四个注解来支持,也就是 @Before , @After , @AfterThrowing , @Round
  • 注解里面的参数表达式的含义的就是在什么地方加入切面方法,在术语里面称之为 切入点,也就是我们上面说的where。这里面的表达式也是@Pointcut的表达式。

6.3.1 @Before 在执行方法前执行

  • 首先我们先说一下里面参数的表达式,我们看到里面有“login()”的字符串,没错,这就是指的我们前面定义的login方法,表示把之前关于com.example.springboot.annotation.service包的限定条件加入进来。你也可以不使用login()方法代指,可以用如下的写法代替

@Before(“execution(public * com.example.springboot.annotation.service…*(…)) && @annotation(sv)”)

  • 而后面的@annotation(sv)表示的是必须含有@SercurValidate 注解的方法,这就是另一个限定条件,sv要和下面的参数名一致。
  • 所以这里注解的全部含义就是:在com.example.springboot.annotation.service包及其子包下的含有@SercurValidate 注解的方法被执行前,先执行demoBefore切面方法。
  • 说明一下demoBefore参数,前面的JoinPoint 参数是固定的,它就是连接点对象,如上通过它我们能获取到目标方法的相关信息,而后一个参数就是我们的注解。

6.3.2 @After 在执行方法执行之后

  • 这里注解的含义就是:在com.example.springboot.annotation.service包及其子包下的含有@SercurValidate 注解的方法执行之后,先执行demoAfter切面方法。

6.3.3 @AfterThrowing 在执行方法执行之后

  • 这里注解的含义就是:在com.example.springboot.annotation.service包及其子包下的含有@SercurValidate 注解的方法报出异常之后,执行demoAfterThrowing切面方法。

6.3.4 @Around 环绕执行

  • 我们看到demoAround方法有点不一样,他的第一个参数是ProceedingJoinPoint类型,而且还有返回值。
  • 如下我们看一下环绕方法的基本模式
 	@Around("login() && @annotation(sv)")
    public Object demoAround(ProceedingJoinPoint pjp, SercurValidate sv) throws Throwable {
    		//执行方法前的操作
    		...
    		//执行方法
            return pjp.proceed();
			//执行方法后的操作
			.....
    }
  • 其实这就是代理模式,ProceedingJoinPoint 类里面多了一个proceed方法,该方法的用处就是执行目标方法,而目标方法的返回值也将通过demoAround的返回值返回给调用者。

ProceedingJoinPoint对象只能在@Around注解下的方法使用,如果放到@Before注解下的方法里,动态代理时会报出异常。

  • 现在demoAround里的逻辑就很清楚了,如果用户名通过校验,直接执行原方法,反之以SercurValidate注解里面的参数值为参数执行目标方法。
  1. 测试结果
    在这里插入图片描述

补充


  • 这一章的重点主要还是在自定义注解的进阶使用上,关于切面编程这里只是根据需要简单介绍。切面编程的内容还是很多的,后面会需要再花几章来学习。
  • 上一章:Spring 自定义注解

猜你喜欢

转载自blog.csdn.net/u014296316/article/details/88046354