优雅的使用token
创始人
2024-02-02 20:50:48

文章目录

  • 最初的代码
  • 方法带参数的切面代码
  • 方法不带参数的切面代码
  • 继承父类的方法
  • 继承父类的方法优化,防止拿错token

最初的代码

@RestController
@RequestMapping("api")
public class TestController {@AutowiredMyService myService;@GetMapping("test")public String test(HttpServletRequest request){String result = myService.test(request);return result;}
}
@Service
public class MyService {public String test(HttpServletRequest request){String token = request.getHeader("token");System.out.println(token);//验证token,处理业务逻辑return "success";}
}

在方法中通过HttpServletRequest获取头信息中的token,然后进行验证,之后再做业务逻辑处理。乍一看没什么问题,但是写多了就觉得这么写很麻烦,每个接口都要多这么一个不必要的参数,能不能处理一下呢?

方法带参数的切面代码

这时候想起来了,以前学Spring的时候不就说过吗,处理这种和业务无关的大量重复劳动,放在切面里不就好了吗。但回头一想,Service里的方法也不是每个都需要验证token,干脆写个注解,用切面来处理加了注解的方法


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedToken {
}

切面要做的很简单,通过SpringMvc提供的RequestContextHolder获取当前的HttpServletRequest请求,然后再取出header中的token

这时候第一个问题来了,我在切面获取到了token以后,怎么传递给Service中调用的方法呢?回想到在切面中可以获取方法的参数,然后动态修改参数的值就可以了。修改Service方法的参数,去掉烦人的HttpServletRequest,添加一个String类型的参数,用于接收token。

@Service
public class MyService {@NeedTokenpublic String test(String token){System.out.println(token);//验证token,处理业务逻辑return "success";}
}

切面实现如下:

@Aspect
@Component
public class TokenAspect {@Pointcut("@annotation(com.cn.hydra.aspectdemo.annotation.NeedToken)")public void tokenPointCut() {}@Around("tokenPointCut()")public Object doAround(ProceedingJoinPoint point) throws Throwable {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String token = request.getHeader("token");Object[] args = point.getArgs();Signature signature = point.getSignature();MethodSignature methodSignature = (MethodSignature) signature;String[] paramName = methodSignature.getParameterNames();List paramNameList = Arrays.asList(paramName);if (paramNameList.contains("token")){int pos = paramNameList.indexOf("token");args[pos]=token;}Object object = point.proceed(args);return object;}
}

切面中做了下面几件事:

  • 定义切点,在加了@NeedToken注解的方法织入逻辑

  • 通过RequestContextHolder获取HttpServletRequest ,获取header中的token

  • 通过MethodSignature 获取方法的参数列表,修改参数列表中的token的值

  • 使用新的参数列表调用原方法,这时候就把token传递给了方法


@RestController
@RequestMapping("api")
public class TestController {@AutowiredMyService myService;@GetMapping("test")public String test(){String result = myService.test(null);return result;}
}

说能解决问题,但是要多写一个null的参数,就让人就很难受了

方法不带参数的切面代码

声明全局变量

如果不通过传递参数的方式,有什么办法能把token传递给方法呢?这里灵机一动,可以通过切面获取方法属于的对象啊,有了对象就好办了,直接通过反射给某个属性注入值。再次修改Service声明一个全局变量,用于反射注入token使用

@Service
public class MyService{private String TOKEN;@NeedTokenpublic String test() {System.out.println(TOKEN);//验证token,处理业务逻辑return  TOKEN;}
}

修改切面实现方法:

@Around("tokenPointCut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {try {ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String token = request.getHeader("token");Field tokenField = point.getTarget().getClass().getDeclaredField("TOKEN");tokenField.setAccessible(true);tokenField.set(point.getTarget(),token);Object object = point.proceed();return object;} catch (Throwable e) {e.printStackTrace();throw e;}
}

注意这里不再去修改方法传入的参数,而是通过获取类的Field ,然后向当前对象的token对应的Field注入实际值来实现的。

继承父类的方法

写到这自我感觉良好了一会,但是写了几个类发现了我每个Service类都得多声明一个String类型的token全局变量啊,不光是麻烦,万一哪个类忘了写不就直接gg了,有没有什么更简便、安全的方法呢?

先定义一个父类,至于为什么使用父类而不是接口,原因就是接口中声明的变量是默认被final修饰的,所以是不能被改变的。

public class BaseService {public String TOKEN = null;
}

修改Service代码,继承BaseService类,删掉自己的TOKEN变量:

@Service
public class MyService extends BaseService {@NeedTokenpublic String test() {System.out.println(TOKEN);//验证token,处理业务逻辑return  TOKEN;}
}

反射的时候不能通过当前对象拿到父类中定义的变量,那我们就简单修改一下切面的代码:

@Around("tokenPointCut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {try {ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String token = request.getHeader("token");//Field tokenField = point.getTarget().getClass().getDeclaredField("TOKEN");Class baseClazz = point.getTarget().getClass().getSuperclass();Field tokenField = baseClazz.getDeclaredField("TOKEN");tokenField.setAccessible(true);tokenField.set(point.getTarget(),token);Object object = point.proceed();return object;} catch (Throwable e) {e.printStackTrace();throw e;}}

修改为通过当前对象获取父类,然后获取父类中的变量,再通过反射注入token值

继承父类的方法优化,防止拿错token

测试了几遍token获取都没啥问题,简直美滋滋。但是隔了一天突然发现不对啊,众所周知Spring的默认情况下Bean都是单例模式,并且全局变量的值任何一个线程都可能去改变。那么就存在情况,可能一个线程会拿到另一个线程修改后的token

重新定义父类,使用ThreadLocal保存token:

public class BaseService2 {public static ThreadLocal TOKEN= ThreadLocal.withInitial(() -> null);
}

修改Service:

@Service
public class MyService2 extends BaseService2 {@NeedTokenpublic boolean testToken(String name) {String token=TOKEN.get();boolean check = name.equals(token);System.out.println(name+"  "+token +"  "+check);return  check;}
}

修改切面:

@Around("tokenPointCut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {try {ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String token = request.getHeader("token");Class baseClazz = point.getTarget().getClass().getSuperclass();Field tokenField = baseClazz.getDeclaredField("TOKEN");ThreadLocal local = (ThreadLocal) tokenField.get(point.getTarget());local.set(token);tokenField.setAccessible(true);tokenField.set(point.getTarget(),local);Object object = point.proceed();return object;} catch (Throwable e) {e.printStackTrace();throw e;}
}//防止内存泄露!!!
@After("tokenPointCut()")public void doAfter(ProceedingJoinPoint point) {TOKEN.remove ();}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...