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。最后的项目工程如下:

spring 注解配置requestmapping 无效_Spring

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注解了属性nameage的两个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();
    }
}

结果如下:

spring 注解配置requestmapping 无效_注解_02

与我们预想的一致,报了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!");
    }
}

spring 注解配置requestmapping 无效_spring_03

程序不再报错了,但是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 注解配置requestmapping 无效_注解_04

运行结果完全正确,说明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);
    }
}

运行程序,得到异常结果:

spring 注解配置requestmapping 无效_注解_05

此时如果我们为@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而已:

spring 注解配置requestmapping 无效_spring_06

至此,@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文件中配置两个Dogdog1dog2,如下所示:

<?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,所以程序运行结果如下:

spring 注解配置requestmapping 无效_Annotation_07

3.JSR-250注解

在2.5版本中,Spring框架的核心(core)现在支持以下JSR-250注解:

  • @PostContruct
  • @PreDestory
  • @Resource

3.0 @PostConstruct@PreDestroy注解

在spring-config.xml文件配置中,为了定义一个bean的安装和卸载,我们可以使用init-methoddestroy-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类中被声明。这将确保一个完美的关闭并调用相关的销毁方法。

运行结果如下:

spring 注解配置requestmapping 无效_spring_08

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提供的注解,它有两个重要的属性:nametype。而默认情况下,@Resource注解按照byName的方式来装配。@Resource的装配顺序是这样的:
  • 如果同时指定了nametype,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
  • 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  • 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

个人认为,统一使用Spring提供的注解比较好,也就是我们更偏向于使用@Autowired注解,特殊情况下使用@Autowired@Qualifier注解的结合即可。