Async注解无效

@Async:由Spring提供,被标记的函数转为异步执行;使用不当则无法达到预期效果,本文主要针对添加注解无效的情况进行分析。

@Service
public class TestService{

   public void test(){
   System.out.println("TestService.test() start");
   testAsync();
   System.out.println("TestService.test() end");
   }

   @Async
   public void testAsync(){
   System.out.println("TestService.testAsync() start");
   Thread.sleep(3000);
   System.out.println("TestService.testAsync() end");
   }
}

//在外部类调用test()
//执行到testAsync()时没有异步处理

//在外部类调用testAsync()
//执行到testAsync()时异步处理

结论:
@Async注解本质上使用的是动态代理;
Spring在容器初始化的时候会将含有AOP注解的类对象替换为代理对象;
外部类调用含有AOP注解的函数时,走代理对象执行异步处理逻辑达到异步效果;
test()调用testAsync()时是自己直接调用自己,没有通过代理类,所以无效。

常用的AOP注解还有@Transactional,使用时无效也可能是如上原因导致

解决方案:
1.采用如上方式,通过外部类调用 read more

Spring实现数据字典翻译

在开始之前,首先我们要了解一个类:BeanPropertyWriter。
这个类是由SerializerFactory 工厂进行实例化的,其作用是对bean中的每个字段进行jackson操作的封装,其中封装了字段的一些元信息,和对此字段进行jackson序列化的操作。
采用Spring项目进行Web服务开发时,在获取到数据后,Spring会通过BeanPropertyWriter对数据进行jackson封装,将其转换为Json串。
如果我们需要在不影响逻辑的情况下对数据进行字典翻译,重写此类是较好的选择

字典翻译实现步骤:
1.实现获取字典的接口

public interface DictService{

    /**
    *key:字典类别
    *value:字典代码值
    *return:字典代码对应的value值
    Object getValueByKey(String key,String value);
}

2.新建注解便于对需要转换的字段进行区分

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface DictConverter{

   String key() default "";
}

3.在需要翻译的字段上添加该注解


@DictConverter(key="TEST)//数据字典中配有字典项为TEST的字典值
private String item;

4.新建SpringUtil类

@Component
public class SpringUtil implements ApplicationContextAware{

   private static ApplicationContext applicationContext=null;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext)throws BeansException{

   if(this.applicationContext=null){
     this.applicationContext=applicationContext;
    }
   }

   //获取applicationContext
   public static ApplicationContext getApplicationContext(){return applicationContext;}

   //通过name获取Bean
   public static Object getBean(String name){
     return getApplicationContext().getBean(name);
   }

   //通过class获取Bean
   public static <T> T Object getBean(Class<T> clazz){
     return getApplicationContext().getBean(clazz);
   }

   //通过name,以及Clazz返回指定的Bean
   public static <T> T Object getBean(String name,Class<T> clazz){
     return getApplicationContext().getBean(name,clazz);
   }
}

5.重写BeanPropertyWriter类(主要实现部分)
粘出BeanPropertyWriter的包名,在自己的工程下创建这个包
新建BeanPropertyWriter类,将jackson的源代码copy过来
声明刚才创建的DictService及注解
创建getDictService()用于获取service对象
找到serializeAsField方法

private DictService dictService;
private DictConverter dictConverter;

private DictService getDictService(){
  if(dictService==null){
    dictService=SpringUtil.getBean(DictService.class);
  }
  return dictService;
}
public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
        Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean);
        //数据字典翻译
        try{
          if(this._member.hasAnnotation(DictConverter.class)){
            dictConverter=this._member.getAnnotation(DictConverter.class);
            if(dictConverter!=null){
               value=getDictService.getValueByKey(dictConverter.key(),value.toString());
               if(value==null){
                  value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean);
               }
            }
         }
        }catch(Exception e){
           //此处可能因字段类型出现报错
           value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean);
        }

        //以下部分不做修改,此处省略
}
* 为提高字典转换速度,建议将DictService进行优化,将字典值存入缓存等,以提高页面响应

Java使用SpringBoot应用配置文件

*配置文件yml和properties均可,以下以yml为例
1.设置配置文件
app:
service:
name: justin
id: 000
2.创建配置文件映射类

@Component//如报错请将此注解注释,因为此时出现了该bean被注册两次的情况
@ConfigurationProperties("app.service")
public class ServiceProperties {

    private String name;//注意该字段名要与配置文件中保持一致
    private String id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

3.创建配置文件读取类并将值赋给对应服务

@Configuration
@EnableConfigurationProperties(ServiceProperties.class)
public class ServiceAutoConfiguration {

    @Autowired
    private ServiceProperties properties;

    @Bean
    public ServiceExecutor serviceExecutor(){
        return new ServiceExecutor(properties.getName(),properties.getId());
    }
}

4.创建并调用对应的服务类

public class ServiceExecutor {

    private final String serviceName;

    private final String serviceId;

    public ServiceExecutor(String serviceName, String serviceId) {
        this.serviceName = serviceName;
        this.serviceId = serviceId;
    }

    public void execute(){
        System.out.println("--模拟对参数进行了处理");
        System.out.println("serviceName:"+serviceName);
        System.out.println("serviceId:"+serviceId);
    }
}

@RestController
public class TestController {

    @Autowired
    private ServiceExecutor serviceExecutor;

    @RequestMapping("/test")
    public void test(){
        serviceExecutor.execute();
    }
}
@ConditionalOnProperty
/**
*该注解用于判断当前@Configuration是否有效
*使用方法:
*1.直接读取配置文件中的值:
**/
@ConditionalOnProperty(value="app.service.enabled")
/**
*2.//如果synchronize在配置文件中并且值为true
**/
@ConditionalOnProperty(name = "synchronize", havingValue = "true")
 
/**
*使用场景:
*在微服务开发中,一般会有一个服务用来存放公共的config配置,
*但是每个服务可能会有细微的区别,这时我们只需要将config服务中提供的引用false掉,重新写入自己的配置即可
**/

自定义Java注解

注解的作用:

1.生成文档
这是最常见的,也是java 最早提供的注解。常用的有 @see @param @return 等

2.跟踪代码依赖性,实现替代配置文件功能
Spring中大量运用注解来简化配置文件

3.在编译时进行格式检查

如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出

实现一个简单的注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyTarget {
}
上面是一个最简单的注解实现,没有定义任何的属性
需要注意的是@Retention(RetentionPolicy.RUNTIME)是定义注解所必须的。
 
@Retention是注解的注解,称为注解的元注解。
括号里有一个枚举类型的值,即为注解内部定义的值。打开Retention的实现:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
可以看到这里定义了一个变量value并且没有缺省值,所以不写这个value就会报错。 
继续打开RetentionPolicy:
public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}
可以发现这个枚举类定义了三个值,这三个值分别代表的是我们定义的MyTarget如何保持。

