@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有
public enum ElementType {/**用于描述类、接口(包括注解类型) 或enum声明 Class, interface (including annotation type), or enum declaration */TYPE,/** 用于描述域 Field declaration (includes enum constants) */FIELD,/**用于描述方法 Method declaration */METHOD,/**用于描述参数 Formal parameter declaration */PARAMETER,/**用于描述构造器 Constructor declaration */CONSTRUCTOR,/**用于描述局部变量 Local variable declaration */LOCAL_VARIABLE,/** Annotation type declaration */ANNOTATION_TYPE,/**用于描述包 Package declaration */PACKAGE,/*** 用来标注类型参数 Type parameter declaration* @since 1.8*/TYPE_PARAMETER,/***能标注任何类型名称 Use of a type* @since 1.8*/TYPE_USE
ElementType.TYPE_PARAMETER(Type parameter declaration) 用来标注类型参数, 栗子如下:
@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParameterAnnotation {}// 如下是该注解的使用例子
public class TypeParameterClass<@TypeParameterAnnotation T> {public <@TypeParameterAnnotation U> T foo(T t) {return null;}
}
ElementType.TYPE_USE(Use of a type) 能标注任何类型名称,包括上面这个(ElementType.TYPE_PARAMETER的),栗子如下:
public class TestTypeUse {@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)public @interface TypeUseAnnotation {}public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {}}// 如下注解的使用都是合法的@SuppressWarnings({ "rawtypes", "unused", "resource" })public static void main(String[] args) throws Exception {TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();typeUseClass.foo("");List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();@TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);}
}
@Retention
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
这些类型和它们所支持的类在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 {
}
注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。具体实例如下:
@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的子类。
注意:@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;
}
自定义注解:
使用@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 "";
}
定义了注解,并在需要的时候给相关类,类属性加上注解信息,如果没有响应的注解信息处理流程,注解可以说是没有实用价值。如何让注解真真的发挥作用,主要就在于注解处理方法,下一步我们将学习注解信息的获取和处理!
二、注解的使用:
第一步:新建一个annotation,名字为:MyAnnotation.java。
package com.dragon.test.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Created by gmq on 2015/9/10.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation
{String hello () default "hello";String world();
}
第二步:建立一个MyTest.java 来使用上面的annotation。
package com.dragon.test.annotation;/*** Created by gmq on 2015/9/10.*/
public class MyTest
{@MyAnnotation(hello = "Hello,Beijing",world = "Hello,world")public void output() {System.out.println("method output is running ");}
}
第三步:用反射机制来调用注解中的内容
package com.dragon.test.annotation;import java.lang.annotation.Annotation;
import java.lang.reflect.Method;/*** 用反射机制来调用注解中的内容* Created by gmq on 2015/9/10.*/
public class MyReflection
{public static void main(String[] args) throws Exception{// 获得要调用的类Class<MyTest> myTestClass = MyTest.class;// 获得要调用的方法,output是要调用的方法名字,new Class[]{}为所需要的参数。空则不是这种Method method = myTestClass.getMethod("output", new Class[]{});// 是否有类型为MyAnnotation的注解if (method.isAnnotationPresent(MyAnnotation.class)){// 获得注解MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);// 调用注解的内容System.out.println(annotation.hello());System.out.println(annotation.world());}System.out.println("----------------------------------");// 获得所有注解。必须是runtime类型的Annotation[] annotations = method.getAnnotations();for (Annotation annotation : annotations){// 遍历所有注解的名字System.out.println(annotation.annotationType().getName());}}
}
输出:
Hello,Beijing
Hello,world
@Documented
Documented注解表明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。 如果一个类型声明被注释了文档化,它的注释成为公共API的一部分。
@Component
@Component and @Bean do two quite different things, and shouldn’t be confused.
@Component (and @Service and @Repository) are used to auto-detect and auto-configure beans using classpath scanning. There’s an implicit one-to-one mapping between the annotated class and the bean (i.e. one bean per class). Control of wiring is quite limited with this approach, since it’s purely declarative.
@Bean is used to explicitly declare a single bean, rather than letting Spring do it automatically as above. It decouples the declaration of the bean from the class definition, and lets you create and configure beans exactly how you choose.
看了一些文章,这两个注解可以互换使用,但还有一些使用目的进行区别的。
@Component被用在要被自动扫描和装配的类上。
@Bean主要被用在方法上,来显式声明要用生成的类。
现在项目上,本工程中的类,一般都使用@Component来生成bean。在把通过web service取得的类,生成Bean时,使用@Bean和getter方法来生成bean。
@Conditional
Conditional 是由 SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下。
package org.springframework.context.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {Class<? extends Condition>[] value();}
Conditional 注解类里只有一个 value 属性,需传入一个 Condition 类型的数组,我们先来看看这个 Condition 接口长什么样。
package org.springframework.context.annotation;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
public interface Condition {boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}
其中,matches() 方法传入的参数 ConditionContext 是专门为 Condition 而设计的一个接口类,可以从中获取到Spring容器的以下对象信息。
当一个 Bean 被 Conditional 注解修饰时,Spring容器会对数组中所有 Condition 接口的 matches() 方法进行判断,只有当其中所有 Condition 接口的 matches()方法都为 ture 时,才会创建 Bean 。
自定义Conditional
接下来,我们将以一个国际化 I18n Bean 动态创建为例(根据配置中的 i18n.lang 属性值来动态地创建国际化 I18n Bean),对如何使用 Conditional 注解进行简单举例:
当 i18n.lang=zh_CN 就创建中文 I18nChs Bean,
当 i18n.lang=en_US 就创建英文 I18nEng Bean。
创建好的两个 Condition 实现类 I18nChsCondition 和 I18nEngCondition 代码如下。
public class I18nChsCondition extends SpringBootCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {String lang = context.getEnvironment().getProperty("i18n.lang");ConditionOutcome outCome = new ConditionOutcome("zh_CN".equals(lang), "i18n.lang=" + lang);return outCome;}
}
public class I18nEngCondition extends SpringBootCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {String lang = context.getEnvironment().getProperty("i18n.lang");ConditionOutcome outCome = new ConditionOutcome("en_US".equals(lang), "i18n.lang=" + lang);return outCome;}}
I18n 接口定义如下。
public interface I18n {// 获取 name 属性的值String i18n(String name);}
I18n 接口的两个实现类 I18nChs 和 I18nEng 定义如下。
@Component
@Conditional(I18nChsCondition.class)
public class I18nChsImpl implements I18n {Map<String, String> map = new HashMap<String, String>() {private static final long serialVersionUID = 1L;{put("lang", "中文");}};@Overridepublic String i18n(String name) {return map.get(name);}
}
@Component
@Conditional(I18nEngCondition.class)
public class I18nEngImpl implements I18n {Map<String, String> map = new HashMap<String, String>() {private static final long serialVersionUID = 1L;{put("lang", "English");}};@Overridepublic String i18n(String name) {return map.get(name);}}
在启动类中添加测试代码代码如下。
@SpringBootApplication
public class App
{public static void main( String[] args ){ConfigurableApplicationContext context = SpringApplication.run(App.class, args);I18n i18n = context.getBean(I18n.class);System.out.println(i18n.getClass().getName());System.out.println(i18n.i18n("lang"));context.close();}
}
配置 application.properties 内容如下:
# language : zh_CN/Chinese,en_US/America
i18n.lang=zh_CN
运行程序,打印结果:
com.pengjunlee.condition.I18nChsImpl
中文
配置 application.properties 内容如下:
# language : zh_CN/Chinese,en_US/America
i18n.lang=en_US
再次运行程序,打印结果:
com.pengjunlee.condition.I18nEngImpl
English
为了书写和调用方便,我们还可以把上面的条件定义成注解,以 I18nChsCondition 为例,定义代码如下。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(I18nChsCondition.class)
public @interface I18nChs {}
将 I18nChs 注解添加到 I18nChsImpl 上。
@Component
@I18nEng
public class I18nChsImpl implements I18n {//内容同上,此处省略}
SpringBoot 扩展注解
从上面的示例不难看出,如果要使用我们自定义条件类实现起来还是有点小麻烦的,不过比较庆幸的是, SpringBoot 在 Conditional 注解的基础上已经提前为我们定义好了一系列功能丰富的注解,我们可以直接使用。
接下来我们使用 ConditionalOnProperty 注解来实现上面的国际化示例。
仅需修改 I18nChsImpl 和 I18nEngImpl 两个实现组件类,其他代码不变,程序执行结果与之前相同。
@Component
@ConditionalOnProperty(name = "i18n.lang", havingValue = "zh_CN", matchIfMissing = true)
public class I18nChsImpl implements I18n {//内容同上,此处省略}
@Component
@ConditionalOnProperty(name = "i18n.lang", havingValue = "en_US", matchIfMissing = false)
public class I18nEngImpl implements I18n {//内容同上,此处省略}