基于annotation的aop时区转换方案

简介: 应用所使用的时间一般都是使用服务器所在时区的时间,可是随着阿里云的国际化发展,有越来越多不同时区的用户,用户希望应用所显示或者定时任务运行的时间以用户所在地时区的需求越来越多,因此也要求了系统具备多时区转换的功能。 对于新应用,最好的方式就是在设计的时候所有的时间都以带时区的时间格式,但是老系统的话就需要解决服务器时区时间和用户时区时间的转换问题。为了改造对老系统原有功能影响最小化,可以使用ao

应用所使用的时间一般都是使用服务器所在时区的时间,可是随着阿里云的国际化发展,有越来越多不同时区的用户,用户希望应用所显示或者定时任务运行的时间以用户所在地时区的需求越来越多,因此也要求了系统具备多时区转换的功能。

对于新应用,最好的方式就是在设计的时候所有的时间都以带时区的时间格式,但是老系统的话就需要解决服务器时区时间和用户时区时间的转换问题。为了改造对老系统原有功能影响最小化,可以使用aop的方式在输入的时候把用户时区转换成服务器时区,然后在输出的时候把服务器时区转换成用户时区。

由于历史原因,老应用经过了好几个团队,因此接口的风格差异太大,整理发现,一个应用接口的返回格式包含两种方式,一部分接口是直接设置ModelMap返回,而另一部分则是return Object返回。由于改造接口工作两比较大,因此采用了以下两种AOP的解决方案:

方案一:基于自定义annotation的方案

定义自定义annotation

@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME)
public  @interface  TimeTransformationType {
Class<?> classType();
String methodName() default "";
}

时间转换实现

抽象类

public abstract class AbstractTimeTransformation {
    /**
    * 入参处理
    */
   public abstract void before(Object[] Args);
   /**
    * 执行后参数处理
    */
   public abstract Object after(Object[] Args,Object result);
}

实现类

public class ModelMapTimeTransformationImpl extends AbstractTimeTransformation{
   @Override
   public void before(Object[] Args) {}
   @Override
   public Object after(Object[] Args,Object result) {            
            for (Object param : Args) {
                if (param instanceof ModelMap) {
                    timeTrans(((ModelMap)param).values());
                }
            }}
}

 

public class ResultObjectTimeTransformationImpl extends AbstractTimeTransformation{
   @Override
   public void before(Object[] Args) {}
   @Override
   public Object after(Object[] Args,Object result) {  timeTrans(result)  }        
}

拦截方法

对于要转换的是通过ModelMap返回的接口,加上注解同时指定时间处理的方法

@TimeTransformationType(classType = ModelMapTimeTransformationImpl.class)
public void queryProfiles(ModelMap modelMap, HttpServletRequest request) {}

同理对于返回Object的方法

@TimeTransformationType(classType = ResultObjectTimeTransformationImpl.class)
public Object queryProfiles( HttpServletRequest request) {}

切面

public class Advice {
    @Around("@annotation(**.TimeTransformationType)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //获取现在执行中的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();       
        Method currentMethod = signature.getMethod();      
  
        //获取该方法上的注解
        TimeTransformationType timeTransformationTypeType = getAnnotation(currentMethod);
        //通过反射拿到具体的处理方法       
        Class<?> classType = (Class<?>) timeTransformationTypeType.classType();        
        String className = classType.getName();        
        final AbstractTimeTransformation timeTrans = (AbstractTimeTransformation) Class.forName(className).newInstance();
        //具体入参处理
        timeTrans.before(joinPoint.getArgs());
       //实际方法执行
       final Object resultObject = joinPoint.proceed();
       //执行后处理
       timeTrans.after(joinPoint.getArgs(),resultObject);
           }
}

如果还有其他返回格式,如ModelAndView,那么只需在实现一个对应的实现类,然后在接口上加上注解就可以了。

 

方案二:基于spring annotation的方案

我们将http接口的注解和返回格式进行分类,其实可以大致分为三大类

