欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring——基于注解的配置

程序员文章站 2022-03-26 14:13:09
...

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——基于注解的配置

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——基于注解的配置

与我们预想的一致,报了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——基于注解的配置

程序不再报错了,但是name属性的值为null,因为我们没有在XML配置中为其注入值。

那么到这里就不难理解@Required注解的作用了。@Required注解用于注解属性的setter方法,如果一个属性的setter方法被@Required注解,则表示在XML配置中,该属性一定要注入值,否则会报异常。

1.@Autowired注解

@Autowired 注解对在哪里和如何完成自动连接提供了更多的细微的控制。

1.0 setter方法中的@Autowired

当Spring遇到一个在setter方法中使用的@Autowired注解,它会通过byType的方法自动为该属性注入值。不理解自动注入的,可以参考Spring——装配Bean

我们在上例中进行扩充,首先新建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——基于注解的配置

运行结果完全正确,说明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——基于注解的配置

此时如果我们为@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——基于注解的配置

至此,@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——基于注解的配置

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——基于注解的配置

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注解的结合即可。