什么是元注解?
简单来说,定义注解时使用到的Java官方提供的注解被称为元注解;
Java定义了如下四种元注解:
什么是元注解?
简单来说,定义注解时使用到的Java官方提供的注解被称为元注解;
Java定义了如下四种元注解:
@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.采用如上方式,通过外部类调用
字典翻译实现步骤:
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进行优化,将字典值存入缓存等,以提高页面响应
@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掉,重新写入自己的配置即可 **/
注解的作用: 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