记录一次对AOP的使用,使用AOP完成i18n多语言支持的集成

上回说到


算了。不说了。。。。之前用增强Controller完成了有i18n的支持,后来想想,要不要试试用拦截器和aop各自实现一次试试,说干就干
首先准备了依赖

依赖

 <!-- aop -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
 <dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>fastjson</artifactId>
     <version>1.2.47</version>
 </dependency>

定义一个切面

我们使用@Aspect注解在上将一个类定义为切面类,
使用@Pointcut(切面表达式)定义切入点
使用@Around(切入点)指定一个切面内容切入哪个切入点
方法有一个参数ProceedingJoinPoint pjp 这个就代表被切入的方法,我们可以使用pjp.proceed()使原方法执行,注意这里是使原方法执行,执行到这里就会直接执行原来的方法,如果有返回值可以直接获取;
如果不调用则不会执行原方法内容,调用了原方法之后,就会暂停在等待返回哪个阶段,,所以此时如果使用的使ResponseEntity响应的,并不会被解析为其中的泛型,返回值依然是ResponseEntity,不像增强Controller,获取到的object是解析后的泛型所指类型;
响应顺序…方法响应=>aop=>增强controller=>拦截器=>过滤器

完整代码


/**
 * 定义一个切面打印请求返回参数信息
 */

@Aspect  // 定义为一个切面
@Configuration
public class LogRecordAspect {

    private final ResultUtils resultUtils;

    public LogRecordAspect(ResultUtils resultUtils) {
        this.resultUtils = resultUtils;
    }

    private static final Logger logger = LoggerFactory.getLogger(LogRecordAspect.class);

    // 定义切点Pointcut
    @Pointcut("execution(public * com.doria.learnProject.codeStyle.LanguageMapper.*Controller.*(..))")
    public void resultPointcut() {
    }

    /**
     * 切面内容,当触发上面的切面以后就会走到这里的代码
     *
     * @param pjp 方法体
     * @return 就是方法返回值
     * @throws Throwable
     */
    @Around("resultPointcut()") // 指定切面需要插入的切点
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        // 获取当前线程所绑定的requestAttributes
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        // 强转以获取Request
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        // 获取request[其实看到这个demo我也很疑惑为什么不直接拿去注入的HttpServletRequest,但是我很菜,不敢怀疑,听大佬的]
        assert sra != null;
        HttpServletRequest request = sra.getRequest();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String json = JSON.toJSONString(request.getParameterMap());
        logger.info("收到请求=>Uri:{},请求方式:{},参数:{}", uri, method, json);

        // 获取方法的返回值result的值就是返回值
        Object result = pjp.proceed();
        logger.info("请求结束=>返回值为:" + JSON.toJSONString(result));

        // 由于特殊需要,这里需要对参数的返回值进行处理,这里是吧code转为对应的语言填入msg
        if (result instanceof Result)
            // 调用语言转换方法,大概内容就是强转然后读取code然后通过i18n转换为msg写入msg
            return resultUtils.languageFormatToResult(result);
        return result;
    }
}

语言填充方法


@Autowired
private MessageSource messageSource;

/**
 * 语言转换方法
 * @param o 待处理对象
 * @return 处理后的对象
 */
public Object languageFormatToResult(Object o){
    // 由于调用方法前使用了instanceof确定了具体类型,我们这里可以直接强转
    Result<?> result = (Result<?>) o;
    // 强转以后取出code
    String key = result.getCode().toString();
    // 设置msg,用code作为key获取参数
    result.setMsg(messageSource.getMessage(key,null, LocaleContextHolder.getLocale()));
    return result;
}

关于切点和切点表达式

切入点

可以通过这个注解定义一个切入点,其中可以没内容

// 定义切点Pointcut
    @Pointcut("execution(public * com.doria.learnProject.codeStyle.LanguageMapper.*Controller.*(..))")
    public void resultPointcut() {
    }

切点表达式

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

这里问号表示当前项可以有也可以没有,其中各项的语义如下:

modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;

切入点表达式详解

后续补充:

aop遵循一个先进后出原则

time:2021.11.16
可以通过注解@Order(num)或者实现接口Ordered重写public int getOrder()方法控制aop的执行顺序
aop围绕ProceedingJoinPoint pjp执行
根据配置的顺序,越小越先执行,执行到pjp.proceed()后按照顺序倒序再执行之后的
方法一:1234 [pjp] 5678
方法二:abcd [pjp] efg
执行结果是 1234 abcd [pjp] efg 5678
案例:
方法1做redis缓存,方法二取出code根据 语言翻译为msg写入;

原来的顺序是语言翻译先执行,那在pjp执行获取响应结果后,就会先执行最后执行的redis缓存部分,结果就缓存了一个没有翻译过的版本,第二次请求进来发现已经有缓存了直接返回了没有翻译过的版本;

改变顺序,先执行redis缓存aop;
这样redis前段先执行完再执行了翻译部分的前端,pjp执行获取结果后,就会执行到最后执行的翻译部分,翻译完成再交给redis缓存部分。这样redis缓存的就是翻译过的版本。这样到了下一次请求进来发现有缓存直接响应的就是翻译后的版本

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值