美文网首页
Spring-IOC基础

Spring-IOC基础

作者: Tian_Peng | 来源:发表于2020-01-14 15:08 被阅读0次

1.概述

本文简要介绍Spring IOC的概念和用法。

IOC全称是Inversion of Control,即控制反转
所谓 IOC ,就是由 Spring IOC 容器来负责维护对象的生命周期(创建、初始化、销毁)和对象之间的关系,而不用我们自己来new一个对象和管理它们。

2.Spring容器

在基于Spring的应用中,你的应用对象生存于Spring容器中,如下图:

容器是Spring框架的核心,Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联,这些对象更加简单干净、易于理解、易于重用并且更易于单元测试。

Spring的容器不是只有一个,Spring自带了多个容器实现,可以归纳为2种类型:

  • bean工厂
    bean工程由org.springframework.beans.factory.BeanFactory接口定义,它是最简单的容器,提供了基本的DI支持
  • 应用上下文
    由org.springframework.context.ApplicationContext接口定义,它是基于BeanFactory构建,并提供应用框架级别的服务

虽然我们可以在bean工程和应用上下文之间任选一种,但bean工厂对大多数应用来说往往太低级了,因此应用上下文比bean工厂更受欢迎,用此我们会把精力集中在应用上下文的使用上。

3.Spring应用上下文

Spring通过应用上下文(ApplicationContext)装载bean的定义并把它们组装起来,Spring应用上下文全权负责对象的创建和组装。
Spring自带了多种应用上下文的实现,它们之间的区别仅仅在于如何加载配置,常见的应用上下文有以下几种:

  • AnnotationConfigApplicationContext
    从一个或多个基于Java的配置类中加载Spring应用上下文
  • AnnotationConfigWebApplicationContext
    从一个或多个基于Java的配置类中加载Spring Web应用上下文
  • ClassPathXmlApplicationContext
    从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源
  • FileSystemXmlApplicationContext
    从文件系统下的一个或多个XML配置文件中加载上下文定义
  • XmlWebApplicationContext
    从Web应用下的一个或多个XML配置文件中加载上下文定义

例如:

ApplicationContext context = new FileSystemXmlApplicationContext("E:/pandora.xml");
ApplicationContext context = new ClassPathXmlApplicationContext("pandora.xml");
ApplicationContext context = new AnnotationConfigApplicationContext(com.tp.pandora.config.PandoraConfig.class);

使用FileSystemXmlApplicationContext和使用ClassPathXmlApplicationContext的区别在于:FileSystemXmlApplicationContext在指定的文件系统路径下查找pandora.xml文件;而ClassPathXmlApplicationContext是在所有的类路径下(包含JAR文件)下查找pandora.xml文件。

应用上下文准备就绪后,我们就可以调用上下文的getBean()方法从Spring容器中获取bean。

4.bean的生命周期

在传统的Java应用中,bean的生命周期很简单:当我们使用关键字new进行bean实例化,这个bean就可以使用了,一旦bean不再被使用则由Java自动进行垃圾回收。

相比之下,Spring容器中的bean的生命周期就相对复杂多了,正确理解Spring bean的生命周期很重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程,下图展示了bean装载到Spring应用上下文的一个典型的生命周期过程:

在bean准备就绪之前,bean工厂执行了若干启动步骤:

  • Spring对bean进行实例化
  • Spring将值和bean的引用注入到bean对应的属性中
  • 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法
  • 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  • 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法将bean所在的应用上下文的引用传进来
  • 如果bean实现了BeanPostProcesser接口,Spring将调用它们的postProcessBeforeInitialization()方法
  • 如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法
  • 如果bean实现了BeanPostProcesser接口,Spring将调用它们的postProcessAfterInitialization()方法
  • 此时bean已经准备就绪,可以被应用程序使用,它们将一直驻留在应用上下文中,直到该应用上下文被销毁
  • 如果bean实现了DisposableBean接口,Spring将调用它的destroy()方法,同样如果bean使用destroy-method声明了销毁方法,该方法也会被调用

5.声明bean

在spring中声明一个bean,将这个bean交给spring容器管理,可以有如下3种方式:

  • 在xml中显式声明一个bean
  • 在Java中显式声明一个bean(JavaConfig)
  • 通过注解自动发现
5.1在xml中声明一个bean

例如我们现在有个Course类,我们想用spring中声明一个由spring容器管理的Course:

package com.tp.beans.xml;

/**
 * FileName: Course
 * Author:   TP
 * Description:课程
 */
public class Course {

    public Course() {
        System.out.println("无参构造:Course被实例化");
    }

    public Course(String id, String name, int durations) {
        this.id = id;
        this.name = name;
        this.durations = durations;
        System.out.println("带参构造:Course被实例化");
    }

    /**
     * 课程ID
     */
    private String id;

    /**
     * 课程名称
     */
    private String name;

    /**
     * 课程时长(单位天)
     */
    private int durations;

    //==========getter/setter==========

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getDurations() {
        return durations;
    }

    public void setDurations(int durations) {
        this.durations = durations;
    }

    @Override
    public String toString() {
        return "Course{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", durations=" + durations +
                '}';
    }
}

我们就可以在spring的配置文件applicationContext.xml中声明这个bean,注意下面的配置文件包含了整章教程的配置信息,对于当前测试内容,请集中关注这一段配置:

<bean id="xmlCourse1" class="com.tp.beans.xml.Course">
        <property name="name" value="Java"/>
        <property name="durations" value="24"/>
        <property name="id" value="222"/>
 </bean>

完整的applicationContext.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" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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="com.tp"/>

    <!--############################构造器注入############################-->
    <!--Spring xml声明bean-->
    <!--构造器注入:构造器参数有List/Set情形-->
    <bean id="xmlStudent1" class="com.tp.beans.xml.Student">
        <constructor-arg name="name" value="xmlStudent1"/>
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="courses">
            <list>
                <ref bean="xmlCourse1"/>
                <ref bean="xmlCourse2"/>
            </list>
        </constructor-arg>
    </bean>
    <!--构造器注入:构造器参数有Map<String, Object>情形-->
    <bean id="xmlMapTestBean" class="com.tp.beans.xml.MapTestBean">
        <constructor-arg name="map">
            <map>
                <entry key="key1" value="123"/>
                <entry key="key2" value-ref="xmlCourse1"/>
            </map>
        </constructor-arg>
    </bean>


    <!--############################属性注入############################-->
    <!--属性注入:字面值常量-->
    <bean id="xmlCourse1" class="com.tp.beans.xml.Course">
        <property name="name" value="Java"/>
        <property name="durations" value="24"/>
        <property name="id" value="222"/>
    </bean>
    <!--属性注入:字面值常量-->
    <bean id="xmlCourse2" class="com.tp.beans.xml.Course">
        <property name="name" value="Python"/>
        <property name="durations" value="25"/>
        <property name="id" value="111"/>
    </bean>
    <!--属性注入:属性有bean情形-->
    <bean id="xmlUser1" class="com.tp.beans.componentscan.User">
        <property name="name" value="xmlUser1"/>
        <property name="book" ref="javaConfigBook"/>
    </bean>
    <!--属性注入:属性有数组情形-->
    <bean id="xmlUser2" class="com.tp.beans.componentscan.User">
        <property name="name" value="xmlUser2"/>
        <property name="book" ref="javaConfigBook"/>
        <property name="favorites">
            <array>
                <value>篮球</value>
                <value>电影</value>
                <value>唱歌</value>
            </array>
        </property>
    </bean>
    <!--属性注入:使用P-命名空间-->
    <bean id="xmlUser3" class="com.tp.beans.componentscan.User" p:name="xmlUser3" p:book-ref="javaConfigBook">
        <property name="favorites">
            <array>
                <value>篮球</value>
                <value>电影</value>
                <value>唱歌</value>
            </array>
        </property>
    </bean>

    <!--属性注入,属性是List<Courses>情形-->
    <bean id="xmlStudent2" class="com.tp.beans.xml.Student">
        <property name="name" value="xmlStudent2"/>
        <property name="courses">
            <list>
                <ref bean="xmlCourse1" />
                <ref bean="xmlCourse2" />
            </list>
        </property>
    </bean>

    <!--测试多环境-->
    <!--<beans profile="dev">
        <bean id="dataSource"
              class="com.tp.beans.profile.DataSource"
              p:userName="root"
              p:passWord="tp123456"
              p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev"/>
    </beans>

    <beans profile="test">
        <bean id="dataSource"
              class="com.tp.beans.profile.DataSource"
              p:userName="root"
              p:passWord="tp123456"
              p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_test"/>
    </beans>

    <beans profile="prod">
        <bean id="dataSource"
              class="com.tp.beans.profile.DataSource"
              p:userName="root"
              p:passWord="tp123456"
              p:url="jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod"/>
    </beans>-->
</beans>

测试:

package com.tp;

import com.tp.beans.xml.Course;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * FileName: Main
 * Author:   TP
 * Description:
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config/applicationContext.xml");
        Course course = (Course) ctx.getBean("xmlCourse1");
        System.out.println(">>>>" + course);
    }
}

我们通过spring上下文提供的getBean(String name)方法可以从spring容器中获取一个指定名称的bean
效果:

注意:由于项目代码为了讲解本章内容,还声明了很多其他的bean,所以大家可以看到控制台打印出很多信息。
大家可以暂时不关注那些控制台信息,而只关注我们本次测试执行对应的控制台信息即可,下面所有的测试同理。

5.2通过Spring的注解声明一个bean

Spring为了我们提供了@Controller、@Service、@Repository、@Component等注解来声明一个 bean,他们之间并没有什么区别,只不过@Controller、@Service、@Repository在语意上更加明确,而@Component更加通用,日常开发中建议能优先@Controller、@Service、@Repository,@Component次之,保证语意更明确,让人一目了然。
为了演示Spring注解声明bean,我们定义了一个User类:

package com.tp.beans.componentscan;

import com.tp.beans.javaconfig.Book;
import com.tp.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * FileName: User
 * Author:   TP
 * Description:用户类,用来测试注解扫描方式声明bean
 */
@Component("componentUser")
public class User {

    public User() {
        System.out.println("无参构造:User被实例化");
    }

    // 构造器注入
    // @Autowired
    public User(Book book) {
        System.out.println("带参构造:User被实例化");
        this.book = book;
    }

    private Integer id;
    private String name;
    private Integer age;
    private String[] favorites;
    private Book book;
    