Pointcut

    @Pointcut("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
    public void responseBodyAspect() {
    }

    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void restControllerAspect() {
    }

    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void controllerAspect() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestMappingAspect() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postMappingAspect() {
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getMappingAspect() {
    }

Pointcut组合

    @Pointcut("( responseBodyAspect() || restControllerAspect()) && requestMappingAspect()")
    public void restfulAspect() {
    }

    @Pointcut("controllerAspect() && requestMappingAspect() && ! restfulAspect()")
    public void viewAspect() {
    }

拦截执行

由于要对入参进行拦截修改,因此需要使用@Around

    @Around("viewAspect()")
    public Object veiwAround(ProceedingJoinPoint joinPoint)throws Throwable{
        Object[] args = joinPoint.getArgs();

        //入参处理
        convertArgs(args);

        Object resultObject = joinPoint.proceed(args);

        try {
            //ModelAndView返回处理
            if (resultObject != null && resultObject instanceof ModelAndView) {
                resultObject 
                =timeZoneConvert(((ModelAndView)resultObject).getModel().values(),true);
            } else {
                if (joinPoint.getArgs() == null || joinPoint.getArgs().length == 0) {
                    return resultObject;
                }
                //ModelMap返回处理
                for (Object param : joinPoint.getArgs()) {
                    if (param instanceof ModelMap) {
                        timeZoneConvert(((ModelMap)param).values(),true);
                    }
                }
            }
        }catch (Exception e){

        }
        return resultObject;
    }
    @Around("restfulAspect()")
    public Object restfulAround(ProceedingJoinPoint joinPoint)throws Throwable{
        Object[] args = joinPoint.getArgs();
        //入参处理
        convertArgs(args);

        Object resultObject = joinPoint.proceed(args);

        try {
           //返回结果处理
           timeZoneConvert(resultObject,true);
        }catch (Exception e){

        }
        return resultObject;

    }
    private void convertArgs(Object[] args){
        try {

            if (args != null && args.length > 0) {
                for(int i=0;i<args.length;i++){
                    args[i] =timeZoneConvert(args[i],false);
                }
            }

        }catch (Exception e){

        }
    }

可以在处理对象里的Field加上自定义注解增加处理效率,同时可以指定时间是string格式时候的时间格式。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TimeZoneFields {
   /**
    * 时间格式字段
    *
    * @return
    */
   String pattern() default "yyyy-MM-dd HH:mm:ss";
}

最后就是时区转换的方法,主要需要对各种格式进行具体的处理,包括Date,String,ZonedDateTime 还有Collection(其中Map需要取values进行循环)和自己定义的对象。

相关文章
|
XML 监控 Java
Spring AOP】@Aspect结合案例详解(一): @Pointcut使用@annotation + 五种通知Advice注解(已附源码)
在微服务流行的当下,在使用SpringCloud/Springboot框架开发中,AOP使用的非常广泛,尤其是@Aspect注解方式当属最流行的,不止功能强大,性能也很优秀,还很舒心!所以本系列就结合案例详细介绍@Aspect方式的切面的各种用法,力求覆盖日常开发中的各种场景。本文带来的案例是:打印Log,主要介绍@Pointcut切点表达式的@annotation方式,以及 五种通知Advice注解:@Before、@After、@AfterRunning、@AfterThrowing、@Around。
|
Java Spring Maven
Spring 一二事(10) - annotation AOP
先贴出POM的内容,这个毕竟是用的maven来简单构建的 1 2 4.0.0 3 org.springframework.samples 4 maven-spring002-aop 5 0.
758 0
|
5月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
98 1
|
3月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
369 1
什么是AOP面向切面编程?怎么简单理解?
|
3月前
|
XML Java 开发者
论面向方面的编程技术及其应用(AOP)
【11月更文挑战第2天】随着软件系统的规模和复杂度不断增加,传统的面向过程编程和面向对象编程(OOP)在应对横切关注点(如日志记录、事务管理、安全性检查等)时显得力不从心。面向方面的编程(Aspect-Oriented Programming,简称AOP)作为一种新的编程范式,通过将横切关注点与业务逻辑分离,提高了代码的可维护性、可重用性和可读性。本文首先概述了AOP的基本概念和技术原理,然后结合一个实际项目,详细阐述了在项目实践中使用AOP技术开发的具体步骤,最后分析了使用AOP的原因、开发过程中存在的问题及所使用的技术带来的实际应用效果。
83 5
|
5月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
57 13