1.什么是Spring,它解决了什么问题?

Spring是一个开源框架,它由Rod Johnson创建。它是为了降低企业应用开发的复杂性、简化开发而创建的一个开源框架,它使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅只限于服务端的开发,从简单性、可测试和松耦合的角度而言,任何Java应用都可以从Spring中收益。

2.Spring的整体架构

Spring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块。架构图如下图:
1602137010443.png
1.Core Container
Core Container(核心容器)包含有Core、Bean、Context、Expression Language(spring表达语言,简称SpEL)模块。

  • Core和Bean模块是框架的基础部分,提供IOC(控制翻转)和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。Core模块主要包含Spring框架基本的核心工具类,Spring的其他组件都要用到这个包里的类,Core模块是其他组件的基本核心。当然你也可以在自己的应用系统中使用这些工具类。

  • Beans模块是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。

  • Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式的对象访问方法。Context模块继承了Beans的特性,为Spring核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对Context的透明创建的支持。Context模块同时也支持J2EE的一些特性,例如EJB、JMX和基础的远程处理。ApplicationContext接口是Context模块的关键。

  • Expression Language模块提供了强大的表达式语言,用于在运行时查询和操纵对象。它是JSP 2.1规范中定义的unifed expression language的扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文(accessiong the context of arrays)、容器和索引器、逻辑和算术运算符、命名变量以及从Spring的IoC容器中根据名称检索对象。它也支持list投影、选择和一般的list聚合。

2.Data Access/Integration
Data Access/Integration层包含JDBC、ORM、OXM、JMS和Transaction模块。

  • JDBC模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码。这个模块包含了Spring对JDBC数据访问进行封装的所有类。

  • ORM模块为流行的对象-关系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一个交互层。利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射,如前边提到的简单声明性事务管理。

Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有这些都遵从Spring的通用事务和DAO异常层次结构。

  • OXM模块提供了一个对Object/XML映射实现的抽象层,Object/XML映射实现包括JAXB、Castor、XMLBeans、JiBX和XStream。

  • JMS(Java Messaging Service)模块主要包含了一些制造和消费消息的特性。

  • Transaction模块支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并且对所有的POJO都适用。

3.Web
Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以,Spring框架支持与Jakarta Struts的集成。Web模块还简化了处理大部分请求以及将请求参数绑定到域对象的工作。Web层包含了Web、Web-Servlet、Web-Struts和Web-Porlet模块,具体说明如下。

  • Web模块:提供了基础的面向Web的集成特性。例如,多文件上传、使用servlet listeners初始化IoC容器以及一个面向Web的应用上下文。它还包含Spring远程支持中Web的相关部分。

  • Web-Servlet模块:web.servlet.jar:该模块包含Spring的model-view-controller(MVC)实现。Spring的MVC框架使得模型范围内的代码和web forms之间能够清楚地分离开来,并与Spring框架的其他特性集成在一起。Web-Struts模块:该模块提供了对Struts的支持,使得类在Spring应用中能够与一个典型的Struts Web层集成在一起。注意,该支持在Spring 3.0中已被弃用。

  • Web-Porlet模块:提供了用于Portlet环境和Web-Servlet模块的MVC的实现。

4.AOP
AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点,从而将逻辑代码分开,降低它们之间的耦合性。利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中,这有点像.Net技术中的attribute概念。通过配置管理特性,Spring AOP模块直接将面向切面的编程功能集成到了Spring框架中,所以可以很容易地使Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。通过AOP可以完成对目标的拦截、权限或参数校验、日志记录、缓存等功能。

  • Aspects模块提供了对AspectJ的集成支持。
  • Instrumentation模块提供了class instrumentation支持和classloader实现,使得可以在特定的应用服务器上使用。

5.Test
Test模块支持使用JUnit和TestNG对Spring组件进行测试。

3.什么是IOC?什么是DI?

IOC是Inversion of Control的简称,中文译为控制反转,不是什么技术,而是一种设计思想。IOC的思想是将对象的控制权交由IOC容器,通过IOC容器完成对象的装配和管控,从而完成对象从初始化到销毁一系列过程的管控。在传统Java SE程序设计中,我们直接在对象内部通过new进行创建对象,是程序主动创建依赖对象;而IOC有一个专门的容器来创建这些对象。从手动通过new创建对象到IOC得到对象控制权自动创建对象,这就是一个”反转”过程。

