Sping:五、自动装配Bean和使用注解开发

7、自动装配Bean

Spring 中有三种装配方式:

  1. XML 显式配置:使用配置文件;
  2. Java 显式配置:实现零配置文件;
  3. 自动装配:自动注入属性值
    • 可以减少 setter 和构造方法的使用;
    • 可以随着对象的发展而更新:例如需要向类增加新的依赖,无需修改配置即可自动注入依赖。

7.1、搭建环境

“一个人有一只宠物。”

Pet

public class Pet {

    private String type;

    public Pet() {
    }

    public Pet(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "Pet{" +
                "type='" + type + '\'' +
                '}';
    }

    public String getType() {
        return type;
    }
}

Person

public class Person {

    private String name;

    private Pet pet;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", pet=" + pet +
                '}';
    }

    public String getName() {
        return name;
    }

    public Pet getPet() {
        return pet;
    }
}

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="pet" class="indi.jaywee.pojo.Pet"/>

    <bean id="person" class="indi.jaywee.pojo.Person">
        <property name="pet" ref="pet"/>
    </bean>
</beans>

Test

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    Person person = context.getBean("person", Person.class);

    System.out.println(person);
}

测试结果

Person 对象创建成功,Pet 装配成功。

Sping:五、自动装配Bean和使用注解开发

7.1、XML自动装配

使用 XML 配置元数据时,使用 Bean 元素的 autowire 属性开启自动装配。

7.1.1、自动装配模式

Sping:五、自动装配Bean和使用注解开发

7.1.2、测试

去掉 ref 属性,并增加注册几个 Bean,测试使用 autowire 进行自动装配。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="pet" class="indi.jaywee.pojo.Pet">
        <constructor-arg name="type" value="myPet"/>
    </bean>
    <bean id="pet1" class="indi.jaywee.pojo.Pet">
        <constructor-arg name="type" value="myPet1"/>
    </bean>
    <bean id="pet2" class="indi.jaywee.pojo.Pet">
        <constructor-arg name="type" value="myPet2"/>
    </bean>

    <bean id="person" class="indi.jaywee.pojo.Person" autowire=""/>
</beans>

1、no

不会为 Person 装配 pet 属性。

Sping:五、自动装配Bean和使用注解开发

2、byName

匹配到 idpetBean 并装配。

Sping:五、自动装配Bean和使用注解开发

  • 如果去掉 idpetBean,即没有与属性同名的Bean,则不会装配。

    Sping:五、自动装配Bean和使用注解开发

3、byType

匹配到多个 Bean ,报错。

Sping:五、自动装配Bean和使用注解开发

  • 只保留一个 Bean,则自动装配相应的Bean:

    Sping:五、自动装配Bean和使用注解开发

4、constructor

Person 中没有 Pet 的构造器。

Sping:五、自动装配Bean和使用注解开发

  • Person 类中增加一个构造器

    public Person(Pet pet) {
        this.pet = pet;
    }  
    
    • 如果只匹配到一个Bean,则自动装配;
    • 如果匹配到0个到多个Bean,则不装配。

7.2、注解自动装配

在使用注解之前,需要引入以下配置。

  1. 导入 context 配置;

  2. 设置注解支持:<context:annotation-config/>

    <?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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
    </beans>
    
  • 相比没有使用注解的 XML 文件,增加了有关 context 的一个 xmlns 和两个 xsi 标记。

    Sping:五、自动装配Bean和使用注解开发

7.2.1、@Autowired

自动装配

  • 可以在属性、setter、构造器使用;

工作机制:byType ,再 byName

  1. Spring 查找容器中是否存在属性类型Bean
  2. 如果容器中只存在一个匹配的 Bean ,即 Bean 只在 XML 中配置了一次,则自动装配;
  3. 如果容器中存在多个匹配的 Bean,即 BeanXML 中配置了多次,则匹配属性名
    • 存在与属性同名的 Bean,自动装配;
    • 不存在同名 Bean,可以添加注解 @Qualifier(value = "beanId") 来匹配 Bean