    // 接口注入
    @Autowired
    private AnimalService animalService;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String[] getFavorites() {
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

    public Book getBook() {
        return book;
    }

    // Setter注入
    // @Autowired
    public void setBook(Book book) {
        this.book = book;
    }

    // 非Setter注入
    // @Autowired
    public void injectBook(Book book) {
        this.book = book;
    }

    public AnimalService getAnimalService() {
        return animalService;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", favorites=" + Arrays.toString(favorites) +
                ", book=" + book +
                '}';
    }
}

当我们使用注解时,还需要开启注解扫描,开启注解驱动的方式有2种(见下文6.1.1)我们选择其中一种形式,即在applicationContext.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 https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.tp" />
</beans>

Spring会扫描com.tp包及所有子包中带有@Controller、@Service、@Repository、@Component注解的类,并将此类初始化到Spring容器中进行管理。
为了后续的测试方便,我们采用Spring-Junit测试,测试准备如下,项目引入spring-test和junit的jar包,编写一个测试基础类用于指定测试环境下的Spring配置,其他的测试类继承此BaseTestCase即可:

//指定测试用例的运行器 这里是指定了Junit4
@RunWith(SpringJUnit4ClassRunner.class)
//指定Spring的配置文件 /为classpath下
@ContextConfiguration(locations = {"classpath:config/*.xml"})
public class BaseTestCase extends AbstractJUnit4SpringContextTests {
}
public class ComponentScanBeanTest extends BaseTestCase {

    /**
     * 最简单的基于注解扫描的JavaBean声明
     */
    @Test
    public void simpleComponentScanStatement() {
        User user = (User) applicationContext.getBean("componentUser");
        System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
        System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
    }
}

结果:

5.3通过JavaConfig声明一个bean

当随着JAVA EE 5.0的发布,其中引入了一个非常重要的特性——Annotations(注解),注解是源代码的标签,这些标签可以在源代码层进行处理或通过编译器把它熔入到class文件中。
Java config是指基于java配置的spring,传统的Spring一般都是基本xml配置的,后来spring3.0新增了许多java config的注解,特别是spring boot,基本都是清一色的java config。
Spring使用注解来描述Bean的配置与采用XML相比,因类注解是在一个类源代码中,可以获得类型安全检查的好处,并且可以良好的支持重构。
常见的java config的注解:

  • @Configuration
    在类上打上这一标签,表示这个类是配置类
  • @ComponentScan
    相当于xml的<context:componentscan basepakage="xxxx">
  • @Bean
    bean的定义,相当于xml的
    <bean id="objectMapper" class="org.codehaus.jackson.map.ObjectMapper" />
  • @EnableWebMvc
    相当于xml的<mvc:annotation-driven>
  • @ImportResource
    相当于xml的<import resource="applicationContext-cache.xml">
  • @PropertySource
    spring 3.1开始引入,它是基于java config的注解,用于指定读取的properties文件
  • @Profile
    spring3.1开始引入,一般用于多环境配置,激活时可用@ActiveProfiles注解,@ActiveProfiles("dev")
    等同于xml配置:
    <beans profile="dev"> <bean id="beanname" class="com.tp.xxx"/> </beans>

为了演示JavaConfig我们定义了一个Book类如下:

package com.tp.beans.javaconfig;

/**
 * FileName: Book
 * Author:   TP
 * Description: Book,用来测试JavaConfig方式声明bean
 */
public class Book {

    public Book() {
        System.out.println("无参构造:Book被实例化");
    }

    private Integer id;
    private String name;
    private Double price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

并且定义一个AutoConfig类帮助我们声明bean:

package com.tp.autoconfig;

import com.tp.beans.condition.Cat;
import com.tp.beans.javaconfig.Book;
import com.tp.beans.componentscan.User;
import com.tp.beans.profile.DataSource;
import com.tp.conditon.LinuxCondition;
import com.tp.conditon.MacCondition;
import com.tp.conditon.WindowsCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

/**
 * FileName: AutoConfig
 * Author:   TP
 * Date:     2020-01-13 13:58
 * Description: JavaConfig声明bean
 */
@Configuration
public class AutoConfig {


    @Bean("javaConfigBook")
    public Book book() {
        Book book = new Book();
        book.setId(222);
        book.setPrice(33.88);
        book.setName("简书");
        return book;
    }

    //======================JavaConfig的构造器注入======================
    @Bean
    public User javaConfigUserConstructorInject(Book book) {
        User user = new User(book);
        user.setId(666);
        user.setAge(33);
        user.setName("Tony老师");
        return user;
    }

    //======================JavaConfig的Setter注入======================
    @Bean("javaConfigUser")
    public User user(Book book) {
        User user = new User();
        user.setId(4);
        user.setAge(18);
        user.setName("Tom老师");
        //setter注入
        user.setBook(book);
        return user;
    }


    //======================用于多环境测试bean声明======================
    @Bean("dataSource")
    @Profile("dev")
    DataSource devDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUserName("root");
        dataSource.setUserName("tp123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_dev");
        return dataSource;
    }

    @Bean("dataSource")
    @Profile("test")
    DataSource testDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUserName("root");
        dataSource.setUserName("tp123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_test");
        return dataSource;
    }