DI是Dependency Injection的简称,中文译为依赖注入, 即由容器动态的将某个依赖关系注入到组件之中。按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,例如A对象中引用了B对象(A对象依赖于B对象),如果此时A对象引用了更多的其他对象(在项目中肯定不止一个A对象),这将会导致高度耦合和难以测试的代码。通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象时进行设置,对象无需自行创建或管理它们的依赖关系。依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己自己去获取依赖,例如上面举例的A对象和B对象,A对象引用了B对象(A依赖于B),A对象就是目标对象,B对象需要注入到A对象,而不是通过B对象自己去获取A对象。

4.使用IOC装配Bean

创建应用组件之间协作的行为通常被称为装配(wiring)。而Bean其实就是一个普通的Java类,要想使用IOC容器那么就必须将目标对象注入到IOC容器中。Spring提供了三种装配Bean的方式(你可以单独使用,也可以混合使用):

  • 通过Xml形式装配Bean。相比较其他两种方式XML形式配置繁琐、类型安全差、对重构不友好。当项目的装配的Bean越来越多时,XML中Bean的配置也会越来越多;配置Bean的元数据时一般要指定Bean的class属性(Bean的类型),如果此时class对应的类型发生重命名或删除操作等其他操作,那么你也要改动class的属性的值,所以类型的安全性差。

  • 通过Java Config(Java配置类)装配Bean。由于JavaConfig是配置代码,Java Config不应该侵入到业务逻辑代码中。通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,所以它的意图更加明确。

  • 自动化装配Bean。自动化装配Bean能减少显式配置,建议尽可能使用自动装配,如果需要显式配置Bean时,推荐使用类型安全且比Xml形式更加强大的Java Config,如果想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。

新建一个maven项目,项目依赖如下:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-core</artifactId>
  5. <version>5.2.9.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-beans</artifactId>
  10. <version>5.2.9.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-context</artifactId>
  15. <version>5.2.9.RELEASE</version>
  16. </dependency>
  17. </dependencies>

4.1 xml形式装配Bean

在com.fly包下新建一个User类,其内容如下:

package com.fly;
public class User {
    public String hello(String name){
        return name+"你好!";
    }
}

紧接着在resources创建application.xml来配置要注入IOC容器的元数据(使用xml方式注入IOC容器,也可以使用注解形式)。下面例子主要在bean标签配置了id属性和class属性,id属性是唯一的且用来标识Bean,class是Bean的类型,知道了Bean的类型IOC就可以通过反射机制完成Bean的实例化。application.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.fly.User"/>
</beans>

编写测试类:

package com.fly;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        /*
         *     也可以通过以下这种方式获取BeanFactory,只不过new XmlBeanFactory()已经过期
         *  BeanFactory bf=new XmlBeanFactory(new ClassPathResource("application.xml"));
         *  User user= bf.getBean("user");
         */

        //指定resources目录下的application.xml为注入IOC元数据配置文件
        ApplicationContext app=new ClassPathXmlApplicationContext("application.xml");
        //获取Bean,这里的user指的Bean的id
        User user=(User) app.getBean("user");
        System.out.println(user.hello("zxp")); //zxp你好!
    }
}

4.2 Java配置类形式装配Bean

修改com.fly下的User类:

package com.fly;
public class User {
    private String name;

    public String getName() {
        return name;
    }

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

    public String hello(){
        return this.name+"你好!";
    }
}

创建一个配置类用于配置Bean信息,@Configuration注解相当于spring配置xml中beans标签;@Bean则相当于spring xml配置中的bean标签,填写的name属性就是bean标签的id属性,如果不填写name属性那么IOC为该bean生成一个唯一的名称。

package com.fly;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean(name="user1")
    public User user1(){
        User user=new User();
        user.setName("zxp");
        return user;
    }

    @Bean(name="user2")
    public User user2(){
        User user=new User();
        user.setName("z乘风");
        return user;
    }
}