通过测试,来解释以上工作机制。

测试

  • 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.xsd">
    
        <context:annotation-config/>
    
        <bean id="pet" class="indi.jaywee.pojo.Pet">
            <constructor-arg name="type" value="myPet"/>
        </bean>
        <bean id="pet1" class="indi.jaywee.pojo.Pet">
            <constructor-arg name="type" value="myPet1"/>
        </bean>
        <bean id="person" class="indi.jaywee.pojo.Person">
            <property name="name" value="jaywee"/>
        </bean>
    
        <bean id="person" class="indi.jaywee.pojo.Person"/>
    </beans>
    
  • pet 属性上使用 @Autowired 注解

    public class Person {
    
        private String name;
    
        @Autowired
        private Pet pet;
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", pet=" + pet +
                    '}';
        }
    
        public String getName() {
            return name;
        }
    
        public Pet getPet() {
            return pet;
        }
    }
    
  • 测试结果:

    Sping:五、自动装配Bean和使用注解开发

    1. pet 属性不为 null,自动装配成功;
    2. pettype 值为 myPet ,说明注入了 idpetBean 对象;

进一步测试

  1. 测试byType:删除 idpetpet1Bean ,保留 idpet2Bean

    • 结果:注入了 idpet2Bean

      Sping:五、自动装配Bean和使用注解开发

  2. 测试byName:删除 idpetBean ,保留 idpet1pet2Bean

    • 匹配到两个 Bean ,并且无法匹配属性名。

      Sping:五、自动装配Bean和使用注解开发

    • 解决:在 pet 属性上使用 @Qualifier(value = "beanId") 注解,匹配容器中对应的 Bean

      @Autowired
      @Qualifier(value = "pet1")
      private Pet pet;
      

      Sping:五、自动装配Bean和使用注解开发

7.2.2、@Resource

Java 提供的注解,工作机制类似@Autowired,可以自行测试。

相比 @Autowired ,拥有name属性,可以指定具体的Bean。

@Resource(name = "pet1")
private Pet pet;

8、使用注解开发

8.1、关于注解

注解实现原理:反射机制和动态代理方法

  1. 注解注入比 XML 注入先执行,因此 XML 配置会覆盖注解配置;

  2. Spring4 后,要使用注解开发,必须要有 aopjar 包支持

    Sping:五、自动装配Bean和使用注解开发

  3. 要使用注解开发,需要设置配置信息

    <?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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package=""/>
    
    </beans>
    

8.1.1、注解的类型

  1. 类级别的注解:添加在类上面。
    • @Component
    • 元注解为 @Component 的注解
      • @Repository
      • @Controller
      • @Service
      • 自定义注解
    • Java EE6@ManagedBean@Named
  2. 类内部的注解:添加在类内部的属性或方法上。
    • @Autowired
    • @Resource
    • @Value
    • ...

8.1.2、工作原理

