# spring-annotation **Repository Path**: cheerchang/spring-annotation ## Basic Information - **Project Name**: spring-annotation - **Description**: 使用springboot实现自定义注解 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-11 - **Last Updated**: 2021-09-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # springboot使用自定义注解,通过aop实现注解业务 ## 前言 本文章介绍通过自定义的注解实现判断用户token是否失效的业务,代码很简单知识点分为以下几个 * 自定义注解 * aop around * token机制的简单说明,这个会在后期做微服务的时候在详细讲解 * base64加密、解密 # 软件架构 简单的mvc模式 * 使用springboot 2.5.2 * java 1.8 # 安装教程 1. 本地需要有java、maven环境 2. 下载项目 3. 使用你的idea运行起来 4. 本项目使用的是web服务,在配置文件可修改端口 # 项目结构 ``` . ├── java │   └── team │   └── exampl │   └── springannotation │   ├── SpringAnnotationApplication.java //启动类 │   ├── annotation │   │   └── CheckToken.java //自定义注解 │   ├── aop │   │   └── MyAspect.java //自定义aop │   ├── cotroller │   │   └── UserController.java //用户controller │   ├── entity │   │   ├── TokenTypeEnum.java //自定义token枚举 │   │   ├── dto │   │   │   ├── BaseDTO.java │   │   │   └── UserDTO.java │   │   └── vo │   │   ├── BaseVO.java │   │   ├── UserInfoVO.java │   │   └── UserLoginVO.java │   ├── service │   │   ├── UserService.java │   │   └── impl │   │   └── UserServiceImpl.java //实现用户业务逻辑 │   └── util │   ├── Base64Util.java //base64工具 │   └── CacheUtil.java //缓存类 └── resources ├── application.properties ├── static └── templates ``` # 项目讲解 以下介绍实现过程,如果看完项目有疑问或者指正错误非常欢迎! ## 自定义注解 ### 代码 team/exampl/springannotation/annotation/CheckToken.java ```java /** * 检查token * * @author cheer */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CheckToken { /** * 在使用时要指定一个token验证类型 * 默认为检查用户失效验证 * * @return token type */ TokenTypeEnum value() default TokenTypeEnum.USER_INVALIDATION; } ``` ### 知识点 1. RetentionPolicy:指定**RUNTIME**类型在VM里保留,所以可以使用反射获取到 2. Target:指定为在方法上使用 3. TokenTypeEnum:自定义一个枚举是为了拓展业务 ## 实现aop ### 代码 team/exampl/springannotation/aop/MyAspect.java ```java /** 1. aop 2. @author cheer */ @Slf4j @Component @Aspect public class MyAspect { @Around("execution(* team.exampl.springannotation.cotroller..*.*(..))") public Object before(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature sig = (MethodSignature) joinPoint.getSignature(); log.info("切入的方法名:{}", sig.getMethod().getName()); //获取方法上注解 CheckToken[] annotation = sig.getMethod().getAnnotationsByType(CheckToken.class); if (annotation.length > 0) { //获取到参数 Object arg = joinPoint.getArgs()[0]; if (arg instanceof BaseDTO) { BaseDTO baseDTO = (BaseDTO) arg; if (baseDTO.getToken() == null) return new BaseVO<>(401, "token null", null); String token = Base64Util.decoder(baseDTO.getToken()); for (CheckToken checkToken : annotation) { TokenTypeEnum typeEnum = checkToken.value(); if (typeEnum == TokenTypeEnum.USER_INVALIDATION) { //验证token,这里就不做这么多验证token逻辑了。TODO 后期新建一个spring cloud项目讲清楚token的使用 if (CacheUtil.checkUserInvalidation(token)) { return joinPoint.proceed(); } return new BaseVO<>(401, "token无效,请重新登录", null); } else if (typeEnum == TokenTypeEnum.USER_RE_LOGIN) { //删除用户token if (CacheUtil.checkUserInvalidation(token)) { CacheUtil.getTokenMap().remove(token); return joinPoint.proceed(); } return new BaseVO<>(401, "token无效,请重新登录", null); } } } } return joinPoint.proceed(); } } ``` ### 知识点 1. **Around**:实现环绕切入是为了把整个业务做完整,因为使用了token验证作为业务场景,所以这个验证业务在aop里必须是完整的。不明白,有疑问?请看第3条。 2. **getAnnotationsByType**:根据注解查找这个方法是否存在你的自定义注解,注解可以是多个因为一个方法上可以实现两个业务不同的相同名称注解。这就是为什么使用自定义枚举`TokenTypeEnum`当作注解参数了。 3. **return new BaseVO**:因为实现了Around方式的切入我们就可以在这里做到一个完整的业务,所以我们就可以在这返回业务response,从而减少代码冗余。 ## token测试 ### 先明白为什么要使用token,什么场景需要token呢? 以下要讲一些token的业务以及简单的实现,选中的是本项目已经可以实现的。 - [x] token可以存放一些简单的用户信息,前端不必每次请求都携带用户的信息 - [x] 它可以在多个服务作为共享信息 - [ ] 解决CSRF(Cross-site request forgery),中文名称:跨站请求伪造 - [ ] 可以作为单点登录 ### UserController代码 team/exampl/springannotation/cotroller/UserController.java ```java /** * 不需要检验token的方法 * * @param dto 用户 * @return 认证 */ @GetMapping("authentication") public BaseVO authentication(UserDTO dto) /** * 需要检验token的方法 * * @param dto 用户 * @return 用户 */ @CheckToken @GetMapping("getUserInfo") public BaseVO getUserInfo(UserDTO dto) ``` ### UserController知识点 1. 在方法上使用自定义注解就可以在`MyAspect`里判断token ### UserServiceImpl代码 team/exampl/springannotation/service/impl/UserServiceImpl.java ```java final String account = "test"; final String password = "test123"; @Override public UserLoginVO authentication(UserDTO dto) { //假设有一个用户账号密码是test,test if (dto.getAccount().equals(account) && dto.getPassword().equals(password)) { UserLoginVO vo = new UserLoginVO(); //这里的token先存放一个用户账号,一般token会存放用户的基础信息。经过加密后传给前端 vo.setToken(Base64Util.encoder(account)); //我们使用用户的账号当作缓存token的key CacheUtil.getTokenMap().put(account, password); return vo; } return null; } @Override public UserInfoVO getUserInfo(UserDTO dto) { //假如这是数据库或缓存,正好有一条用户信息,并且是根据account查询用户 Map userMap = new HashMap<>(); userMap.put(account, new UserInfoVO("KING")); //这里使用token获取用户的基础信息 String token = Base64Util.decoder(dto.getToken()); //已知token存放的是用户账号那么就可以按照这个账号查找用户是否存在并返回用户信息 if (userMap.containsKey(token)) return userMap.get(token); return null; } ``` ### UserServiceImpl知识点 1. 使用base64加密用户信息,其实加密token是比这复杂的,服务器要生成签名对前端发过来的token进行验证等一些操作,这个项目只做了加密简单的例子能先理解token,**注意这样加密没有安全性**。 2. 每次生成token后要存起来。建议存在redis,因为在使用微服务时多个服务可以共享token。 3. getUserInfo方法可以直接使用decoder解密token,因为它在被controoler调用的时候就已经被`MyAspect`验证过token信息是否已经存在了