编写测试类,对于注解形式使用AnnotationConfigApplicationContext类的构造函数即可获取ApplicationContext(应用上下文对象),只需指定我们编写好的Bean配置类即可;AnnotationConfigApplicationContext和ClassPathXmlApplicationContext都是ApplicationContext接口的子类,相比较繁琐的xml配置,注解形式的更加便捷。

package com.fly;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        //指定Bean配置类
        ApplicationContext app=new AnnotationConfigApplicationContext(Config.class);
        //获取 id为user1的bean
        User user1=(User) app.getBean("user1");
        System.out.println(user1.hello());//zxp你好!

        //获取 id为user2的bean
        User user2=(User) app.getBean("user2");//z乘风你好!
        System.out.println(user2.hello());
    }
}

4.3 自动装配Bean

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

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

组件扫描和自动装配组合在一起能发挥出巨大的威力,它们能够将显式的配置降低到最小。

自动装配的例子:
MediaDevices.class:

package com.fly;
/*创建媒体设备类,提供一个播放方法 */
public interface MediaDevices {
     void play(String name);
}

Mp3.class:

package com.fly;
import org.springframework.stereotype.Component;
/*
 *@Component 表明该类会作为组件类,并告知Spring要为此类创建Bean,value属性用于指定创建Bean的name
 * 如果不指定value,@Component会以类名(首字母小写)作为Bean的name
 * */
@Component
public class Mp3  implements MediaDevices {
    public void play(String name) {
        System.out.println(name+"在play...");
    }
}

Mp3.class上使用了@Component修饰,@Component表明修饰类会被作为组件类,并告知Spring要为此类创建Bean。如果不指定@Component的value,Spring在创建Bean时将以修饰类首字母小写作为Bean的name,你也可以设置value属性的值指定创建Bean的name。

除了@Component还有另外一种为Bean命名的形式,通过Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为Bean设置ID,例如:@Named(“mp3”)。对比@Component注解@Named注解命名感觉有点不清晰,所以在开发中一般使用@Component注解比较多。

Mp3PlayerConfig.class

package com.fly;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/*mp3播放配置类*/
@Configuration
/*
 *@ComponentScan用于启用组件扫描,如果没有其他配置,@ComponentScan默认会扫描当前类所在包路径,因为当前类位于
 * com.fly包下,所以Spring将会扫描com.fly包及com.fly包下的所有子包,查找带有@component注解的类
 * */
@ComponentScan
public class Mp3PlayerConfig {

}

@Configuration注解用于标识类为一个配置类,相当于XML配置中的beans。@ComponentScan用于启用组件扫描,通过此注解Spring容器就可以知道需要装配哪个地方的Bean了。@ComponentScan可以通过属性设定扫描的范围:

情况 描述
什么都不配置 如果没有其他配置,@CoponentScan注解默认会扫描修饰类的所在的包路径,由于上面Mp3PlayerConfig.class位于com.fly包下,Spring将会扫描这个包下以及这个包下所有的子包
为@ComponentScan指定扫描包路径。例如:@ComponentScan(“com.fly”),这种形式是basePackages的别名 @ComponentScan会扫描指定的包路径及其路径下的所有子包。例子中@ComponentScan会扫描com.fly包及其包路径下的所有子包。
通过basePackages属性指定多个包路径。例如:@ComponentScan(basePackages = {“com.fly”,“com.fx”}) @ComponentScan会扫描com.fly和com.fx包及其路径下所有的子包,basePackages属性是一个数组类型,运行指定多个扫描包路径。
通过basePackageClasses属性指定扫描类所在包下的所有组件。例如:@ComponentScan(basePackageClasses ={Mp3PlayerConfig.class,Mp4PlayerConfig.class}) basePackageClasses是一个数组,basePackageClasses属性会去扫描类所在包下的所有组件,而不是指定某个组件!

4.4 应用上下文

