• 注解+拦截器 实现登录校验

项目中在进入方法之前判断用户是否登录、登录了则继续执行方法,未登录则返回异常信息。

a.先定义一个注解@NeedLogin

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {

}

 b.再写个拦截器,控制具体逻辑

public class NeedLoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 反射获取方法上的NeedLogin注解
        if (handler instanceof HandlerMethod){
            /**
             * 注意:
             * 当接口顶层没有设置根路劲时,Spring boot 将接口理解为静态资源(ResourceHttpRequestHandler)
             *  @see com.yds.start.controller.DemoErrorController
             * (HandlerMethod)handler 会报错(ClassCastException)
             *
             * ResourceHttpRequestHandler是用来处理静态资源的;而HandlerMethod则是springMVC中用@Controller声明的一个bean及对应的处理方法.
             *
             */
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            NeedLogin loginRequired = handlerMethod.getMethod().getAnnotation(NeedLogin.class);
            if(loginRequired != null){
                // 有NeedLogin注解说明需要登录,提示用户登录
                response.setContentType("text/plain; charset=utf-8");
                PrintWriter writer = response.getWriter();
                writer.print("你访问的资源需要登录");
                writer.flush();
                writer.close();
                return false;
            }
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}

 c.看效果(写两个接口一个带@NeedLogin,一个不带)

@RestController
@RequestMapping("/demo")
public class DemoController {

    @GetMapping("/sourcea")
    public String sourceA(String str){
        return "你正在访问sourceA资源";
    }

    @NeedLogin
    @GetMapping("/sourceb")
    public String sourceB(String str){
        System.out.println("sourceb");
        return "你正在访问sourceB资源";
    }
}

http://127.0.0.1:8080/demo/sourcea

http://127.0.0.1:8080/demo/sourceb

  • 注解+AOP 日志打印

项目中可以根据注解自定义打印日志

a.定义注解@MyLog

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

b.AOP 实现逻辑

@Slf4j
@Aspect
@Component
public class LogAspect {
    /**
     * PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
     *  切面最主要的就是切点,所有的故事都围绕切点发生
     *  logPointCut()代表切点名称
     */
    @Pointcut("@annotation(com.yds.start.common.annotation.MyLog)")
    public void logPointCut() {
    }

    /**
     * 环绕通知
     * @param joinPoint
     */
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint){
        // 获取方法名称
        String methodName = joinPoint.getSignature().getName();
        // 获取入参
        Object[] param = joinPoint.getArgs();

        StringBuilder sb = new StringBuilder();
        for(Object o : param){
            sb.append(o + "; ");
        }
        //自由发挥…………
        log.info("进入[{}]方法,参数为:{}" ,methodName, sb);

        // 继续执行方法
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        log.info(methodName + "方法执行结束");
    }

}

c.看效果(写个接口,加上@MyLog注解)

@MyLog
@GetMapping(value = "/test")
public String test(String str) {
    return str;
}

访问http://127.0.0.1:8080/demo/test?str=123

观察控制台输出:

2022-02-23 10:10:04.317  INFO 95922 --- [nio-8080-exec-6] com.yds.start.common.aop.LogAspect       : 进入[test]方法,参数为:123; 
2022-02-23 10:10:04.318  INFO 95922 --- [nio-8080-exec-6] com.yds.start.common.aop.LogAspect       : test方法执行结束
  • 注解+AOP+Redis 实现锁机制

a.先定义注解@SyncLock

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SyncLock {
    //键
    String lockKey() default "";
    //附加键
    String extendKey() default "";
    //过期时间
    int remainSecond() default 60;
    //是否例外
    boolean needException() default false;
}

b.实现锁机制

@Component
@Aspect
@Slf4j
public class SyncLockAspect {

    @Autowired
    private RedisUtilsContent redisUtil;

    @Pointcut("@annotation(com.yds.start.common.annotation.SyncLock)")
    public void lockOperator() {
    }

    @Around(value = "lockOperator()")
    public Object lockOperatorImpl(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //获取锁Key
        StringBuilder lockKey = new StringBuilder(method.getAnnotation(SyncLock.class).lockKey());
        //获取附加Key
        String extendKey = method.getAnnotation(SyncLock.class).extendKey();
        //获取过期时间
        int remainSecond = method.getAnnotation(SyncLock.class).remainSecond();
        //是否例外
        boolean needException = method.getAnnotation(SyncLock.class).needException();
        //额外键可以是方法参数中的唯一值,例如uuid等,达到小颗粒度化
        if (!StringUtils.isEmpty(extendKey)) {
            //方法参数
            String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);
            if (parameterNames != null && parameterNames.length > 0) {
                lockKey.append("_").append(parseSpEL(extendKey, parameterNames, joinPoint.getArgs()));
            }
        }
        log.info("sync lock : {}", lockKey);
        if (!StringUtils.isEmpty(lockKey.toString()) && !redisUtil.setNX(lockKey.toString(), "locked", remainSecond)) {
            log.warn("lockKey has locked : {}", lockKey);
            if (!needException) {
                return null;
            } else {
                throw new RuntimeException("资源被锁");
            }
        }
        Object obj;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            log.error("lock function exception : {}, throwable", lockKey, throwable);
            throw throwable;
        } finally {
            redisUtil.del(lockKey.toString());
        }
        return obj;
    }

    /**
     * spel转换
     */
    private String parseSpEL(String lockValue, String[] parameterNames, Object[] args) {
        StringBuilder extendKey = new StringBuilder();
        SpelExpressionParser parser = new SpelExpressionParser();
        //设置参数上下文
        EvaluationContext context = new StandardEvaluationContext();
        IntStream.range(0, parameterNames.length)
                .forEach(i -> context.setVariable(parameterNames[i], args[i]));

        Expression expr = parser.parseExpression(lockValue);
        extendKey.append(expr.getValue(context));
        return extendKey.toString();
    }
    

}

c.使用

//param 中的uuid的值作为extendKey 拼接到key中,已做到细颗粒度化
@SyncLock(lockKey = "updateInfo",extendKey="#uuid")
public void updateInfo(Object param) {
    //业务逻辑
}

@SyncLock
public void updateInfo2(Object param) {
    //业务逻辑
}

//****