    @Bean("dataSource")
    @Profile("prod")
    DataSource prodDataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setUserName("root");
        dataSource.setUserName("tp123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbcStudy_prod");
        return dataSource;
    }

    //======================用于条件注解测试bean声明======================
    @Bean("cat")
    @Conditional(MacCondition.class)
    Cat macCat(){
        Cat cat = new Cat();
        cat.setName("Mac Cat");
        return cat;
    }

    @Bean("cat")
    @Conditional(WindowsCondition.class)
    Cat windowsCat(){
        Cat cat = new Cat();
        cat.setName("Windows Cat");
        return cat;
    }

    @Bean("cat")
    @Conditional(LinuxCondition.class)
    Cat linuxCat(){
        Cat cat = new Cat();
        cat.setName("Linux Cat");
        return cat;
    }
}

从该类的book()方法中我们定义了一个id为"javaConfigBook"的bean
测试:

@Test
public void simpleJavaConfigBeanStatement(){
    Book book = (Book) applicationContext.getBean("javaConfigBook");
    System.out.println(">>>基于JavaConfig的JavaBean声明:" + book);
}

效果:

6.装配Bean

装配:创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
在Spring中装配bean有多种方式,Spring提供了三种主要的装配机制:

  • 在XML中进行显式配置
  • 在JavaConfigg中进行显式配置
  • 隐式的bean发现机制和自动配置

Spring的不同配置方式是允许互相搭配的,也就是说这三种方式可以兼容并存,我们可以选择使用XML装配一些bean,使用基于Java的配置(JavaConfig)来装配另一些bean,而将剩余的bean让Spring去自动发现。
官方建议尽可能的使用自动配置的机制,显式配置越少越好。

Spring从两个角度来实现自动化装配:

  • 组件扫描(component scanning)
    Spring会自动发现应用上下文中所创建的bean
  • 自动装配(autowiring)
    Spring自动满足bean之间的依赖

组件扫描配合自动装配可以发挥巨大的威力,能够将显式配置降到最少

6.1 隐式的bean发现机制和自动配置:组件扫描装配bean

下面以一个示例展示如何自动化装配bean,首先我们定义一个接口Animal:

public interface Animal {
    void eat();
}

接下来我们写一个Animal的实现类AnimalServiceImpl:

package com.tp.service.impl;

import com.tp.service.AnimalService;
import org.springframework.stereotype.Service;

/**
 * FileName: AnimalServiceImpl
 * Author:   TP
 * Description:
 */
@Service
public class AnimalServiceImpl implements AnimalService {

    @Override
    public void eat() {
        System.out.println("动物在吃饭....");
    }
}

注意我们在AnimalServiceImpl类上使用了@Service注解,这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean,Spring会帮我们创建这个bean。
但是组件扫描默认是不启用的,我们仍然需要显示的配置一下以开启Spring的组件扫描,从而命令Spring去寻找带有@Service注解的类,并为其创建bean

6.1.1如何开启组件扫描呢
  • 1.使用JavaConfig开启组件扫描
    我们先定义一个类:
@Configuration
@ComponentScan
public class ComponentScanConfig {

}

类ComponentScanConfig通过Java代码定义类Spring的装配规则,它使用了@ComponentScan这个注解,这个注解能够在Spring中启用组件扫描。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包(包括子包)
如果你想指定不同的基础包,可以设置@ComponentScan的value属性为你要指定的基础包,例如:

@Configuration
@ComponentScan("com.tp")
public class ComponentScanConfig {
    
}

如果你想更加清晰地表明你所设置的是基础包,可以通过basePackages属性进行配置:

@Configuration
@ComponentScan(basePackages = "com.tp")
public class ComponentScanConfig {
    
}

basePackages其实可以指定多个基础包,只需要将basePackages属性设置为一个数组即可:

@Configuration
@ComponentScan(basePackages = {"com.tp.pandora", "com.tp.athena"})
public class ComponentScanConfig {
    
}

上面的例子中所设置的基础包都是以String类型表示的,可以实现包扫描配置。
但是这种方法是类型不安全(not type-safe)的,如果哪一天我们重构了代码,那么这里指定的基础包就可能出现错误了,其实@ComponentScan还为我们提供了另外一种方法,就是将其指定为包中所包含的类或接口,所指定的类或接口所在的包将会作为组件扫描的基础包,例如:

@Configuration
@ComponentScan(basePackageClasses = {Dog.class, Cat.class})
public class ComponentScanConfig {

}
  • 2.使用XML开启组件扫描
    在Xml配置文件中加入如下代码即可开启组件扫描
<context:component-scan base-package="com.tp" />

6.1.2为组件扫描的bean命名

Spring应用上下文中所有的bean都会给定一个ID,上述代码中虽然我们没有明确的为AnimalServiceImpl类设置ID,但Spring会根据类型为其指定一个ID,规则是类名且首字母小写(本例即为animalServiceImpl)。
如果你想为这个bean设置一个不同的ID,只需要将你期望的ID传递给@Service即可,例如:

@Service("lonelyDog")
public class AnimalServiceImpl implements AnimalService {

    @Override
    public void eat() {
        System.out.println("动物在吃饭....");
    }
}

还有另外一种为bean命名的方式——@Named,@Named是Java依赖注入规范提供的注解。
Spring支持将@Named作为@Component的替代方案,两者之间差距不大,在绝大多数场景二者是可以互相替换的。
但是更推荐使用@Component注解。

