一、什么是注解 注解,主要提供一种机制,这种机制容许程序员在编写代码的同时能够直接编写元数据。 二、介绍 何为注解?--->元数据:描述数据自身的数据。 注解就是代码的元数据,他们包含了代码自身的信息。 被注解的代码并不会直接被注解影响。这只会向第三系统提供关于本身的信息以用于不一样的需求。 注解会被编译至class文件中,并且会在运行时被处理程序提取出来用于业务逻辑。固然,建立在运行时不可用的注解也是可能的,甚至能够建立只在源文件中可用,在编译时不可用的注解。 三、消费器 注解自己并不包含任何功能逻辑,它们也不会影响本身注解的代码。 注解消费器:它们是利用被注解代码并根据注解信息产生不一样行为的系统或者应用程序。 例如,在Java自带的内建注解(元注解)中,消费器是执行被注解代码的JVM。还有其余稍后谈到的其余例子,例如JUnit,消费器是读取,分析被注解代码的JUnit处理程序,它还能够决定测试单元和方法执行顺序。 消费器使用Java中的反射机制来读取和分析被注解的源代码。使用的主要的包有:java.lang, java.lang.reflect。 四、注解语法和注解元素 声明一个注解须要使用“@”做为前缀,这便向编译器说明,该元素为注解。例如: @Annotation public void annotatedMehod() { ... } 上述的注解名称为Annotation,它正在注解annotatedMethod方法。编译器会处理它。注解能够以键值对的形式持有有不少元素,即注解的属性。 @Annotation( info = "I am an annotation", counter = "55" ) public void annotatedMehod() { ... } 若是注解只包含一个元素(或者只须要指定一个元素的值,其它则使用默认值),能够像这样声明: @Annotation("I am an annotation") public void annotatedMehod() { ... } 就像咱们看到的同样,若是没有元素须要被指定,则不须要括号。多个注解可使用在同一代码上,例如类: @ Annotation (info = "U a u O") @ Annotation2 class AnnotatedClass { ... } 一些java自己提供的开箱即用的注解,咱们称之为内建注解。也能够定义你本身的注解,称之为子定义注解。 五、在什么地方使用 注解基本上能够在Java程序的每个元素上使用:类,域,方法,包,变量,等等。 自Java8,诞生了经过类型注解的理念。在此以前,注解是限于在前面讨论的元素的声明上使用。今后,不管是类型仍是声明均可以使用注解,就像: @MyAnnotation String str = "danibuiza"; 六、使用案例 注解能够知足许多要求,最广泛的是: **向编译器提供信息:注解能够被编译器用来根据不一样的规则产生警告,甚至错误。一个例子是Java8中@FunctionalInterface注解,这个注解使得编译器校验被注解的类,检查它是不是一个正确的函数式接口。 **文档:注解能够被软件应用程序计算代码的质量例如:FindBugs,PMD或者自动生成报告,例如:用来Jenkins, Jira,Teamcity。 **代码生成:注解可使用代码中展示的元数据信息来自动生成代码或者XML文件,一个不错的例子是JAXB。 **运行时处理:在运行时检查的注解能够用作不一样的目的,像单元测试(JUnit),依赖注入(Spring),校验,日志(Log4j),数据访问(Hibernate)等等。 七、内建注解 Java语言自带了一系列的注解。 Meta注解:它们的目的注解其余注解,而且包含关于其它注解的信息。 **@Retention:这个注解注在其余注解上,并用来讲明如何存储已被标记的注解。这是一种元注解,用来标记注解并提供注解的信息。可能的值是: (1)SOURCE:代表这个注解会被编译器忽略,并只会保留在源代码中。 (2)CLASS:代表这个注解会经过编译驻留在CLASS文件,但会被JVM在运行时忽略,正由于如此,其在运行时不可见。 (3)RUNTIME:表示这个注解会被JVM获取,并在运行时经过反射获取。 **@Target:这个注解用于限制某个元素能够被注解的类型。例如: (1)ANNOTATION_TYPE 表示该注解能够应用到其余注解上 (2)CONSTRUCTOR 表示可使用到构造器上 (3)FIELD 表示可使用到域或属性上 (4)LOCAL_VARIABLE表示可使用到局部变量上。 (5)METHOD可使用到方法级别的注解上。 (6)PACKAGE可使用到包声明上。 (7)PARAMETER可使用到方法的参数上 (8)TYPE可使用到一个类的任何元素上。 **@Documented:被注解的元素将会做为Javadoc产生的文档中的内容。注解都默认不会成为成为文档中的内容。这个注解能够对其它注解使用。 **@Inherited:在默认状况下,注解不会被子类继承。被此注解标记的注解会被全部子类继承。这个注解能够对类使用。 **@Deprecated:说明被标记的元素不该该再度使用。这个注解会让编译器产生警告消息。可使用到方法,类和域上。相应的解释和缘由,包括另外一个可取代的方法应该同时和这个注解使用。 **@SuppressWarnings:说明编译器不会针对指定的一个或多个缘由产生警告。例如:若是咱们不想由于存在还没有使用的私有方法而获得警告能够这样作: **@SuppressWarnings( "unused") private String myNotUsedMethod(){ ... } 一般,编译器会由于没调用该方而产生警告; 用了注解抑制了这种行为。该注解须要一个或多个参数来指定抑制的警告类型。 **@Override:向编译器说明被注解元素是重写的父类的一个元素。在重写父类元素的时候此注解并不是强制性的,不过能够在重写错误时帮助编译器产生错误以提醒咱们。好比子类方法的参数和父类不匹配,或返回值类型不一样。 **@SafeVarargs:断言方法或者构造器的代码不会对参数进行不安全的操做。在Java的后续版本中,使用这个注解时将会令编译器产生一个错误在编译期间防止潜在的不安全操做。 八、Java 8 与注解 Java8带来了一些优点,一样注解框架的能力也获得了提高。 @Repeatable注解,关于类型注解的声明,函数式接口注解@FunctionalInterface(与Lambdas结合使用)。 @Repeatable:说明该注解标识的注解能够屡次使用到同一个元素的声明上。 看一个使用的例子。首先咱们创造一个能容纳重复的注解的容器: /** * Container for the {@link CanBeRepeated} Annotation containing a list of values */ @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) public @interface RepeatedValues { CanBeRepeated[] value(); } 接着,建立注解自己,而后标记@Repeatable @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) @Repeatable( RepeatedValues.class ) public @interface CanBeRepeated { String value(); } 最后,咱们能够这样重复地使用: @CanBeRepeated( "the color is green" ) @CanBeRepeated( "the color is red" ) @CanBeRepeated( "the color is blue" ) public class RepeatableAnnotated { } 若是咱们尝试去掉@Repeatable @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) public @interface CannotBeRepeated { String value(); } @CannotBeRepeated( "info" ) /* * if we try repeat the annotation we will get an error: Duplicate annotation of non-repeatable type * * @CannotBeRepeated. Only annotation types marked * * @Repeatable can be used multiple times at one target. */ // @CannotBeRepeated( "more info" ) public class RepeatableAnnotatedWrong { } 咱们会获得编译器的错误信息: Duplicate annotation of non-repeatable type 自Java8开始,咱们能够在类型上使用注解。因为咱们在任何地方均可以使用类型,包括 new操做符,casting,implements,throw等等。注解能够改善对Java代码的分析而且保证更加健壮的类型检查。这个例子说明了这一点: @SuppressWarnings( "unused" ) public static void main( String[] args ) { // type def @TypeAnnotated String cannotBeEmpty = null; // type List<@TypeAnnotated String> myList = new ArrayList<String>(); // values String myString = new @TypeAnnotated String( "this is annotated in java 8" ); } // in method params public void methodAnnotated( @TypeAnnotated int parameter ) { System.out.println( "do nothing" ); } 全部的这些在Java8以前都是不可能的。 @FunctionalInterface:这个注解表示一个函数式接口元素。函数式接口是一种只有一个抽象方法(非默认)的接口。编译器会检查被注解元素,若是不符,就会产生错误。例子以下: // implementing its methods @SuppressWarnings( "unused" ) MyCustomInterface myFuncInterface = new MyCustomInterface() { @Override public int doSomething( int param ) { return param * 10; } }; // using lambdas @SuppressWarnings( "unused" ) MyCustomInterface myFuncInterfaceLambdas = ( x ) -> ( x * 10 ); } @FunctionalInterface interface MyCustomInterface { /* * more abstract methods will cause the interface not to be a valid functional interface and * the compiler will thrown an error:Invalid '@FunctionalInterface' annotation; * FunctionalInterfaceAnnotation.MyCustomInterface is not a functional interface */ // boolean isFunctionalInterface(); int doSomething( int param ); } 这个注解能够被使用到类,接口,枚举和注解自己。它的被JVM保留并在runtime可见,这个是它的声明: @Documented @Retention(value=RUNTIME) @Target(value=TYPE) public @interface FunctionalInterface 九、自定义注解 正如咱们以前屡次说起的,能够定义和实现自定义注解。本章咱们即将探讨。 首先,定义一个注解: public @interface CustomAnnotationClass 这样建立了一个新的注解类型名为 CustomAnnotationClass。关键字:@interface说明这是一个自定义注解的定义。 以后,你须要为此注解定义一对强制性的属性,保留策略和目标。还有一些其余属性能够定义,不过这两个是最基本和重要的。 因此,咱们为自定义的注解设置属性: @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE ) public @interface CustomAnnotationClass implements CustomAnnotationMethod 在保留策略中 RUNTIME 告诉编译器这个注解应该被被JVM保留,而且能经过反射在运行时分析。经过 TYPE 咱们又设置该注解能够被使用到任何类的元素上。 以后,咱们定义两个注解的成员: @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE ) public @interface CustomAnnotationClass { public String author() default "danibuiza"; public String date(); } 以上咱们仅定义了默认值为“danibuiza”的 author 属性和没有默认值的date属性。咱们应强调全部的方法声明都不能有参数和throw子句。这个返回值的类型被限制为以前提过的字符串,类,枚举,注解和存储这些类型的数组。 如今咱们能够像这样使用刚建立的自定义注解: @CustomAnnotationClass( date = "2014-05-05" ) public class AnnotatedClass { ... } 在另外一种相似的用法中咱们能够建立一种注解方法的注解,使用Target METHOD: @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.METHOD ) public @interface CustomAnnotationMethod { public String author() default "danibuiza"; public String date(); public String description(); } 这种注解可使用在方法声明上: @CustomAnnotationMethod( date = "2014-06-05", description = "annotated method" ) public String annotatedMethod() { return "nothing niente"; } @CustomAnnotationMethod( author = "friend of mine", date = "2014-06-05", description = "annotated method" ) public String annotatedMethodFromAFriend() { return "nothing niente"; } 有不少其它属性能够用在自定义注解上,可是 目标 (Target)和 保留策略(Retention Policy)是最重要的两个。 十、提取注解 Java反射API包含了许多方法来在运行时从类,方法或者其它元素获取注解。接口AnnotatedElement包含了大部分重要的方法,以下: getAnnotations(): 返回该元素的全部注解,包括没有显式定义该元素上的注解。 isAnnotationPresent(annotation): 检查传入的注解是否存在于当前元素。 getAnnotation(class): 按照传入的参数获取指定类型的注解。返回null说明当前元素不带有此注解。 class 经过java.lang.Class被实现,java.lang.reflect.Method 和 java.lang.reflect.Field,因此能够基本上被和任何Java元素使用。 如今,咱们将看一个怎么读取注解的例子: public static void main( String[] args ) throws Exception { Class<AnnotatedClass> object = AnnotatedClass.class; // Retrieve all annotations from the class Annotation[] annotations = object.getAnnotations(); for( Annotation annotation : annotations ) { System.out.println( annotation ); } // Checks if an annotation is present if( object.isAnnotationPresent( CustomAnnotationClass.class ) ) { // Gets the desired annotation Annotation annotation = object.getAnnotation( CustomAnnotationClass.class ); System.out.println( annotation ); } // the same for all methods of the class for( Method method : object.getDeclaredMethods() ) { if( method.isAnnotationPresent( CustomAnnotationMethod.class ) ) { Annotation annotation = method.getAnnotation( CustomAnnotationMethod.class ); System.out.println( annotation ); } } } 输出以下: @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=friend of mine, date=2014-06-05, description=annotated method) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=danibuiza, date=2014-06-05, description=annotated method) 在这个程序中,咱们能够看到 getAnnotations()方法来获取全部某个对象(方法,类)上的全部注解的用法。展现了怎样使用isAnnotationPresent()方法和getAnnotation()方法检查是否存在特定的注解,和如何获取它。 十一、注解中的继承 注解在Java中可使用继承。这种继承和普通的面向对象继承几乎没有共同点。 若是一个注解在Java中被标识成继承,使用了保留注解@Inherited,说明它注解的这个类将自动地把这个注解传递到全部子类中而不用在子类中声明。一般,一个类继承了父类,并不继承父类的注解。这彻底和使用注解的目的一致的:提供关于被注解的代码的信息而不修改它们的行为。 咱们经过一个例子更清楚地说明。首先,咱们定义一个自动继承的自定义注解。 @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface InheritedAnnotation { } 有一个父类名为:AnnotatedSuperClass,已经被自定义的注解给注解上了: @InheritedAnnotation public class AnnotatedSuperClass { public void oneMethod() { } } 一个子类继承父类: @InheritedAnnotation public class AnnotatedSuperClass { public void oneMethod() { } } 子类 AnnotatedSubClass 展现了自动继承的注解 @InheritedAnnotation。咱们看到下面的代码经过 isAnnotationPresent() 方法测试出了当前注解。 <pre>System.out.println( "is true: " + AnnotatedSuperClass.class.isAnnotationPresent( InheritedAnnotation.class ) ); System.out.println( "is true: " + AnnotatedSubClass.class.isAnnotationPresent( InheritedAnnotation.class ) );</pre> <pre> 输出以下: is true: true is true: true 咱们能够看到子类虽然并无声明注解,但仍是被自动地注解上了。 若是咱们尝试注解在一个接口中: @InheritedAnnotation public interface AnnotatedInterface { public void oneMethod(); } 一个实现了该接口的类: public class AnnotatedImplementedClass implements AnnotatedInterface { @Override public void oneMethod() { } } 通过 isAnnotationPresent() 方法测试: System.out.println( "is true: " + AnnotatedInterface.class.isAnnotationPresent( InheritedAnnotation.class ) ); System.out.println( "is true: " + AnnotatedImplementedClass.class.isAnnotationPresent( InheritedAnnotation.class ) ); 结果以下: is true: true is true: false 这个结果说明继承注解和接口在一块儿使用时,接口中的注解在实现类中:仅仅被忽略。实现类并不继承接口的注解;接口继承仅仅适用于类继承。正如 AnnotatedSubClass。 @Inheriated注解仅在存在继承关系的类上产生效果,在接口和实现类上并不工做。这条一样也适用在方法,变量,包等等。只有类才和这个注解连用。 注解不能继承注解,若是你尝试这么作了,就会获得编译器抛出的错误: Annotation type declaration cannot have explicit superinterfaces 十二、使用注解的知名类库 一些类库如:JAXB, Spring Framework, Findbugs, Log4j, Hibernate, Junit。它们使用注解来完成代码质量分析,单元测试,XML解析,依赖注入和许多其它的工做。 12.1. Junit 这个框架用于完成Java中的单元测试。自JUnit4开始,注解被普遍应用,成为Junit的设计的主干之一。 基本上,JUnit处理程序经过反射读取类和测试套件,按照在方法上,类上的注解顺序地执行它们。固然还有一些用来修改测试执行的注解。其它注解都用来执行测试,阻止执行,改变执行顺序等等。 用到的注解至关多,可是咱们将会看到最重要的几个: @Test:这个注解向JUnit说明这个被注解的方法必定是一个可执行的测试方法。这个注解只能标识在方法上,而且被JVM保留至运行时。 @Test public void testMe() { //test assertions assertEquals(1,1); } 从这个例子能够看到咱们如何在JUnit中使用这类注解。 @Before:这个注解用来向JUnit说明被标记的方法应该在全部测试方法以前被执行。这对于在测试以前设置测试环境和初始化很是有用。一样只适用于方法上: @Before public void setUp() { // initializing variables count = 0; init(); } @After:这个注解用来向JUnit说明被注解的方法应该在全部单元测试以后执行。这个注解一般用来销毁资源,关闭,释放资源或者清理,重置等工做。 @After public void destroy() { // closing input stream stream.close(); } @Ignore:这个方法用来向JUnit说明被注解的方法应该不被看成测试单元执行。即便它被注解成为一个测试方法,也只能被忽略。 @Ignore @Test public void donotTestMe() { count = -22; System.out.println( "donotTestMe(): " + count ); } 这个方法可能在在开发调试阶段使用,但一旦开始进入发布阶段变须要将被忽略的代码去掉。 @FixMethodOrder:指定执行的顺序,正常状况下Junit处理程序负责它按照彻底随机的没法预知的顺序执行。当全部的测试方法都相互独立的时候,不推荐使用这个注解。可是,当测试的场景须要测试方法按照必定规则的时候,这个注解就派上用场了。 查看源代码打印帮助 @FixMethodOrder( MethodSorters.NAME_ASCENDING ) public class JunitAnnotated 12.2. Hibernate ORM Hibernate多是一个用得最普遍的对象关系映射类库。它提供了对象模型和关系型数据库的映射框架。使用注解做为设计的一部分。 下面的代码段使用了@Entity和@Table。这两个是用来向消费器(Hibernate处理程序)说明被注解的类是一个实体类,以及它映射的SQL表名。实际上,这个注解仅仅是指明了主表,还能够有说明字表的注解。 @Entity @Table( name = "hibernate_annotated" ) public class HibernateAnnotated 接下来的代码段展现了如何向Hibernate处理程序说明被标记的元素是表的主键,并映射名为“id”的列,而且主键是自动生成的。 @Id @GeneratedValue @Column( name = "id" ) private int id; 为了指定标准的SQL表列名,咱们能够写以下注解: @Column( name = "description" ) private String description; 这说明被标记的元素映射的是这个类所对应的表中名为“description”的一列。 12.3. Spring MVC Spring是个被普遍使用的Java企业级应用框架。其一项重要的特性就是在Java程序使用依赖注入。 Spring使用注解做为XML(Spring的早期版本使用的基于XML配置)的一种替代方式。如今二者都是可行的。你可使用XML文件或者注解配置你的项目。在我看来二者都各有优点。 咱们将在下例中展现两个可用注解: @Component public class DependencyInjectionAnnotation { private String description; public String getDescription() { return description; } @Autowired public void setDescription( String description ) { this.description = description; } } 在代码片断中咱们能够找到两个使用在了类和方法上的注解。 @Component:说明被标记的元素,在本例中是一个类,是一个自动检测的目标。这意味着被注解的类,将会被Spring容器实例化并管理。 @Autowired:Spring容器将会尝试经过类型(这是一种元素匹配机制)使用这个set方法来自动装配。此注解也可使用在构造器和属性上,Spring也会根据注解的地方不一样采起不一样的操做。 12.4. Findbugs 这是一个用来测量代码质量,并提供一系列可能提升它的工具。它会根据预约义(或者自定义)的违反规则来检查代码。Findbugs提供一系列注解来容许开发者来改变默认行为。 它主要使用反射读取代码(和包含的注解)并决定基于它们,应该采起什么行为。 一个列子是 edu.umd.cs.findbugs.annotations.SuppressFBWarnings 注解期待一种违反规定的键值并把它看成参数。这很是像 java.lang.SuppressWarnings。被用来向 Findbugs 说明当执行代码分析的时候,忽略指定的违反规则。 这是一个例子: @SuppressFBWarnings( "HE_EQUALS_USE_HASHCODE" ) public class FindBugsAnnotated { @Override public boolean equals( Object arg0 ) { return super.equals( arg0 ); } } 这个类已经重现了 Object 的 equals 方法,可是并无重写 hashCode 方法。这一般会致使问题,由于 hashCode 和 equals 二者应该被同时重写,不然在使用这个对象做为 HashMap 的key值的时候会致使错误。因此,Findbugs会在违反规则报告中创造一个错误条目。 若是注解 @SuppressFBWarnings 设置了 HE_EQUALS_USE_HASHCODE 值之后,处理程序就不会在抛出这类型的错误了。 Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode() Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode() 这个类重写了 equals 方法,可是没有重写 hashCode 方法,继承自 java.lang.Object 的 hashCode 方法(返回每一个对象被JVM赋予的特定值)。所以,这个类几乎违反了相同的对象必须 hashcode 相同的一致性。 若是你认为这个类的实例不会插入到哈希表,推荐的作法是像这样实现 hashCode 方法: public int hashCode() { assert false : "hashCode not designed"; return 42; // any arbitrary constant will do } Rank: Troubling (14), confidence: High Pattern: HE_EQUALS_USE_HASHCODE Type: HE, Category: BAD_PRACTICE (Bad practice) 这个错误包含了该问题的一种解释而且提示如何处理它。在这种状况下,解决方案应该是实现 hashCode 方法。 12.5. JAXB JAXB是一个用来相互转换和映射XML文件与Java对象的类库。实际上,这个类库与标准JRE一块儿提供,不须要任何额外的下载和配置。能够直接经过引入 java.xml.bind.annotation 包下的类直接使用。 JAXB使用注解来告知处理程序(或者是JVM)XML文件与代码的相互转化。例如,注解能够用来在代码上标记XML节点,XMl属性,值等等。咱们将看到一个例子: 首先,咱们声明一个类说明它应该是XML文件中的一个节点: import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlType( propOrder = { "brand", "model", "year", "km" } ) @XmlRootElement( name = "Car" ) class Car ... 使用注解@XmlType,@XmlRoootElement。它们用来告知 JAXB 处理程序 Car 这个类在转换后,将会转换成为XML中的一个节点。这个@XmlType说明了属性在XML中的顺序。JAXB将会基于这些注解执行合适的操做。 除了分离的 getter 和 setter 属性,不再须要向这个类中添加其它东西来完成转换。如今咱们须要一个消费器程序来执行转换成XML: Car car = new Car(); car.setBrand( "Mercedes" ); car.setModel( "SLK" ); car.setYear( 2011 ); car.setKm( 15000 ); Car carVW = new Car(); carVW.setBrand( "VW" ); carVW.setModel( "Touran" ); carVW.setYear( 2005 ); carVW.setKm( 150000 ); /* init jaxb marshaler */ JAXBContext jaxbContext = JAXBContext.newInstance( Car.class ); Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); /* set this flag to true to format the output */ jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true ); /* marshaling of java objects in xml (output to standard output) */ jaxbMarshaller.marshal( car, System.out ); jaxbMarshaller.marshal( carVW, System.out ); 程序产生的输入以下: 查看源代码打印帮助 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Car> <brand>Mercedes</brand> <model>SLK</model> <year>2011</year> <km>15000</km> </Car> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Car> <brand>VW</brand> <model>Touran</model> <year>2005</year> <km>150000</km> </Car> 1三、小结 基本上,注解都是做为包含代码信息的元数据而被标记到代码中。它们不会改变或者影响代码的任何意义,而是被第三方称为消费器的程序经过反射的方式使用。 注解是Java中一种分析元数据的强大机制,能够在不一样的程序中担任不一样的做用,例如校验,依赖注入,单元测试。