@Retention(RetentionPolicy.CLASS)
注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候

@Retention(RetentionPolicy.SOURCE)
注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中

@Retention(RetentionPolicy.RUNTIME)
注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时

还需要注意的是java的元注解一共有四个: 
@Document 
@Target 
@Retention 
@Inherited

各个的作用,读者自己查一下吧。

注解处理的一个基础:
 T getAnnotation(Class annotationClass): 
返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

Annotation[] getAnnotations():
返回该程序元素上存在的所有注解。

boolean is AnnotationPresent(Class annotationClass):
判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

Annotation[] getDeclaredAnnotations():
返回直接存在于此元素上的所有注释。
与此接口中的其他方法不同,该方法将忽略继承的注释。
(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)
该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

实现为属性赋值的注解:
package com.justin.test.util;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface CarNameTarget {
    String name() default "";
}
package com.justin.test.util;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface CarSalerTarget {
    String salerName();

    int age() default 1;
}
package com.justin.test.entity;

import com.justin.test.util.CarNameTarget;
import com.justin.test.util.CarSalerTarget;

/**
 * 描述:
 * 汽车实体类
 *
 * @author 孫兵
 * @create 2018-05-15 10:16
 */
public class BnechCar {
    @CarNameTarget(name = "奔驰")
    private String name;

    @CarSalerTarget(salerName = "justin", age = 22)
    private String salerInfo;
}
package com.justin.test.util;

import java.lang.reflect.Field;

/**
 * 描述:
 * 汽车信息工具类
 *
 * @author 孫兵
 * @create 2018-05-15 10:19
 */
public class CarInfoUtil {

    public static void getFruitInfo(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(CarNameTarget.class)) {
                CarNameTarget carNameTarget = field.getAnnotation(CarNameTarget.class);
                String carColour = "汽车的中文名:" + carNameTarget.name();
                System.out.println(carColour);
            } else if (field.isAnnotationPresent(CarSalerTarget.class)) {
                CarSalerTarget carSalerTarget = field.getAnnotation(CarSalerTarget.class);
                String salerInfo = "销售员姓名:" + carSalerTarget.salerName() + ",年龄:" + carSalerTarget.age();
                System.out.println(salerInfo);
            }
        }
    }
}
/**
     * 自定义注解测试
     */
    private void targetTest() {
        CarInfoUtil.getFruitInfo(BnechCar.class);
    }

打印信息:

汽车的中文名:奔驰
销售员姓名:justin,年龄:22