IOC 容器根据 XML 配置,进行以下操作:

  1. 扫描.class文件,将包含类级别注解Bean 注册到 BeanFactory 中;
  2. 注册相应注解的后置处理器(post-processor,注册的同时将其实例化,用于处理类内部的注解
  3. 将其处理器放到 BeanFactorybeanPostProcessors 列表中;
  4. 创建 Bean 的过程中,属性注入或者初始化 Bean 时,调用相应的处理器进行处理。

8.1.3、后置处理器

后置处理器(post-processor)主要用于处理类内部的注解。

  1. 要使用注解的话,必须要在 XML 中注册相应的处理器,作为支持;
  2. 基于第1点说明,如果要使用多种注解,相应的就要注册很多个处理器。这样的话非常麻烦,因此可以使用<context:annotation-config/>标签来隐式注册它们。

<context:annotation-config/>标签

隐式注册了5个后置处理器,对应支持的类内部的注解才生效。

后置处理器 处理注解 参考文档
ConfigurationClass
PostProcessor
@Configuration ConfigurationClassPostProcessor
AutowiredAnnotationBean
PostProcessor
@Autowired
@Value
@Inject
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBean
PostProcessor
@PostConstruct、@PreDestroy
@Resource
@WebServiceRef
@EJB
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBean
PostProcessor
@PersistenceUnit
@PersistenceContext
PersistenceAnnotationBeanPostProcessor
EventListenerMethod
Processor
@EventListener EventListenerMethodProcessor

<context:component-scan>标签

扫描指定包下的 Bean ,被扫描的 Bean 中包含的类级别的注解才会生效,Bean 才会被注册到容器中。

  1. base-package 属性:用于指定扫描的包路径;
  2. 使用该标签,隐式启用了<context:annotation-config/>标签。即使用当前标签时,不再需要引入<context:annotation-config/>标签。

8.1.4、注解小结

  1. 注解有2种类型:类级别、类内部;
  2. IOC容器会自动注册类级别注解Bean,前提是有<context:component-scan>配置支持;
  3. 要使用类内部的注解,需要有相应处理器的支持,处理器需要在 XML 中注册;
  4. 通过<context:annotation-config/>标签来隐式注册处理器;
  5. 要让类级别注解生效,就要<context:component-scan>配置支持,而这个标签又隐式启动了<context:annotation-config/>
  6. 在使用时,我们只需要引入相关配置,以及<context:component-scan>标签。

8.2、环境搭建

先搭建一个没有使用注解的环境,再引入注解开发。

8.1.1、实体类

Major

public class Major {
    /**
     * 专业名
     */
    private String name;

    public Major() {
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Major{" +
                "name='" + name + '\'' +
                '}';
    }
}

Student

public class Student {
    /**
     * 姓名
     */
    private String name;
    /**
     * 专业
     */
    private Major major;

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" + "\n" +
                "name='" + name + '\'' + "\n" +
                "major=" + major + "\n" +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setMajor(Major major) {
        this.major = major;
    }
}

8.1.2、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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="major" class="indi.jaywee.pojo.Major">
        <property name="name" value="Java"/>
    </bean>

    <bean id="student" class="indi.jaywee.pojo.Student">
        <property name="name" value="jaywee"/>
        <property name="major" ref="major"/>
    </bean>

</beans>

8.1.3、测试

JUnit

@Test
public void test() {
    // 实例化容器
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 获取Bean
    Student student = context.getBean("student", Student.class);

    System.out.println(student);
}

运行结果

Bean 对象被 IOC 容器成功创建并组装了,基本环境搭建完成。

Sping:五、自动装配Bean和使用注解开发

8.2.4、引入注解

基本环境已搭建完成,现在引入支持注解的配置,来进行注解开发。

注意扫描包的路径

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="indi.jaywee/>

</beans>

当前运行结果

Sping:五、自动装配Bean和使用注解开发

8.3、注册Bean

将包含类级别注解Bean 注册到容器中,由容器管理。

相当于在XML文件中使用<bean/>标签,可以使用 value 属性设置 BeanName

  • @Component:注册 Java Bean
  • 元注解为 @Component 的注解
    • @Repository:注册 DAO
    • @Controller:注册 Controller
    • @Service:注册 Service

8.3.1、@Component

Java Bean上方添加 @Component 注解,将其注册到容器中。

@Component
public class Student {
	...
}
  1. 自动注册的 Bean 名:开头小写的类名,即 student

    Student student = context.getBean("student", Student.class);
    
  2. 可以通过 value 属性,显式设置 Bean 名,value可以省略:

    @Component(value = "myStudent")
    public class Student {
        ...
    }
    
    @Component("myStudent") // 直接填写Bean名
    public class Student {
        ...
    }
    
    Student student = context.getBean("myStudent", Student.class);
    
  3. 结果

    Sping:五、自动装配Bean和使用注解开发

8.3.2、@Component的衍生注解

即元注解为 @Component 的注解:

  • @Repository:只能用于DAO
  • @Controller:用于控制层
  • @Service:用于业务层

添加注解

在类上方添加注解,将其注册到容器中。

@Repository
public class StudentDaoImpl implements StudentDao{
    ...
}

@Controller
public class StudentController {
    ...
}

@Service
public class StudentServiceImpl implements StudentService{
	...
}

Bean名

  1. 默认 Bean 名:开头小写的非限定类名

    StudentDao dao = context.getBean("studentDaoImpl", StudentDaoImpl.class);
    
    StudentController controller = context.getBean("studentController", StudentController.class;
                                                   
    StudentService service = context.getBean("studentServiceImpl", StudentService.class);
    
  2. 可以通过 value 属性,显式设置 Bean。也可以省略 value,直接填写 Bean 名:

    @Component(value = "daoImpl")
    public class Student {
        ...
    }
    
    @Controller("controller") // 直接填写Bean名
    public class StudentController {
        ...
    }
    
    @Component(value = "daoImpl") // 直接填写Bean名
    public class Student {
        ...
    }
    
  3. 显式设置 Bean 名后,默认的 Bean 名失效

    StudentDao dao = context.getBean("daoImpl", StudentDaoImpl.class);
    
    StudentController controller = context.getBean("controller", StudentController.class);
    
    StudentDao dao = context.getBean("daoImpl", StudentDaoImpl.class);
    

8.3.3、@Bean

用于方法,将方法返回的对象注册到容器中。

@Bean
public Major getMajor1() {
    ...
}
  1. 自动注册的 Bean 名:方法名,即 getMajor1

    Major major = context.getBean("getMajor1", Major.class);
    
  2. 可以通过 value 属性,显式设置 Bean 名:

    @Bean(value = "m1")
    public Major getMajor1() {
    	...
    }
    
    Major major = context.getBean("m1", Major.class);
    

8.4、装配Bean

8.4.1、基本数据类型

@Value

用于基本数据类型的属性的注入。

@Value("jaywee")
private String name;

@Value("Java")
private String name;

Sping:五、自动装配Bean和使用注解开发

8.4.2、引用数据类型

@Autowired

用于引用数据类型(其他 Bean依赖)的注入。

@Autowired
private Major major;

Sping:五、自动装配Bean和使用注解开发

@Resource

@Autowired 的效果类似,可以使用 name 属性指定具体的 Bean

具体有关这 2个注解的使用,参照 7、自动装配Bean

8.5、微调注解

@Qualifier

用于属性、构造方法和方法的参数,为其选择一个特定的 Bean,通常与 @Autowired 结合使用。

例如:会为 pet 属性注入 idpet1Bean

public class Person {
    @Resource
    @Qualifier(value = "pet1")
    private Pet pet;
    
    public Person(@Qualifier(value = "pet1")Pet pet) {
        this.pet = pet;
    }
}

等价于使用 @Resource 及其 name 属性,并且推荐使用这种方式。

public class Person {
    @Resource(name = "pet1")
    private Pet pet;
    
    public Person(@Qualifier(value = "pet1")Pet pet) {
        this.pet = pet;
    }
}

@Nullable

用于标记属性或方法参数,表示该属性可以为 null

@Nullable
private Pet pet;

也可以通过 @AutowiredRequired = false实现。

@Autowired(required = false)
private Pet pet;

8.6、小结

Sping:五、自动装配Bean和使用注解开发

XML与注解

  1. XML 适用于任何场合,维护简单;
  2. 注解在一定程度上可以替代 XML 配置文件,但是维护相对复杂;
  3. 最佳实践:
    • XML 管理 Bean:即手动在 XML 中注册 Bean,而不是用类级别注解;
    • 注解装配 Bean:即使用类内部注解,自动注入属性值。
上一篇:关于单链表的增删改查方法的递归实现(JAVA语言实现)


下一篇:获取spring上下文的bean 工具类