@Named("lonelyDog")
public class AnimalServiceImpl implements AnimalService {

    @Override
    public void eat() {
        System.out.println("动物在吃饭....");
    }
}

6.1.3为组件扫描的bean进行自动装配

自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。
为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
对于组件扫描自动装配bean这种方式,Spring可以有3种依赖注入形式:

  • 构造器注入
    @Autowired使用到构造方法上
  • 接口注入
    当我们注入一个接口时,实际注入的是这个接口的实现类
  • 方法注入
    @Autowired使用到非构造方法上
    个别文章中会说成setter注入,个人觉得描述不够准确,因为Spring不仅支持属性的setter方法上进行自动注入,也支持任意非setter方法上进行自动注入,见下文
6.1.3-1 构造器注入

我们改造一下User和类如下,构造器上加入@Autowired注解:

package com.tp.beans.componentscan;

import com.tp.beans.javaconfig.Book;
import com.tp.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * FileName: User
 * Author:   TP
 * Description:用户类,用来测试注解扫描方式声明bean
 */
@Component("componentUser")
public class User {

    public User() {
        System.out.println("无参构造:User被实例化");
    }

    // 构造器注入
    @Autowired
    public User(Book book) {
        System.out.println("带参构造:User被实例化");
        this.book = book;
    }

    private Integer id;
    private String name;
    private Integer age;
    private String[] favorites;
    private Book book;
    
    // 接口注入
    // @Autowired
    private AnimalService animalService;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String[] getFavorites() {
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

    public Book getBook() {
        return book;
    }

    // Setter注入
    // @Autowired
    public void setBook(Book book) {
        this.book = book;
    }

    // 非Setter注入
    // @Autowired
    public void injectBook(Book book) {
        this.book = book;
    }

    public AnimalService getAnimalService() {
        return animalService;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", favorites=" + Arrays.toString(favorites) +
                ", book=" + book +
                '}';
    }
}

User类的构造器上添加类@Autowired注解,这表明当Spring创建User bean的时候会通过这个构造器进行实例化,并且会在Spring容器中找一个Book类型的bean,传入到构造函数中,测试代码如下:

/**
 * 演示构造器注入
 */
@Test
public void simpleComponentScanConstructorInject() {
    User user = (User) applicationContext.getBean("componentUser");
    System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
    System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
}

运行结果:

6.1.3-2 方法注入

@Autowired注解不仅可以用在构造方法上,也可以用在属性的setter方法上。
例如我们重新改造一下User类,这次将构造方法上的@Autowired注释掉,打开setter方法上的@Autowired:

package com.tp.beans.componentscan;

import com.tp.beans.javaconfig.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * FileName: User
 * Author:   TP
 * Description:用户类,用来测试注解扫描方式声明bean
 */
@Component("componentUser")
public class User {

    public User() {
        System.out.println("无参构造:User被实例化");
    }

    // 构造器注入
    // @Autowired
    public User(Book book) {
        System.out.println("带参构造:User被实例化");
        this.book = book;
    }

    private Integer id;
    private String name;
    private Integer age;
    private String[] favorites;

    // 接口注入
    // @Autowired
    private AnimalService animalService;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String[] getFavorites() {
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

    public Book getBook() {
        return book;
    }

    // Setter注入
    @Autowired
    public void setBook(Book book) {
        this.book = book;
    }

    // 非Setter注入
    // @Autowired
    public void injectBook(Book book) {
        this.book = book;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", favorites=" + Arrays.toString(favorites) +
                ", book=" + book +
                '}';
    }
}

测试:

/**
 * 打开User的Setter上的@Autowired注解,演示Setter注入
 */
@Test
public void simpleComponentScanSetterInject() {
    User user = (User) applicationContext.getBean("componentUser");
    System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
    System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
}

运行效果:


在Spring初始化后,它会尽可能去满足bean的依赖。
实际上Setter方法并没有什么特殊之处,@Autowired可以用在类的任意方法上并发挥完全相同的作用。
例如我们将@Autowired用在下面示例的injectBook(Book book)方法上:

package com.tp.beans.componentscan;

import com.tp.beans.javaconfig.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * FileName: User
 * Author:   TP
 * Description:用户类,用来测试注解扫描方式声明bean
 */
@Component("componentUser")
public class User {

    public User() {
        System.out.println("无参构造:User被实例化");
    }

    // 构造器注入
    // @Autowired
    public User(Book book) {
        System.out.println("带参构造:User被实例化");
        this.book = book;
    }

    private Integer id;
    private String name;
    private Integer age;
    private String[] favorites;

    // 接口注入
    // @Autowired
    private AnimalService animalService;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String[] getFavorites() {
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

    public Book getBook() {
        return book;
    }

    // Setter注入
    // @Autowired
    public void setBook(Book book) {
        this.book = book;
    }

    // 非Setter注入
    @Autowired
    public void injectBook(Book book) {
        this.book = book;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", favorites=" + Arrays.toString(favorites) +
                ", book=" + book +
                '}';
    }
}

测试:

@Test
public void simpleComponentScanNotSetterInject() {
    User user = (User) applicationContext.getBean("componentUser");
    System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
    System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
}

运行效果:

6.1.3-3 接口注入

User类改造如下,增加一个属性:private AnimalService animalService;
并用@Autowired进行标注

package com.tp.beans.componentscan;

import com.tp.beans.javaconfig.Book;
import com.tp.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * FileName: User
 * Author:   TP
 * Description:用户类,用来测试注解扫描方式声明bean
 */
@Component("componentUser")
public class User {

