1.引言
前段时间因为业务的需求,需要对律所的数据进行拆分与合并,因为整个系统涉及到大量的业务模块,为了尽量减少各具体模块的迁移开发工作量,需要对迁移过程中的共性内容进行抽象,从而设计出一套更通用的迁移框架。
迁移框架在DB层面进行复制,为了更方便的描述表结构及表之间的关联,使用了注解进行定义,故总结一下注解相关的知识。
注解是JDK1.5引入的新特性,是在代码里面的一种特殊标记,针对这些标记,我们可以在源码、编译或运行时对代码做特殊处理。目前很多主流框架都有在使用注解,如:
JDK里面的注解@Override、@Resource等;
Spring里面使用的注解@Autowired、@Configurable等;
JUnit测试框架里面的@Test、@Before、@BeforeClass等。
可以说了解注解是我们设计出优雅代码,通读各类框架源码的必备技能,尤其是在Spring Boot大行其道的今天。
2.自定义注解
2.1 定义及使用
以下代码定义了一个注解Table,有一个属性value用来表示表的名称。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Table{
/**
* 表的名称
*/
String value() default "";
}
以下代码使用了刚才定义的注解Table,指定了value的属性值app_matter,因为只有一个属性,所以没有写全称value="app_matter"
@Table("app_matter")
public class MatterDefinition {
@Id(value ="pk_id", mode = IdGeneratorMode.UUID)
String id;
}
定义注解的过程中,还存在很多其他信息,下面的章节对定义注解中涉及的各个信息进行说明。
2.2 元注解
上面在定义注解的过程中,使用到了很多其他注解,这种在描述注解的注解,称之为元注解。
@Target
定义当前注解可以使用在什么地方,上面的Table注解Target使用了TYPE,所以可以应用到类、接口、注解及枚举中,其他Target的类型还有:
其中TYPE_PARAMETER、TYPE_USE是1.8的新增特性,因为并没有提供反射接口获取处理,所以该注解一般要开发者自己实现或第三方开发工具实现。
ElementType.TYPE_PARAMETER:
表示该注解可以应用到类型参数的申明中,如:
public <@MyTypeParameterT> void show(Tmessage){}
ElementType.TYPE_USE:
表示该注解可以使用到类型出现的地方,如:
MatterDefinition instance = new @MyTypeUse MatterDefinition();。
@Retention
定义当前注解的保留策略,如:
RetentionPolicy.SOURCE:
表示该注解仅存在源码中,编译成class文件后,注解信息将丢失,如:@Override注解。
RetentionPolicy.CLASS:
表示该注解存在编译后的class文件中,是注解保留策略的默认方式。
RetentionPolicy.RUNTIME:
表示该注解存在class文件中,并且被jvm加载后还存在,可以使用反射相关接口进行获取。
@Documented
定义该注解是否支持导出到javadoc文档中。
@Inherited
定义该注解是否可以被子类继承,如:ClassA使用了Inherited注解,ClassB继承了ClassA,则ClassB可以获取到该注解。
2.3 注解元素
上例中的注解Table,定义了元素value
String value() default "";
3.重复注解
Repeatable:重复注解,JDK1.8引入的新特性,可以在类或方法等上指定多个相同的注解属性,这种方式只是一种语法糖,下面的Demo类中最终生成的注解其实还是Items。
@Target(ElementType.TYPE)
public @interface Items{
Item[] value();
}
@Repeatable(Items.class)
@Target(ElementType.TYPE)
@interface Item{
String value() default "";
}
@Item("item1")
@Item("item2")
class Demo {
public static void main(String[] args) {
}
}
4.注解反射
和注解相关的接口有Annotation和AnnotatedElement,其中:
所有的注解对象annotation都实现了Annotation接口,该对象的annotationType方法返回注解的类型(如:Table.class),该对象自身是一个动态代理对象(如:$Proxy2)。
5.注解的应用
5.1 注解属性的获取
@Table("app_matter")
class MatterDefinition {
@Id(value ="pk_id", mode = IdGeneratorMode.UUID)
String id;
}
public class AnnotationDemo {
public static void main(String[] args)throws NoSuchFieldException {
System.out.println(MatterDefinition.class.isAnnotationPresent(Table.class));//true
Table tableAnnotation = MatterDefinition.class.getAnnotation(Table.class);
System.out.println(tableAnnotation.value());//app_task
Field idField = MatterDefinition.class.getDeclaredField("id");
Id idAnnotation = idField.getAnnotation(Id.class);
System.out.println(idAnnotation.mode());//IdGeneratorMode.UUID
}
}
5.2 获取指定包下含有指定注解的类
方式一:原生反射
通过JDK原生的方式进行反射处理,即获取到指定包下的所有类,然后遍历每个类判断当前类是否出现指定注解,这种方式相当于从零开始撰写很多基础代码。
方式二:借助Spring中注解Component的Bean管理
在定义注解(如:Table)的时候加上元注解@Component,以把使用当前注解的类注册为Bean对象
从容器ApplicationContext中调用:Map getBeansWithAnnotation(Class annotationType)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component//此处加上Component注解
@interface MyTableAnnotation{
String value() default "";
}
@MyTableAnnotation("app_task")
class TaskDefinition {
@Id(value ="pk_id", mode = IdGeneratorMode.UUID)
String id;
}
@Component
public class ComponentAnnotationDemo implements ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext= applicationContext;
}
public void show() {
//获取容器中指定注解类型的所有Bean对象
Map stringObjectMap =applicationContext.getBeansWithAnnotation(MyTableAnnotation.class);
System.out.println("stringObjectMap:"+ stringObjectMap);
}
}
方式三:借助Spring的包扫描注解ClassPathScanningCandidateComponentProvider
通过ClassPathScanningCandidateComponentProvider扫描指定的包
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTableAnnotation{
String value() default "";
}
@MyTableAnnotation("app_task")
class TaskDefinition {
@Id(value ="pk_id", mode = IdGeneratorMode.UUID)
String id;
}
@Component
public class ComponentAnnotationDemo {
public void show() {
//useDefaultFilters:是否扫描默认的Component,Controller,Service,Repository注解
ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
//以注解的方式作为扫描条件,也可以指定正则(如:RegexPatternTypeFilter)等其他扫描方式
classPathScanningCandidateComponentProvider.addIncludeFilter(newAnnotationTypeFilter(MyTableAnnotation.class));
//basePackage:指定扫描的基础包,空字符串为所有包
Set beanDefinitionSet = classPathScanningCandidateComponentProvider.findCandidateComponents("com.icourt");
System.out.println(beanDefinitionSet);
}
}
6.几个注解的简单说明
此处简单列举几个注解的说明,权当混个脸熟,后期再根据实际情况,结合各框架,进行专题深入讲解。
以下三个注解是JDK1.5开始支持注解特性时引入的,也算是鼻祖了。
Override:表示重写了父类的指定方法;如果方法上有该注解,但方法名与超类不同,开发工具将给出错误警告。
Deprecated:表示指定的类或方法已经被废弃,不建议继续使用,而应该使用新的替代方案。
SuppressWarnings:表示忽略指定类型的警告提示。
以下两个注解是JDK1.8引入的新特性。
FunctionalInterface:函数式接口的注解,主要用在Lambda表达式中,表示该接口只能有一个抽象方法,从而可以使用Lambda表达式进行简写。
Repeatable:重复注解,可以在类或方法等上指定多个相同的注解属性,上面章节有说明。
以下三个是通用注解(Common Annotation),原来是Java EE5.0规范的一部分,后面加入到了Java SE6.0中,避免框架或各类开发者重复定义。Java SE里面仅包含了注解类的定义,但没有包含注解类的解析实现,而是由Java EE容器进行实现,如:应用到Servlet规范的生命周期注解;Spring对这几个注解也做了解析支持。
Resource:表示一种资源,如果应用到属性或方法上,则这个类在实例化的时候,将自动注入该类型或命名的对象。
PostConstruct:表示在对象创建之后做的额外处理。
PreDestroy:表示在对象销毁之前做的额外处理。
以下注解是Spring2.5版本引入的,针对MVC和通用组件的一套注解。
Controller:表示这是一个Controller层对象,一般封装参数校验,权限判断,及不可复用的简单逻辑。
Service:表示这是一个业务层Bean对象,一般封装具体的业务逻辑。
Repository:Spring 2.0引入的注解,对数据访问层的对象进行Bean标示。
Component:表示这是一个Bean对象。
Autowired:默认根据类型自动装配指定的Bean对象,如果要按名称进行装配,可以结合Qualifier注解。
以下两个注解是Spring3.0为了减少XML配置而引入的基于Java的注解。
Configuration:表示这是一个配置类,里面会包含一个或多个被Bean标注的方法,用于Spring创建出Bean的实例。
Bean:标注的方法可以构造出Bean的实例,方法名默认为容器中Bean的名称。
以下为Spring4.0引入的条件注解。
Conditional:根据某个条件决定是否创建某个bean,这也是Spring Boot框架实现的基础。
7.要点总结
对前面介绍的内容做一张脑图进行总结,方便大家一览文章的关键要点。