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