Spring注解配置
从 Spring 2.5 开始就可以使用注解来配置依赖注入。使用注解的方式使我们无需在XML中配置一个Bean引用,更加简单和方便。
注解配置默认情况下在Spring中是关闭的,我们需要在配置文件中使用<context:annotation-config/>
激活它。
如下spring-config.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
</beans>
一旦激活注解配置后,我们就可以在代码中使用注解来进行依赖注入。其中下面是几个重要的注解:
-
@Required
注解应用于bean属性的setter方法 -
@Autowired
注解可以应用到bean属性的setter方法,非setter方法,构造函数和属性 -
@Qualifier
,通过指定确切的将被引用的bean,@Autowired
和@Qualifier
注解可以用来删除混乱 - JSR-250 Annotations,Spring支持JSR-250的基础的注解,其中包括了
@Resource
,@PostContruct
和@PreDestory
注解
0.@Required
注解
@Required
注解应用于bean属性的setter方法,它表示受影响的bean属性在配置时必须放在XML配置文件中,否则容器就会抛出一个BeanInitializationException
异常。
下面我们举一个例子来说明。我们在IDEA中创建Maven工程,并且引入Spring的几个核心库,包括:spring-core,spring-beans和spring-context。最后的项目工程如下:
pom.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gavin</groupId>
<artifactId>spring-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring-version>5.0.4.RELEASE</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
</dependencies>
</project>
我们在domain包下创建一个Person
类,如下:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old!");
}
}
可以看到,我们使用@Required
注解了属性name
和age
的两个setter方法setName()
和setAge()
,这表示我们在使用XML为Person
类注入属性时必须注入这两个属性。
假如我们的spring-config.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean id="person" class="com.gavin.domain.Person">
<property name="age" value="20"/>
</bean>
</beans>
可以看到,我们在为Person
注入属性时,只注入了age
属性,那么创建Main
类测试运行:
package com.gavin.test;
import com.gavin.domain.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
Person person = applicationContext.getBean("person", Person.class);
person.sayHello();
}
}
结果如下:
与我们预想的一致,报了BeanInitializationException
异常。如果我们把name
属性的@Required
注解去掉再运行:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old!");
}
}
程序不再报错了,但是name
属性的值为null,因为我们没有在XML配置中为其注入值。
那么到这里就不难理解@Required
注解的作用了。@Required
注解用于注解属性的setter方法,如果一个属性的setter方法被@Required
注解,则表示在XML配置中,该属性一定要注入值,否则会报异常。
1.@Autowired
注解
@Autowired
注解对在哪里和如何完成自动连接提供了更多的细微的控制。
1.0 setter方法中的@Autowired
当Spring遇到一个在setter方法中使用的@Autowired
注解,它会通过byType
的方法自动为该属性注入值。
我们在上例中进行扩充,首先新建domain对象Dog
,如下:
package com.gavin.domain;
public class Dog {
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
然后我们在Person
类中添加Dog
属性,表示Person
拥有Dog
,并添加setter方法,然后为setter
方法添加@Autowired
注解:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
private Dog dog;
@Autowired
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old! I have a dog : " + dog);
}
}
接着,我们在spring-config.xml
中装配Dog
对象,如下:
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean id="person" class="com.gavin.domain.Person">
<property name="age" value="20"/>
<property name="name" value="Gavin"/>
</bean>
<bean id="dog" class="com.gavin.domain.Dog">
<property name="name" value="旺财"/>
<property name="color" value="黄色"/>
</bean>
</beans>
可以看到,我们在XML文件中,并没有为Person
对象注入Dog
的值,但是此时我们运行程序,得到结果:
运行结果完全正确,说明Spring自动为Person
类注入了Dog
属性,这正是@Autowired
注解的作用。
1.1 属性中的@Autowired
注解
我们可以直接在属性上运用@Autowired
注解,这样我们可以无需为该属性写setter方法,Spring会自动为该属性注入值。所以如果我们在属性上运用@Autowired
注解,那么Person
类将变为:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
@Autowired
private Dog dog;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old! I have a dog : " + dog);
}
}
此时再运行程序,我们可以发现如上面的例子一样,Spring会为我们自动装配Person
类中的Dog
属性的值。
1.2 构造方法中的@Autowired
注解
此外,我们也可以在构造方法上使用@Autowired
注解,假如我们在Person
类添加构造方法,并为其添加Dog
参数,在构造方法中初始化Dog
属性,如下:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
private Dog dog;
@Autowired
public Person(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old! I have a dog : " + dog);
}
}
程序的运行结果依然是正确的。Spring为我们自动关联了Dog
对象。
1.3 @Autowired
属性的(required = false)选项
默认情况下,@Autowired
注解意味着依赖是必须的,它类似于@Required
注释,然而,你可以使用@Autowired
的(required=false)
选项关闭默认行为。
假设我们在spring-config.xml
中删除Dog
的配置,如下:
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean id="person" class="com.gavin.domain.Person">
<property name="age" value="20"/>
<property name="name" value="Gavin"/>
</bean>
</beans>
然后我们在Person
类中使用@Autowired
注解Dog
属性,如下:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
@Autowired
private Dog dog;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old! I have a dog : " + dog);
}
}
运行程序,得到异常结果:
此时如果我们为@Autowired
加上(required = false)
属性,则表示Dog
属性不是必须的:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
@Autowired(required = false)
private Dog dog;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old! I have a dog : " + dog);
}
}
再次运行程序,我们就可以得到正常的运行结果,只是Dog
属性为null
而已:
至此,@Autowired
注解的用法就介绍完毕了。
2.@Qualifier
注解
有时候会出现这样一种情况,当我们创建多个具有相同类型的bean时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,我们可以结合使用@Qualifier
和@Autowired
注解通过指定哪一个真正的bean将会被装配来消除混乱。
我们依然使用上面的例子来介绍。
首先Dog
类仍然如下:
package com.gavin.domain;
public class Dog {
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
此时,我们在spring-config.xml文件中配置两个Dog
,dog1
和dog2
,如下所示:
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<bean id="person" class="com.gavin.domain.Person">
<property name="age" value="20"/>
<property name="name" value="Gavin"/>
</bean>
<bean id="dog1" class="com.gavin.domain.Dog">
<property name="name" value="大黄"/>
<property name="color" value="黄色"/>
</bean>
<bean id="dog2" class="com.gavin.domain.Dog">
<property name="name" value="小黑"/>
<property name="color" value="黑色"/>
</bean>
</beans>
假设我们Person
类没变,其包含Dog
属性,并且使用@Autowired
指定其自动装配,那么此时编译器就会报错,因为我们配置了两个Dog
对象,它不知道具体要装配哪个Dog
对象。所以我们可以使用@Qualifier
注解来指定装配的是具体哪一个对象。如下:
package com.gavin.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Required;
public class Person {
private String name;
private int age;
@Autowired
@Qualifier("dog1")
private Dog dog;
public String getName() {
return name;
}
@Required
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Required
public void setAge(int age) {
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old! I have a dog : " + dog);
}
}
我们指定了其装配的是dog1
,所以程序运行结果如下:
3.JSR-250注解
在2.5版本中,Spring框架的核心(core)现在支持以下JSR-250注解:
@PostContruct
@PreDestory
@Resource
3.0 @PostConstruct
和@PreDestroy
注解
在spring-config.xml文件配置中,为了定义一个bean的安装和卸载,我们可以使用init-method
和destroy-method
参数声明。init-method
属性指定了一个方法,该方法在bean实例化之后会被立即调用。同样地,destroy-method
指定了一个方法,该方法在一个bean从容器中删除之前被调用。
我们可以使用@PostConstruct
注解作为初始化回调方法的一个替代,@PreDestroy
注解作为销毁回调方法的一个替代。
我们在上例的基础上做扩充,首先我们创建HelloService
类,如下:
package com.gavin.service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class HelloService {
private String message;
public HelloService() {
System.out.println("Inside the constructor");
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
@PostConstruct
public void init(){
System.out.println("构造方法正在被执行...");
}
@PreDestroy
public void destory(){
System.out.println("Bean正在被销毁...");
}
}
我们通过@PostConstruct
注解和@PreDestory
注解指定了初始化回调方法和销毁回调方法。
接着,我们在XML配置文件中装配HelloService
,如下:
<bean id="helloService" class="com.gavin.service.HelloService">
<property name="message" value="Hello, World!"/>
</bean>
将主方法更改如下:
package com.gavin.test;
import com.gavin.domain.Person;
import com.gavin.service.HelloService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
// Person person = applicationContext.getBean("person", Person.class);
// person.sayHello();
HelloService helloService = applicationContext.getBean("helloService", HelloService.class);
System.out.println(helloService.getMessage());
((ClassPathXmlApplicationContext) applicationContext).registerShutdownHook();
}
}
这里,我们需要注册一个关闭钩registerShutdownHook()
方法,该方法在 AbstractApplicationContext
类中被声明。这将确保一个完美的关闭并调用相关的销毁方法。
运行结果如下:
3.1 @Resource
注解
我们可以在字段中或者setter方法中使用@Resource
注解,它使用一个name
属性,该属性以一个bean名称的形式被注入,也就是说,它遵循byName
形式的自动装配。
比如我们在上面的例子中使用了@Autowired
与@Qualifier
注解的结合:
public class Person {
private String name;
private int age;
@Autowired
@Qualifier("dog1")
private Dog dog;
// ...
}
在这里,我们也可以使用@Resource
注解,效果是一样的,写法如下:
public class Person {
private String name;
private int age;
@Resource(name = "dog1")
private Dog dog;
// ...
}
@Resource
与@Autowired
注解的用法很类似,它们的区别如下:
-
@Autowired
注解为Spring提供的注解,只按照byType
方式注入,默认情况下,它要求依赖对象必须存在,如果允许为null
,可以设置它的required
属性为false
,如果我们想按照byName
方式来装配,可以结合@Qualifier
注解一起使用; @Resource
为J2EE提供的注解,它有两个重要的属性:name
和type
。而默认情况下,@Resource
注解按照byName
的方式来装配。@Resource
的装配顺序是这样的:
- 如果同时指定了
name
和type
,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。 - 如果指定了
name
,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。 - 如果指定了
type
,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。 - 如果既没有指定
name
,又没有指定type
,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
个人认为,统一使用Spring提供的注解比较好,也就是我们更偏向于使用
@Autowired
注解,特殊情况下使用@Autowired
与@Qualifier
注解的结合即可。