上面例子中通过ApplicationContext接口的getBean()获取Bean实例,但是Spring的容器并不是只有一个。Spring自带了多个容器实现,可以归结为两种不同的类型。BeanFactory是最简单的容器,提供基本的DI支持。ApplicationContext基于BeanFactory构建,并提供应用框架级别的服务,例如从properties文件解析文件信息以及发布应用事件给感兴趣的事件监听者。由于ApplicationContext提供的功能更加丰富,下面列举多种类型的应用上下文(它们都实现了ApplicationContext接口):

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

    ApplicationContext app=new FileSystemXmlApplicationContext("c:/application.xml");
    
  • XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。

5.依赖注入的两种方式

Spring提供构造器注入和属性注入两种注入方式,这两种方式都能向Spring容器注入Bean。当属性过多时建议使用构造器注入,属性数量比较少可以使用属性注入。

5.1 构造器注入

在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:

  • 元素。如果要装配集合(例如List、Set、Map)比c-命名空间更有优势,使用c-命名空间的属性无法实现装配集合的功能。
  • 使用Spring3.0所引入的c-命名空间。不装配集合的情况都可以建议使用命令空间简化配置。

使用**元素完成构造器注入:**

(1).改造一下User类,添加一个有参构造方法:

package com.fly;
public class User {
    private String name;
    private String alias;
    public String getName() {
        return name;
    }
    public void setName(String name) { this.name = name; }
    public String getAlias() { return alias;  }
    public void setAlias(String alias) { this.alias = alias;}

    //User有参构造方法
    public User(String name,String alias){
        this.name=name;
        this.alias=alias;
    }
    public String hello(){
        return this.name+"你好!你的别名是:"+this.alias;
    }
}

(2).在resources/application.xml配置Bean元数据,下面配置使用了元素为构造方法的参数设值,因为User类提供了一个有参构造方法并传入了name和alias参数,所以需要在User Bean配置两个元素,value属性配置的值对应构造方法的参数值(顺序一一对应)

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.fly.User" >
        <constructor-arg value="zxp"/>
        <constructor-arg value="大帅比"/>
    </bean>
</beans>

(3).测试。标签配置的value值已经成功注入到User构造方法参数中。

package com.fly;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("application.xml");
        //获取 id为user的bean
        User user1=(User) app.getBean("user");
        System.out.println(user1.hello());//zxp你好!你的别名是:大帅比
    }
}

如果你不想标签按照参数顺序(索引)来传递,你可以使用name属性指定构造方法中要传递的参数,例如:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.fly.User" >
        <constructor-arg value="zxp" name="alias"/>
        <constructor-arg value="大帅比" name="name"/>
    </bean>
</beans>

运行测试类结果为:”大帅比你好!你的别名是:zxp”。

元素属性如下:

属性名称 描述
value 为Bean构造方法参数设置的值,如果不设置name属性,的顺序跟Bean构造方法参数的顺序是一一对应
name 指定Bean构造方法参数的名称,value会根据name属性与构造方法的参数名匹配而设定值
type 指定Bean构造方法参数的类型,例如 type=”java.lang.String”
index 指定与Bean构造方法参数对应下标,index=0对应构造方法的第一个参数,以此类推
ref ref属性用于引用其他Bean

ref例子:
**
Student.class(很简单,提供了一个有参构造函数):

package com.fly;
public class Student {
    private String stuName;
    private Integer age;
    public Integer getAge() {  return age; }
    public void setAge(Integer age) { this.age = age; }
    public void setStuName(String stuName) { this.stuName = stuName;  }
    public String getStuName() { return stuName; }
    public Student(String stuName,Integer age){
        this.stuName=stuName;
        this.age=age;
    }
}

User.class(提供了一个有参构造函数,并传入一个Student类型的参数):

package com.fly;
public class User {
    private String name;
    private Integer age;
    public User(Student student){
        this.name=student.getStuName();
        this.age=student.getAge();
    }
    public String hello(){
        return this.name+"你好!你的age是:"+this.age;
    }
}

resources/application配置Bean元数据:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置 student bean-->
    <bean id="student" class="com.fly.Student">
        <constructor-arg value="zxp大帅比" name="stuName" type="java.lang.String"/>
        <constructor-arg value="18" name="age" type="java.lang.Integer"/>
    </bean>

    <bean id="user" class="com.fly.User">
        <!-- 使用ref引用student Bean -->
        <constructor-arg ref="student"/>
    </bean>
