本文以验证用户是否登录为例,主要介绍如何自定义一个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
- 定义一个名为
CheckLoginPostProcessor
的类,继承于BeanPostProcessor - 重写
postProcessBeforeInitialization
和postProcessAfterInitialization
方法
代码如下:
@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");
}