    public User() {
        System.out.println("无参构造:User被实例化");
    }

    // 构造器注入
    // @Autowired
    public User(Book book) {
        System.out.println("带参构造:User被实例化");
        this.book = book;
    }

    private Integer id;
    private String name;
    private Integer age;
    private String[] favorites;

      // 接口注入
    @Autowired
    private AnimalService animalService;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String[] getFavorites() {
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

    public Book getBook() {
        return book;
    }

    // Setter注入
    // @Autowired
    public void setBook(Book book) {
        this.book = book;
    }

    // 非Setter注入
    // @Autowired
    public void injectBook(Book book) {
        this.book = book;
    }

    public AnimalService getAnimalService() {
        return animalService;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", favorites=" + Arrays.toString(favorites) +
                ", book=" + book +
                '}';
    }
}


测试:

@Test
public void simpleComponentScanStatement() {
    User user = (User) applicationContext.getBean("componentUser");
    System.out.println(">>>基于注解扫描的JavaBean声明:" + user);
    System.out.println(">>>基于注解扫描的JavaBean声明,注入的book:" + user.getBook());
    System.out.println(user.getAnimalService());
    user.getAnimalService().eat();
}

运行效果:

不管是构造器注入、Setter方法注入还是其他方法注入,Spring都会尝试满足方法参数上所声明的依赖,加入有且仅有一个bean匹配依赖bean的时候,这个bean就会被装配进来,如果没有匹配的bean,那么在应用上下文创建的时候 Spring会抛出异常,在某种特定场景或业务需求下你可能要避免这个异常,这个时候你可以将@Autowired的required属性设置为false(@Autowired默认值为true)。
将required设置为false时,Spring会 尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配状态,此时你需要谨慎对待,防止你的代码出现NullPointerException异常。

@Autowired是Spring特有的注解,如果你不想使用@Autowired也可以使用@Inject替代(@Inject注解来源于Java依赖注入规范),尽管二者存在着一些细微的差别,但是在绝大多数情况下二者可以相互替代,个人更推荐@Autowired

6.2JavaConfig装配bean

尽管在很多场景下通过组件扫描和自动装配实现Spirng自动化配置是更为推荐的方式,但是有时候自动化配置的方案行不通,例如你想将第三方库中的组件装配到你的应用中,这种情况下是没法在它的类上添加@Component和@Autowired注解的,这种情况下我们就不能使用上述的自动化装配方案了,只能使用显式装配的方式,显式装配有2种方案:JavaConfig或xml,下面介绍如何使用JavaConfig装配bean

创建JavaConfig类的关键在于为其添加@Configuration注解,该注解表明这个类是一个配置类,更类应该包含在Spring应用上下文中如何创建bean的细节,在上面的Spring声明简单bean的学习中我们已经学会了如何创建一个JavaConfig。
通常在JavaConfig中声明一个bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个bean添加@Bean注解,@Bean注解会告诉Spring这个方法将返回一个对象,这个对象要注册为Spring上下文中的一个bean,方法体中包括了最终产生这个bean的逻辑。
默认情况下,bean的id与方法名一致,如果你想为其设置一个不同的名字的话,你可以选择重命名这个方法,也可以@Bean的name属性指定一个不同的名字,例如 :

package com.tp.autoconfig;

import com.tp.bean.Book;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * FileName: AutoConfig
 * Author:   TP
 * Description: JavaConfig声明一个bean
 */
@Configuration
public class AutoConfig {

    @Bean(name = "springInAction")
    public Book book(){
        Book book = new Book();
        return book;
    }
}

6.2.1JavaConfig实现注入

我们上面的book是非常简单的bean,它自身没有其他的依赖,但是我们知道User这个bean有个构造函数是需要依赖book的,那么我们怎么样用JavaConfig的方式配置User呢?

  • JavaConfig构造器注入
    上面的User类的带参构造中需要一个Book参数,代码片段:
public User(Book book) {
    System.out.println("带参构造:User被实例化");
    this.book = book;
}

JavaConfig中,可以用如下的方式实现构造器注入:

//======================JavaConfig的构造器注入======================
@Bean
public User javaConfigUserConstructorInject(Book book) {
    User user = new User(book);
    user.setId(666);
    user.setAge(33);
    user.setName("Tony老师");
    return user;
}

运行测试类:

/**
 * JavaConfig声明bean+构造器注入
 */
@Test
public void javaConfigConstructorInject(){
    User user = (User) applicationContext.getBean("javaConfigUserConstructorInject");
    System.out.println("JavaConfig声明bean+构造器注入:" + user);
}

效果:

上面我们实现了JavaConfig的构造器注入,实际我们还可以使用setter注入:

