在 Spring IoC 概览 中我们提到过,除了配置文件,Spring 还能通过注解进行 Bean 的依赖管理。
传统的 XML 注入 bean 存在以下缺点:
如果所有内容都配置在 XML 文件中,长久以往配置文件将会十分庞大;如果按照需求拆分配置文件,那么 XML 文件又会变得很多,可维护性会降低;
开发中 Java 和 XML 之间不断切换,是一件麻烦的事。
由此引入注解与 Bean 紧密结合,既大大减少了配置文件的体积,又增加了 Bean 的可读性和内聚性。
关于注解的配置也是在 Bean 的定义 XML 文件上,使用 <context:annotation-config/>
标记:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > <context:annotation-config /> </beans >
或者使用 <context:component-scan base-package="xxx" />
开启扫描功能:
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8"?> <beans ... > <context:component-scan base-package ="xxx" /> </beans >
以下篇幅介绍几个常见的 Spring 注解。
非类级别的注解 包括修饰字段、构造函数和方法的注解们。
@Required 应用于 bean 某个属性的 setter 中,标明该属性必须在配置文件 xml 中配置,且值不能为 null;否则容器会抛出 BeanInitializationException
。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Student { private Integer age; private String name; @Required public void setAge (Integer age) { this .age = age; } public Integer getAge () { return age; } @Required public void setName (String name) { this .name = name; } public String getName () { return name; } }
示例配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <beans ... > <context:annotation-config /> <bean id ="student" class ="com.example.Student" > <property name ="name" value ="Zara" /> </bean > </beans >
@Autowired 顾名思义,就是自动装配 。能应用于 bean 中某个属性的 setter 方法、非 setter 方法、构造函数和属性上;标记后可以更精确地控制在何处和如何进行自动装配。
@Autowired 默认按 byType 的方式,在容器里查找匹配的 bean;当 Spring 容器中有且只有一个匹配 的 bean 时,完成自动装配。
如果需要 byName 装配,可结合 @Qualifier
使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <beans ... > <context:annotation-config /> <bean id ="textEditor" class ="com.example.TextEditor" > </bean > <bean id ="spellChecker" class ="com.example.SpellChecker" > </bean > </beans >
我们可以看到,上面的配置文件中没有配置任何的 constructor-arg
或者 property
,这就属于非 xml 配置的 IoC。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TextEditor { private SpellChecker spellChecker; @Autowired public void setSpellChecker (SpellChecker spellChecker) { this .spellChecker = spellChecker; } public void spellCheck () { spellChecker.checkSpelling(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TextEditor { @Autowired private SpellChecker spellChecker; public TextEditor () { System.out.println("Inside TextEditor constructor." ); } public void spellCheck () { spellChecker.checkSpelling(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TextEditor { private SpellChecker spellChecker; @Autowired public TextEditor (SpellChecker spellChecker) { System.out.println("Inside TextEditor constructor." ); this .spellChecker = spellChecker; } public void spellCheck () { spellChecker.checkSpelling(); } }
1 2 3 4 5 6 7 8 @Autowired public void prepare (SpellChecker spellChecker, WordCounter counter) { this .spellChecker = spellChecker; this .wordCounter = counter; }
要注意的是,如果需要被注入的 Bean 里面保留着依赖的 <property>
,Spring 会按照配置文件(XML)优先 的原则进行依赖注入,容器会寻找对应依赖中的 getter / setter。 这个时候,如果依赖该 bean 的 .java 文件标记了 @Autowired 而 bean 没有定义 getter / setter,bean 的初始化就会报错。
@Autowired
默认表明依赖是必须的,相当于标记了 @Required
。如果 bean 找不到而允许空值,不抛出异常,可使用 @Autowired(required=false)
关闭默认行为。
1 2 3 4 5 6 7 8 9 10 public class Student { @Autowired(required=false) private Integer age; @Autowired private String name; ... }
@Qualifier 指定注入 Bean 的具体名称(name),能够从多个匹配的相同类型(type)的 bean 中找出想要装配的某个 bean。
存在多个实例时与 @Autowired
结合使用。
示例配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <beans ... > <context:annotation-config /> <bean id ="profile" class ="com.example.Profile" > </bean > <bean id ="student1" class ="com.example.Student" > <property name ="name" value ="Zara" /> <property name ="age" value ="11" /> </bean > <bean id ="student2" class ="com.example.Student" > <property name ="name" value ="Nuha" /> <property name ="age" value ="2" /> </bean > </beans >
使用代码示例:
1 2 3 4 5 6 7 8 public class Profile { @Autowired @Qualifier("student1") private Student student; ... }
不仅如此,同一个接口类的两个实现类 bean 也是通过这样的方法匹配想要的 bean:
定义一个 ICar 接口:
1 2 3 4 public interface ICar { public String getCarModel () ; }
两个实现类:
1 2 3 4 5 6 public class Q5 implements ICar { public String getCarModel () { return "Q5" ; } }
1 2 3 4 5 6 public class A3 implements ICar { public String getCarModel () { return "A3" ; } }
工厂类:
1 2 3 4 5 6 7 public class AudiFactory { @Autowired private ICar car; ... }
配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <beans ... > <context:annotation-config /> <bean id ="audiFactory" class ="com.example.AudiFactory" > </bean > <bean id ="q5" class ="com.example.Q5" > </bean > <bean id ="a3" class ="com.example.A3" > </bean > </beans >
调用 audiFactory 的时候会抛出 NoSuchBeanDefinitionException
:说 No unique bean of type ICar is defined。
因此需要删除其中一个 bean 的定义,或者通过 @Qualifier 指明是哪一个 bean。
@Resource 和 @Autowired 非常相似,作用于 setter 上,使用 name 属性,默认遵循 byName 语义自动装配。
@Resource 没有指定任何内容,默认 byName 匹配,找不到再 byType;
如果指定了 name 或者 type,则 byType 匹配;
如果同时指定了 name 和 type,则根据指定的 name 和 type 去匹配,任何一个不匹配都会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class TextEditor { private SpellChecker spellChecker; @Resource(name= "spellChecker") public void setSpellChecker (SpellChecker spellChecker) { this .spellChecker = spellChecker; } public SpellChecker getSpellChecker () { return spellChecker; } public void spellCheck () { spellChecker.checkSpelling(); } }
注:
@Resource 和 @Autowired 功能是一样的,都需要配置 DI 注解解析器
@Resource 是 JavaEE 的注解,默认按名称(name)注入;@Autowired 是 Spring 的注解,默认按类型(type)注入
因此建议使用 @Resource 来减少与 Spring 之间的耦合。
@PostConstruct & @PreDestroy @PostConstruct
:相当于 xml 配置中的 init-method;@PreDestroy
:相当于 xml 配置中的 destroy-method。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class HelloWorld { private String message; ... @PostConstruct public void init () { System.out.println("Bean is going through init." ); } @PreDestroy public void destroy () { System.out.println("Bean will destroy now." ); } } ctx.registerShutdownHook();
配置:
1 2 3 4 5 6 7 8 9 10 <beans ... > <context:annotation-config /> <bean id ="helloWorld" class ="com.example.HelloWorld" > <property name ="message" value ="Hello World!" /> </bean > </beans >
使用注解可以指定多个初始化 / 销毁方法,而配置文件只能分别指定一个。
添加注解后,在配置文件中,该 bean 的 init-method 和 destroy-method 属性可无需配置。
如配置文件添加了 init-method 和 destroy-method:
如配置的方法相同,则两者行为相同,两者互相覆盖;
如配置方法不同,则先执行注解 的方法,再执行配置的方法。
@Resource、@PostConstruct 和 @PreDestroy 都属于 JavaEE 的 JSR-250(Java Specification Request)注释。
构造 IoC 容器的注解 添加了这些注解,XML 配置文件可以得到真正的简化,做到“无配置”地依赖注入。
@Configuration 表明该类可作为 Spring IoC 容器用来管理 bean 的配置类。 标记了之后就不需要额外的 XML 进行配置了。
类里面的某个方法如果标记了 @Bean
,就会作为 Spring 容器中的 Bean 被注册进去。
@Bean @Bean 是属于标记在方法上面的注解。 以此手动创建一个 Spring Bean 注册到 Spring context。
结合两个注解来看一段示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration public class HelloWorldConfig { @Bean public HelloWorld helloWorld () { return new HelloWorld(); } } ApplicationContext ctx1 = new AnnotationConfigApplicationContext(HelloWorldConfig.class); HelloWorld helloWorld1 = ctx1.getBean(HelloWorld.class); ApplicationContext ctx2 = new AnnotationConfigApplicationContext(); ctx2.register(AppConfig.class, OtherConfig.class, HelloWorldConfig.class, ...); HelloWorldConfig config = ctx2.getBean(HelloWorldConfig.class); HelloWorld helloWorld2 = config.helloWorld(); Assert.assertEquals(helloWorld1, helloWorld2);
以上代码等同于以下配置:
1 2 3 <beans > <bean id ="helloWorld" class ="com.example.HelloWorld" /> </beans >
@Configuration 源码:
1 2 3 4 5 6 7 8 9 10 11 12 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor( annotation = Component.class ) String value () default "" ; boolean proxyBeanMethods () default true ; }
以上代码说明 Spring Configuration 配置类也是 Spring 的一个组件。
bean 的依赖性可以通过构造函数注入接收:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class AppConfig { @Bean public Foo foo () { return new Foo(bar()); } @Bean public Bar bar () { return new Bar(); } }
@Bean 注解可指定任意初始化和销毁的回调,也可指定 bean 的作用域:
1 2 3 4 5 6 7 8 9 @Configuration public class AppConfig { @Bean(initMethod = "init", destroyMethod = "cleanup") @Scope("prototype") public Foo foo () { return new Foo(); } }
其中 @Scope
用来配置 Spring bean 的作用域:
singleton
为单例;
prototype
为原型,每次都会 new 一个新的对象出来。
@Import 根据传入的 class 确定类的全路径名,将其加载进容器中。
1 2 3 4 5 6 7 8 9 @Configuration public class ConfigA { @Bean public A a () { return new A(); } }
1 2 3 4 5 6 7 8 9 10 @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B a () { return new A(); } }
通过 context 初始化的时候,指定 ConfigB.class 即可获得 bean A 和 B。
以下代码会得到两个不同的 Bean:
1 2 3 4 5 6 7 8 9 @Import(A.class) @Configuration public class ConfigA { @Bean public A a () { return new A(); } }
Import 进容器的是 com.xxx.A 全路径名,而 @Bean 引入容器的是 a;虽然它们是同一个类,但是是两个 bean 。
@ConditionalXX 顾名思义,条件注解。被标记了的类或方法要满足了一定的条件之后,相应的 bean 才会被注册进容器中。
Spring 中定义了很多条件注解:
在以下的实例代码中,调用 getBean() 获取 car 的时候会报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Bean @ConditionalOnBean(name = "engine") public Car car () { Car car = new Car(); car.setBrand("Honda" ); car.setEngine(engine()); car.setModel("Civic" ); car.setIteration(11 ); return car; } public Engine engine () { Engine engine = new Engine(); engine.setModel("earth dream" ); engine.setModel("L15C8" ); return engine; }
因为 car 不能被 Spring 注册进容器中,即使通过了 engine() 传入了所需变量,但是所依赖的 bean(@ConditionalOnBean(name = "engine")
)没被初始化,所以 car 初始化失败。
在实际应用中可利用 @ConditionalXX 选择性地注册 bean (比如不同系统用到的不同数据库 driver),减轻 Spring 应用负担。
其它创建对象的注解 这些注解都是标记在类上的,用于表示 Spring 中的组件。
@Component
将某个 Java 类标记为 bean,是所有受 Spring 管理组件的通用形式。
Spring 组件扫描机制能将标记了 @Component 的类拉入应用程序中。
当组件不好归类的时候,我们可以使用它进行标注。
@Controller
将 Java 类标记为 Spring Web MVC 控制器(控制层 Controller 组件)。
同样,被标记的 bean 会被自动导入到 IoC 容器中。
@Service
是 @Component 注解的特化,用于标记业务层的组件。
可在服务层类中使用 @Service 而不是 @Component,它不会对 @Component 注解提供任何其他行为。
@Repository
也是 @Component 注解的特化,用于标记持久层的组件(DAO 类)。
被标记之后,IoC 容器会将该 DAO 导入,并使未经检查的异常有资格转换为 Spring DataAccessException。
其它 @ComponentScan
:扫描并装配指定包下的所有 @Component, @Controller, @Service, @Repository 注解的 Bean 到 IOC 容器中。
默认情况下扫描入口类(XxxApplication.class)同级以及子级包 下的所有文件;
可通过 basePackages
& value
/ basePackageClasses
指定扫描路径
@Primary
:手动创建 Spring Bean 时,如果 IoC 容器发现了多个 bean 候选者,则 @Primary
可以指定当前为默认注入的 bean;否则会抛出异常。
@Lazy
:延迟初始化,表示延迟注入 bean。