</beans>

测试。上面User Bean通过的ref属性引用了id为student的Bean,student的东西就能提供给User Bean使用了。

package com.fly;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test01 {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("application.xml");
        //获取 id为user的bean
        User user1=(User) app.getBean("user");
        System.out.println(user1.hello());//zxp大帅比你好!你的age是:18
    }
}


使用**c-命名空间完成构造注入:
c-命名空间形式相比较更简化,使用c-命名空间需要在bean标签引入xmlns:c=”http://www.springframework.org/schema/c",c-命名空间格式为:c:构造方法参数名-命名约定,例如c:student-ref="student",c:student的student是Bean构造方法的参数名,-ref是一个命名约定,它会告诉Spring正在装配一个名为student的bean的引用,而不是一个字面量"student"。使用c-命名空间更改上面的例子,下面运行结果跟上面的例子一模一样。

c命名空间+构造方法参数名的形式:

<?xml version="1.0" encoding="UTF-8"?>
<!--使用c-命名空间值beans新增 xmlns:c="http://www.springframework.org/schema/c" -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置 student bean-->
    <bean id="student" class="com.fly.Student" c:stuName="zxp大帅比" c:age="18"/>

    <!-- c空间命名格式为: c:构造函数参数名-命名约定 -->
    <bean id="user" class="com.fly.User" c:student-ref="student"/>
</beans>

如果你不想使用c命名空间+构造方法参数名的形式,你还可以使用c命名空间+构造方法参数索引的形式,运行的结果还是跟上面例子一模一样。

<?xml version="1.0" encoding="UTF-8"?>
<!--使用c-命名空间值beans新增 xmlns:c="http://www.springframework.org/schema/c" -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置 student bean-->
    <bean id="student" class="com.fly.Student" c:_0="zxp大帅比" c:_1="18"/>

    <!-- c空间命名格式为: c:构造函数参数名-命名约定 -->
    <bean id="user" class="com.fly.User" c:student-ref="student"/>
</beans>

构造器注入集合:
User.class(Student.class不变):

package com.fly;
import java.util.List;
public class User {
    private String name;
    private Integer age;
    public void setAge(Integer age) { this.age = age;  }
    public void setName(String name) { this.name = name;  }
    public Integer getAge() { return age;}
    public String getName() {return name;}
    //创建构造方法,构造方法传入List<Student>类型的参数
    public User(String name, Integer age, List<Student> studentList){
        this.name=name;
        this.age=age;
        for (Student student:studentList) {
            System.out.println("stuName:"+student.getStuName()+",age:"+student.getAge());
        }
    }
    public String hello(){
        return this.name+"你好!你的age是:"+this.age;
    }
}

application.xml(错误的)。因为Usre的构造方法提供了三个参数,第三个参数是一个List类型,在声明Bean时,我们必须为List提供一个初始值。最简单的办法就是将List设置为null(如下面的标签)。但是我们在User的构造函数中遍历List,如果设置为null在调用User无参构造方法时就会抛出NullPointerException异常(空指针异常),最好的办法就是声明List集合数据。

<?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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="student" class="com.fly.Student" c:stuName="zxp" c:age="18"/>

    <bean id="user" class="com.fly.User">
       <constructor-arg value="user01"/>
       <constructor-arg value="12"/>
       <constructor-arg>
           <null/>
       </constructor-arg> 
    </bean>
</beans>

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

    <bean id="student1" class="com.fly.Student" c:stuName="zxp" c:age="18"/>
    <bean id="student2" class="com.fly.Student" c:stuName="猪妖" c:age="1000"/>
    <bean id="student3" class="com.fly.Student" c:stuName="大黄" c:age="5"/>

    <bean id="user" class="com.fly.User">
       <constructor-arg value="user01"/>
       <constructor-arg value="12"/>
       <constructor-arg>
           <list>
               <!-- 如果要为list设值就使用<value>标签,如果要引用其他Bean就使用<ref> -->
               <ref bean="student1"/>
               <ref bean="student2"/>
               <ref bean="student3"/>
           </list>
       </constructor-arg>
    </bean>
</beans>