  • JavaConfig属性Setter注入
//======================JavaConfig的Setter注入======================
@Bean("javaConfigUser")
public User user(Book book) {
    User user = new User();
    user.setId(4);
    user.setAge(18);
    user.setName("Tom老师");
    //setter注入
    user.setBook(book);
    return user;
}

测试:

/**
 * JavaConfig声明bean+setter注入
 */
@Test
public void javaConfigSetterInject(){
    User user = (User) applicationContext.getBean("javaConfigUser");
    System.out.println("JavaConfig声明bean+setter注入:" + user);
}

运行结果:

6.3通过xml装配bean

Spring的xml只有一种声明bean的方式:通过<bean>标签声明
但是对于DI,却有多种声明DI的方式:
对于构造器注入,有2种方式:

  • <constructor-arg>
  • 使用Spring3.0引入的C-命名空间
6.3.1构造器采用<constructor-arg>注入示例
<bean id="xmlUser0" class="com.tp.beans.componentscan.User">
    <constructor-arg name="book" ref="javaConfigBook"/>
</bean>

从上面的配置我们可以看出,我们声明xmlUser0这个bean时我们通过<constructor-arg name="book" ref="javaConfigBook"/>指明了构造参数book指向id为"javaConfigBook"的这个bean
测试:

@Test
public void test0(){
    User user = (User) applicationContext.getBean("xmlUser0");
    System.out.println(">>>>" + user);
}

运行效果:

6.3.2 构造器采用C-命名空间注入示例

C-命名空间是为了更加简洁的描述构造器参数,如果我们的bean时多个构造参数,我们就可以不必写多个<constructor-arg>,而使用C-命名空间替代:

<!--构造器注入:构造器参数为bean情形,使用c标签-->
<bean id="xmlUserC" class="com.tp.beans.componentscan.User" c:book-ref="javaConfigBook" />

C-命名空间的写法规则:

同时C-命名空间支持构造器参数名使用构造器参数位置的索引替代,例如上面的改成:

<bean id="xmlUserC" class="com.tp.beans.componentscan.User" c:_0-ref="javaConfigBook" />

结果是一样的
当你的构造器参数只有一个的时候,你甚至可以不写参数名也不写索引位置,类似这样:

<bean id="xmlUserC" class="com.tp.beans.componentscan.User" c:_-ref="javaConfigBook" />

如果构造器参数想注入字面值常量,使用c:参数名="字面值常量"即可
但有一点需要注意,并不是所有情况下C-命名空间都能替代<constructor-arg>,当构造参数为字面值常量或某个对象时二者可以相互替代,但当构造参数为集合时,C-命名空间是无能为力的,而<constructor-arg>可以做到,例如我们有个学生类Student和课程类Course分别如下:

package com.tp.beans.xml;

import java.util.List;
import java.util.Properties;

/**
 * FileName: Student
 * Author:   TP
 * Description:
 */
public class Student {

    public Student() {
        System.out.println("无参构造:Student被实例化");
    }

    public Student(String name, String age, List<Course> courses) {
        this.name = name;
        this.age = age;
        this.courses = courses;
        System.out.println("带参构造:Student被实例化");
    }

    private String name;
    private String age;
    private List<Course> courses;

    private Properties properties;

    //==========getter/setter==========

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public List<Course> getCourses() {
        return courses;
    }

    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", courses=" + courses +
                '}';
    }
}
package com.tp.beans.xml;

/**
 * FileName: Course
 * Author:   TP
 * Description:课程
 */
public class Course {

    public Course() {
        System.out.println("无参构造:Course被实例化");
    }

    public Course(String id, String name, int durations) {
        this.id = id;
        this.name = name;
        this.durations = durations;
        System.out.println("带参构造:Course被实例化");
    }

    /**
     * 课程ID
     */
    private String id;

    /**
     * 课程名称
     */
    private String name;

    /**
     * 课程时长(单位天)
     */
    private int durations;

    //==========getter/setter==========

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getDurations() {
        return durations;
    }

    public void setDurations(int durations) {
        this.durations = durations;
    }

    @Override
    public String toString() {
        return "Course{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", durations=" + durations +
                '}';
    }
}

此时如果Student使用带参构造器声明一个bean,我们就可以这样写:

<!--构造器注入:构造器参数有List/Set情形-->
<bean id="xmlStudent1" class="com.tp.beans.xml.Student">
    <constructor-arg name="name" value="xmlStudent1"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="courses"><null/></constructor-arg>
</bean>

当然这里我们将courses设置为类null,所以就不给代码示例了,如果不想设置null可以这样写:

<!--构造器注入:构造器参数有List/Set情形-->
<bean id="xmlStudent1" class="com.tp.beans.xml.Student">
    <constructor-arg name="name" value="xmlStudent1"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="courses">
        <list>
            <ref bean="xmlCourse1"/>
            <ref bean="xmlCourse2"/>
        </list>
    </constructor-arg>
</bean>

如果构造参数集合的泛型是String等非自定义的bean,比如我们给User这个类增加1个构造函数:

public User(String name, String[] favorites) {
    this.name = name;
    this.favorites = favorites;
}

那么xml声明时可以这样写:

<!--构造器注入:构造器参数有数组情形-->
<bean id="xmlUser4" class="com.tp.beans.componentscan.User" c:name="小张">
    <constructor-arg name="favorites">
        <list>
            <value>吃饭</value>
            <value>睡觉</value>
            <value>打豆豆</value>
        </list>
    </constructor-arg>
</bean>

测试:

/**
 * 构造器注入:构造器参数有数组情形
 */
@Test
public void testCnsotructorArray(){
    User user = (User) applicationContext.getBean("xmlUser4");
    System.out.println(">>>>" + user);
}

结果:

如果是set集合则将<list>改成<set>,如果为<set>则所有的重复值都会被忽略,存放顺序也无法得到保证。
如果是数组Array则将<list>改成<array>,注意不改也是可以的<array>节点可以被<list>节点替代

