Spring自定义AOP注解

本文以验证用户是否登录为例,主要介绍如何自定义一个AOP注解并解析

功能:用户如果已登录,在http请求头中会包含Authorization头,这个字段内存着用户登录后服务端分配的token,服务端会对Controller中被@CheckLogin修饰的接口进行登录验证

定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CheckLogin {
}

@Target({ElementType.METHOD}):标识此注解用于方法上
@Retention(RetentionPolicy.RUNTIME):标识此注解运行时有效
@Inherited:标识如果方法所在的类被子类继承,此方法同时会继承此注解

如果未标识@Inherited,会导致后续生成CGLIB代理时拿不到注解

定义注解BeanPostProcessor

  1. 定义一个名为CheckLoginPostProcessor的类,继承于BeanPostProcessor
  2. 重写postProcessBeforeInitializationpostProcessAfterInitialization方法

代码如下:

@Component
public class CheckLoginPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
	// 在前置初始化操作中不需要操作,直接返回
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
	// 获取bean中所有的方法
        var methods = bean.getClass().getMethods();
        CheckLogin annotation = null;
        for (var m: methods) {
	    // 获取方法中的@CheckLogin注解,如果不存在返回null
            annotation = AnnotationUtils.getAnnotation(m, CheckLogin.class);
            if (annotation != null) break;
        }
	// 当前bean不包含CheckLogin注解,直接返回
        if (annotation == null) {
            return bean;
        }
	// 创建CGLIB操作对象
        var enhancer = new Enhancer();
	// 设置代理对象继承于bean
        enhancer.setSuperclass(bean.getClass());
	// 设置代理对象的方法回调,重新定义原方法的执行流程
        enhancer.setCallback(new CheckLoginInterceptor());
	// 创建对象
        Object proxy = enhancer.create();
        // 因为重新创建了代理对象,会导致Spring原先@Autowired的属性丢失,需要手工重新对所有属性值重新注入
        var fields = proxy.getClass().getSuperclass().getDeclaredFields();
        for (var f: fields) {
            try {
                var sourceField = bean.getClass().getDeclaredField(f.getName());
		// @Autowired一般都是private,通过反射读取值需要设置访问权限
                sourceField.setAccessible(true);
                f.setAccessible(true);
                f.set(proxy, sourceField.get(bean));
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
	// 返回代理对象
        return proxy;
    }

    private static class CheckLoginInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            if (method.getAnnotation(CheckLogin.class) == null) {
                return methodProxy.invokeSuper(o, objects);
            }
            var attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            var token = attribute.getRequest().getHeader("Authorization");
            if (token.equals("")) {
		// 验证返回的类型,最好要和原方法相同的返回类型
                return Result.failed("用户未登录", -25);
            }
            var loginService = (LoginService)ApplicationContextUtil.getApplicationContext().getBean("loginService");
            var isLogin = loginService.isLogin(token);
            if (isLogin) {
                return methodProxy.invokeSuper(o, objects);
            }
            return Result.failed("用户未登录", -25);
        }
    }
}

使用

在需要验证登录的接口处,加上@CheckLogin注解即可

@PostMapping("/api/post/save")
@CheckLogin
public Result<String> savePost(@RequestBody PostDTO post) {
    this.service.savePost(post);
    return Result.success("save success");
}

posted on 2020-09-07 12:26  Kakura  阅读(560)  评论(0编辑  收藏  举报

导航