测试结果为:

stuName:zxp,age:18
stuName:猪妖,age:1000
stuName:大黄,age:5
user01你好!你的age是:12

构造器注入更多集合:
User.class:

package com.fly;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class User {
    private String name;
    private Integer age;
    public void setAge(Integer age) { this.age = age;  }
    public void setName(String name) { this.name = name;  }
    public Integer getAge() { return age;}
    public String getName() {return name;}
    //创建构造方法,构造方法传入List<Student>类型的参数
    public User(String name, Integer age, List<Student> studentList, Set<String> strSet, Map<String,Student> studentMap){
        this.name=name;
        this.age=age;
        for (Student student:studentList) {
            System.out.println("stuName:"+student.getStuName()+",age:"+student.getAge());
        }
        for (String str:strSet) {
            System.out.println(str);
        }
        for (Map.Entry<String,Student> map:studentMap.entrySet()) {
            System.out.println(map.getKey()+"=>"+"stuName:"+map.getValue().getStuName()
                    +",age:"+map.getValue().getAge());
        }
    }
    public String hello(){
        return this.name+"你好!你的age是:"+this.age;
    }
}

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

    <bean id="student1" class="com.fly.Student" c:stuName="zxp" c:age="18"/>
    <bean id="student2" class="com.fly.Student" c:stuName="猪妖" c:age="1000"/>
    <bean id="student3" class="com.fly.Student" c:stuName="大黄" c:age="5"/>

    <bean id="user" class="com.fly.User">
       <constructor-arg value="user01"/>
       <constructor-arg value="12"/>
       <constructor-arg>
           <list>
               <!-- 如果要为list设值就使用<value>标签,如果要引用其他Bean就使用<ref> -->
               <ref bean="student1"/>
               <ref bean="student2"/>
               <ref bean="student3"/>
           </list>
       </constructor-arg>
       <constructor-arg>
           <set>
               <value>大狗子</value>
               <value>二狗子</value>
               <value>大黄</value>
               <value>小白</value>
           </set>
       </constructor-arg>
       <constructor-arg>
           <map>
               <entry key="stu1" value-ref="student1"/>
               <entry key="stu2" value-ref="student2"/>
               <entry key="stu3" value-ref="student3"/>
           </map>
       </constructor-arg> 
    </bean>
</beans>

运行结果为:

stuName:zxp,age:18
stuName:猪妖,age:1000
stuName:大黄,age:5
大狗子
二狗子
大黄
小白
stu1=>stuName:zxp,age:18
stu2=>stuName:猪妖,age:1000
stu3=>stuName:大黄,age:5
user01你好!你的age是:12

5.2 属性注入

在XML中声明DI时,会有多种可选的配置方案和风格。具体到属性注入,有两种基本的配置方案可供选择:

  • 元素
  • 使用Spring3.0所提供的p-命名空间

使用元素完成属性(setter)注入:
User.class:

package com.fly;
public class User {
    private String name;
    private Integer age;
    private Student student;
    public void setAge(Integer age) { this.age = age;  }
    public void setName(String name) { this.name = name;  }
    public Integer getAge() { return age;}
    public String getName() {return name;}
    public Student getStudent() { return student; }
    public void setStudent(Student student) { this.student = student; }
    public String hello(){
        System.out.println("stuName:"+this.student.getStuName()+",age:"+this.student.getAge());
        return this.name+"你好!你的age是:"+this.age;
    }
}

application.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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="student" class="com.fly.Student" c:stuName="z乘风" c:age="10000"/>
    <bean id="user" class="com.fly.User">
        <property name="name" value="zxp"/>
        <property name="age" value="18"/>
        <property name="student" ref="student"/>
    </bean>
</beans>

运行结果为:

stuName:z乘风,age:10000
zxp你好!你的age是:18

使用p-命名空间完成属性(setter)注入:

使用p-命名空间需要在beans标签中引入xmlns:p=”http://www.springframework.org/schema/p",p-命名空间跟c-命名空间使用方式类似。p-命令空间格式为:p:属性名="所注入的属性值“ 或者 p:属性名-ref=”引用Bean的id” 。使用p-命名空间修改上面的例子,application.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:c="http://www.springframework.org/schema/c"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="student" class="com.fly.Student" c:stuName="z乘风" c:age="10000"/>
    <bean id="user" class="com.fly.User" p:name="zxp" p:age="18" p:student-ref="student"/>