    <!--构造器注入:构造器参数有List/Set情形-->
    <bean id="xmlStudent1" class="com.tp.beans.xml.Student">
        <constructor-arg name="name" value="xmlStudent1"/>
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="courses">
            <list>
                <ref bean="xmlCourse1"/>
                <ref bean="xmlCourse2"/>
            </list>
        </constructor-arg>
    </bean>

测试:

/**
 * 构造器注入:构造器参数有List/Set情形
 */
@Test
public void test1() {
    Student student = (Student) applicationContext.getBean("xmlStudent1");
    System.out.println(">>>>" + student);
}

结果:

如果构造参数是Map<String, Object>,假设有个bean定义如下:

package com.tp.beans.xml;

import java.util.Map;

/**
 * FileName: MapTestBean
 * Author:   TP
 * Description:
 */
public class MapTestBean {

    public MapTestBean(Map<String, Object> info) {
        this.info = info;
    }

    private Map<String, Object> info;

    public Map<String, Object> getInfo() {
        return info;
    }

    public void setInfo(Map<String, Object> info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "MapTestBean{" +
                "info=" + info +
                '}';
    }
}

那么在Spring XML中就可以这么写:

<!--构造器注入:构造器参数有Map<String, Object>情形-->
<bean id="xmlMapTestBean" class="com.tp.beans.xml.MapTestBean">
    <constructor-arg name="info">
        <map>
            <entry key="key1" value="value1"/>
            <entry key="key2" value-ref="xmlCourse1"/>
        </map>
    </constructor-arg>
</bean>

测试:

/**
 * 构造器注入:构造器参数有Map<String, Object>情形
 */
@Test
public void test2() {
    MapTestBean mapTestBean = (MapTestBean) applicationContext.getBean("xmlMapTestBean");
    System.out.println(">>>>" + mapTestBean);
}

结果:

6.3.3 属性注入

在上面的例子中我们侧重讲解通过构造器注入,接下来我们看一下如何通过Spring XML实现属性注入,属性注入也可以有2种方式:

  • <property>
  • P-命名空间

使用<property>实现属性注入:
例如Course类:

<!--属性注入:字面值常量-->
<bean id="xmlCourse1" class="com.tp.beans.xml.Course">
    <property name="name" value="Java"/>
    <property name="durations" value="24"/>
    <property name="id" value="222"/>
</bean>
<!--属性注入:字面值常量-->
<bean id="xmlCourse2" class="com.tp.beans.xml.Course">
    <property name="name" value="Python"/>
    <property name="durations" value="25"/>
    <property name="id" value="111"/>
</bean>

如果属性是一个JavaBean可以使用ref进行指定:

<!--属性注入:属性有bean情形-->
<bean id="xmlUser1" class="com.tp.beans.componentscan.User">
    <property name="name" value="xmlUser1"/>
    <property name="book" ref="javaConfigBook"/>
</bean>

如果属性是一个数组可以使用<array>进行指定,<array>可以被<list>替代:

<bean id="xmlUser2" class="com.tp.beans.componentscan.User">
    <property name="name" value="xmlUser2"/>
    <property name="book" ref="javaConfigBook"/>
    <property name="favorites">
        <array>
            <value>篮球</value>
            <value>电影</value>
            <value>唱歌</value>
        </array>
    </property>
</bean>

如果属性是一个List<对象>:

<!--属性注入,属性是List<Courses>情形-->
<bean id="xmlStudent2" class="com.tp.beans.xml.Student">
    <property name="name" value="xmlStudent2"/>
    <property name="courses">
        <list>
            <ref bean="xmlCourse1" />
            <ref bean="xmlCourse2" />
        </list>
    </property>
</bean>

注意:既可以通过 ref 使用外部定义好的 Bean,也可以直接在 list 或者 array 节点中定义 bean
如果属性是一个Map<String, Object>:

<property name="map">
    <map>
        <entry key="age" value="99"/>
        <entry key="name" value="javaboy"/>
    </map>
</property>

如果属性是一个java.util.Properties:

<property name="info">
    <props>
        <prop key="age">99</prop>
        <prop key="name">javaboy</prop>
    </props>
</property>

使用P-命名空间实现属性注入:
属性的P-命名空间和构造器的C-命名空间用法是相似的,P-命名空间的格式为:

C-命名空间相似,装配bean应用与装配字面量的唯一区别在于是否带有" -ref "后缀,例如:

<!--属性注入:使用P-命名空间-->
<bean id="user4" class="com.tp.beans.User" p:name="用户4" p:book-ref="book">
    <property name="favorites">
        <array>
            <value>篮球</value>
            <value>电影</value>
            <value>唱歌</value>
        </array>
    </property>
</bean>

相关文章

网友评论

      本文标题:Spring-IOC基础

      本文链接:https://www.haomeiwen.com/subject/ecxqxctx.html