思来想去,还是写一篇关于注解的文章。
我很清楚所有初学者的疑惑,以下文章,希望初学者先学会用,在去了解annotation的实现机制及源代码。
注解的概念
注解与注释类似,却又有些不同,注释仅仅起到解释的作用,而注解不仅有解释作用,还有解析的作用。
古人云,见文知义,我想注解的第一个作用便在于此,注解往往就是用一个词来统括所有它所包含的意义,例如:@Overide 便为覆盖, @Inherited 便为继承,它们都起到了对程序解释说明的作用。同时,当你使用诸如@Overide注解的时候,编译器便会增强程序的检查过程,那么这个检查过程就是所谓的解析。譬如你在复写父类的私有方法的时候,编译器是不会报错的,但是运行会有错误。但是如果你在复写该方法的时候,添加了@Overide注解,那么你不用介意太多,当你复写了不该复写的东西的时候,注解会提示你。
那么知道了注解的基本作用的后,你一定急于知道注解背后是怎么实现的。先别急,我们先聊聊注解的分类,由表及里的去剖析。
元注解
元注解的作用就是负责注解其他的注解,在Java5.0中提出了四个meta-annotation:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
从Java8.0开始,又提出了两个新的meta-annotation:
5.@Native,
6.@Repeatable
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {
}
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
//上面的做法其实是枚举类型的用法
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。
@Documented:
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
@Inherited:
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。换句话说,既然这个标签将被用于这个类的子类,那么这个类的子子孙孙都是可以被继承的了。更言外之意,final是不能使用这个标签的。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
/**
*
* @author peida
*
*/
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
@Native
这个注释意味着一个定义了常量的域可能引用了本地方法代码。该注解可被一些生成本地头文件的工具用来决定某个头文件是否被需要,以及如果有这种需要的时候,该注解里需要包含哪些注释。
@Repeatable
java.lang.annotation.Repeatable
is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. The value of @Repeatable
indicates the containing annotation type for the repeatable annotation type.
自定义注解:
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解参数的可支持数据类型:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:下面的例子FruitName注解就只有一个参数成员。
简单的自定义注解和使用注解实例:
package annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 水果名称注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
package annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 水果颜色注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};
/**
* 颜色属性
* @return
*/
Color fruitColor() default Color.GREEN;
}
package annotation;
import annotation.FruitColor.Color;
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void displayName(){
System.out.println("水果的名字是:苹果");
}
}
从上面的例子中可以看出,注解可以起到初始化实例变量的作用。
注解元素的默认值:
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。例如:
package annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 水果供应者注解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
* @return
*/
public int id() default -1;
/**
* 供应商名称
* @return
*/
public String name() default "";
/**
* 供应商地址
* @return
*/
public String address() default "";
}
注解在Spring中的应用
谈到spring中的注解,我们就不得不谈到spring对传统web开发的一大革命性举措,这其中xml文件起到了居功至伟的作用。但是随着开发规模的扩大,配置xml文件也会成为一种负担,所以这就有了注解在spring中的应用。
注释配置相对于 XML 配置具有很多的优势:
- 它可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作。如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO 的属性名、类型等信息,如果关系表字段和 PO 属性名、类型都一致,您甚至无需编写任务属性映射信息——因为这些信息都可以通过 Java 反射机制获取。
- 注释和 Java 代码位于一个文件中,而 XML 配置采用独立的配置文件,大多数配置信息在程序开发完成后都不会调整,如果配置信息和 Java 代码放在一起,有助于增强程序的内聚性。而采用独立的 XML 配置文件,程序员在编写一个功能时,往往需要在程序文件和配置文件中不停切换,这种思维上的不连贯会降低开发效率。
因此在很多情况下,注解配置比 XML 配置更受欢迎,注解配置有进一步流行的趋势。Spring 2.5 的一大增强就是引入了很多注解类,现在可以使用注解配置完成大部分 XML 配置的功能。接下来讲述的是使用注解进行 Bean 定义和依赖注入的内容。
那么我们先来看看在使用注解配置以前,我们是如何配置bean并完成bean之间关系的建立。接下来这几个例子的代码是来源于网上的,并非原创,但是完全可以用来学习。
我们现在有三个类:Boss, Car, Office这三个类需要在spring的容器中配置为bean。
Office仅有一个属性:
package com.TEST;
public class Office{
private String officeNo = "001";
//省略setter getter
@Overide
public String toString(){
return "officeNo: " + officeNo;
}
}
Car有两个属性:
package com.TEST;
public class Car{
private String brand;
private double price;
//省略getter setter
@Overide
public String toString(){
return "brand: " + brand + "," + "price: " + price;
}
}
Boss拥有Office和Car类型的两个属性:
package com.TEST;
public class Boss {
private Car car;
private Office office;
// 省略 get/setter
@Override
public String toString() {
return "car:" + car + "\n" + "office:" + office;
}
}
我们在Spring容器中将Office和Car声明为Bean,并注入到Boss Bean中:
下面是使用传统的XML完成这个工作的配置文件beans.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id = "boss" class = "com.TEST.Boss">
<property name = "car" ref = "car"/>
<property name = "office" ref = "office"/>
</bean>
<bean id = "office" class = "com.TEST.Office">
<property name = "officeNo" value = "002"/>
</bean>
<bean id = "car" class = "com.TEST.Car">
<property name = "brand" value = "红旗 CA72"/>
<property name = "price" value = "20000"/>
</bean>
</beans>
然后我们整合运行一下上述代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ManuIocTest{
public static void main(String[] args){
String[] locations = {"beans.xml"};
ApplicationContext cox = new ClassPathXmlApplicationContext(locations);
Boss boss = (Boss)ctx.getBean("boss");
System.out.println(boss);
}
}
下面我们看看如何使用@Autowired注解来完成上述工作,Spring2.5引入了该注解,它可以对类成员变量,方法及构造函数进行标注,完成自动装配的工作。来看一下使用@Autowired进行成员变量自动注入的代码:
package com.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class Boss {
@Autowired
private Car car;
@Autowired
private Office office;
…
}
Spring通过一个BeanPostProcessor对@Autowired进行解析,所以要让@Autowired起作用必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessorBean.
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- 该 BeanPostProcessor 将自动起作用,对标注 @Autowired 的 Bean 进行自动注入 -->
<bean class="org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor"/>
<!-- 移除 boss Bean 的属性注入配置的信息 -->
<bean id="boss" class="com.Test.Boss"/>
<bean id="office" class="com.Test.Office">
<property name="officeNo" value="001"/>
</bean>
<bean id="car" class="com.Test.Car" scope="singleton">
<property name="brand" value=" 红旗 CA72"/>
<property name="price" value="20000"/>
</bean>
</beans>
这样,当Spring容器启动的时候,AutoWiredAnnotationBeanPostProcessor将扫描Sprign容器中的所有Bean,当发现Bean中拥有@Autowired注解时就找到和其匹配(默认按类型匹配)的Bean,并注入到对应的地方中去。
按照上面的配置,Spring 将直接采用 Java 反射机制对 Boss 中的 car 和 office 这两个私有成员变量进行自动注入。所以对成员变量使用@Autowired 后,您大可将它们的 setter 方法(setCar() 和 setOffice())从 Boss 中删除。
当然,也可以通过 @Autowired 对方法或构造函数进行标注,来看下面的代码,将@Autowired注解标注在Setter方法上
package com.Test;
public class Boss {
private Car car;
private Office office;
@Autowired
public void setCar(Car car) {
this.car = car;
}
@Autowired
public void setOffice(Office office) {
this.office = office;
}
…
}
这时,@Autowired 将查找被标注的方法的入参类型的 Bean,并调用方法自动注入这些 Bean。而下面的使用方法则对构造函数进行标注:
package com.baobaotao;
public class Boss {
private Car car;
private Office office;
@Autowired
public Boss(Car car ,Office office){
this.car = car;
this.office = office ;
}
…
}
由于Boss构造函数有两个入参,分别是car和office, @Autowired将分别寻找和它们类型匹配的 Bean,将它们作为Boss(Car car, Office office)的入参来创建Boss Bean。
当候选 Bean 数目不为 1 时的应对方法
在默认情况下使用 @Autowired
注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出BeanCreationException
异常,并指出必须至少拥有一个匹配的 Bean。我们可以来做一个实验:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ">
<bean class="org.springframework.beans.factory.annotation.
AutowiredAnnotationBeanPostProcessor"/>
<bean id="boss" class="com.baobaotao.Boss"/>
<!-- 将 office Bean 注释掉 -->
<!-- <bean id="office" class="com.baobaotao.Office">
<property name="officeNo" value="001"/>
</bean>-->
<bean id="car" class="com.baobaotao.Car" scope="singleton">
<property name="brand" value=" 红旗 CA72"/>
<property name="price" value="20000"/>
</bean>
</beans>
待续。。