</beans>

6.XML和JavaConfig装配Bean混用使用

一般情况下我们推荐使用自动装配或JavaConfig装配Bean,有些情况下用XML形式来装配Bean更合适,下面介绍在JavaConfig中引入XML配置,在XML中引入Java Config配置。

6.1 @Import:用于多个Bean的组合

假设现在有一个UserConfig.class,随着项目的复杂度推移UserConfig.class配置项变得越来笨重,我们希望能对UserConfig.class进行拆分,将它拆分为多个Config。Spring提供@Import注解将多个类组合在一起,例如下面的例子(User和Student都是辅助类)。**

//User.class
package com.fly;
public class User {
    private String name;
    private Integer age;
    public void setAge(Integer age) { this.age = age;  }
    public void setName(String name) { this.name = name;  }
    public Integer getAge() { return age;}
    public String getName() {return name;}
    public User(String name,Integer age){
        this.name=name;
        this.age=age;
    }
    public String hello(){ return this.name+"你好!你的age是:"+this.age; }
}
//Student.class
package com.fly;
public class Student {
    private String stuName;
    private Integer age;
    public Integer getAge() {  return age; }
    public void setAge(Integer age) { this.age = age; }
    public void setStuName(String stuName) { this.stuName = stuName;  }
    public String getStuName() { return stuName; }
    public Student(String stuName,Integer age){
        this.stuName=stuName;
        this.age=age;
    }
}
//UserConfig.class
package com.fly;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/*
*  假设UserConfig.class很繁杂了,现在对UserConfig进行拆分,原来UserConfig的配置项拆分至UserConfig和
*  UserPlusConfig中
* */
@Configuration
public class UserConfig {
    @Bean
    public Student userConfig(Student student){
        return new Student(student.getStuName(),student.getAge());
    }
}

方式1:直接使用@Import组合一个类:

package com.fly;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Import(UserConfig.class)
public class UserPlusConfig {

    @Bean
    public Student userPlusConfig(Student student){
        return new Student(student.getStuName(),student.getAge());
    }
}

方式2(推荐)创建一个新类组合多个配置类:

package com.fly;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({UserConfig.class,UserPlusConfig.class})
public class SoundSystemConfig {

}
//测试类
package com.fly;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test{
    public static void main(String[] args) {
        ApplicationContext context=new AnnotationConfigApplicationContext(SoundSystemConfig.class);
        Student student=(Student) context.getBean("getStudent");
        System.out.println(student.getStuName()); //zxp-student
        User us=(User) context.getBean("getUser"); 
        System.out.println(us.getName()); //zxp1111
    }
}

@ImportResource:导入指定资源文件组合Bean
假设现在有两个Bean,一个是使用XML配置定义,另一个是采用JavaConfig的方式配置,此时JavaConfig Bean想引用采用XML配置的Bean,可以在JavaConfig通过@ImportResource导入XML文件就能将2个配置组合在一起。

7.Bean的介绍

Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的(例如,以XML<bean/>定义的形式)在容器本身内,这些bean定义表示为BeanDefinition 对象,这些对象包含(除其他信息外)以下元数据:

  • Bean的类名:通常,定义了Bean的实际实现类
  • Bean行为配置元素:用于声明Bean在容器中的行为(作用域,生命周期回调等)
  • Bean的依赖项:一个或多个Bean引用其他Bean进行工作,这些引用被称为依赖项
  • 在新创建的对象中设置的其他配置设置:例如,池的大小限制或在管理连接池的bean中使用的连接数

Bean的属性如下:

属性 描述
id Bean唯一标识符,可以通过Bean的id或name获取Bean
class Bean对应的实现类,通过class属性的值IOC通过反射机制可以完成对象的实例化
scope Bean的作用域,指定Bean的作用范围。Bean一共有singleton、requestprototype、session、application五种作用域。

8.Bean的生命周期

9.Bean的五种作用域