Bootstrap

Spring IOC

一 Spring的概要

1.1 简介

        Spring,春天的意思,意指给软件行业带来春天。2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

官网 : Spring | Home

官方下载地址 : JFrog

GitHub : Spring · GitHub

1.2 优点

  1. Spring是一个开源的轻量级的应用开发框架,其目的是用于简化企业级应用程序开发,降低侵入性;

  2. Spring提供的IOC和AOP功能,可以将组建的耦合度降至最低,即解耦,便于系统日后的维护和升级

  3. 提供了对持久层、事务的支持

  4. 提供了MVC框架思想的实现,用于开发企业级应用。

  5. 也可以与第三方框架和技术整合应用,可以自由选择采用哪种技术进行开发

一句话概括:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)

1.3 Spring的发展历程

1997 年 IBM提出了EJB 的思想

1998 年,SUN制定开发标准规范 EJB1.0

1999 年,EJB1.1 发布

2001 年,EJB2.0 发布

2003 年,EJB2.1 发布

2006 年,EJB3.0 发布

Rod Johnson (罗德·约翰逊,Spring 之父)

Expert One-to-One J2EE Development without EJB(2004)

阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)

2017年9月份发布了Spring的最新版本Spring 5.0通用版

1.4 模块

        Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。

        组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  1. 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开

  2. Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。

  3. Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

  4. Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

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

  6. Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  7. Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

1.5 拓展 Spring Boot与Spring Cloud

  • Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务;

  • Spring Cloud是基于Spring Boot实现的;

  • Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架;

  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。

  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

二 Spring IOC

2.1 IOC简介

        IoC(Inverse Of Control)控制反转:即,把创建对象的权利交给框架,也就是指将对象的创建、对象的初始化、对象的存储、对象的管理交给了Spring容器。IOC本质是一种设计思想

        IoC 是一种通过描述来生成或者获取对象的技术,对于Java初学者 更多时候所熟悉的是使用 new 关键字来创建对象,而在spring中则不是,它是通过描述(XML或注解)来创建对象

        在 Spring 中把每一个需要管理的对象称之为Spring Bean(简称为Bean),而Spring中管理这些 Bean 的容器,被我们称之为 Spring IoC容器(简称IoC容器)

         在此之前,当需要对象时,通常是利用new关键字创建一个对象:

/* 创建一个User对象——这里使用new对象的方式造成了程序之间的耦合性提升 */
User u = new User();

        但由于new对象,会提高类和类之间的依赖关系,即代码之间的耦合性。

        而现在我们可以将对象的创建交给框架来做:

// 获取Spring IoC容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// 从spring的IoC容器中获取User对象
private User user = (User)ctx.getBean("user");

        将需要的POJO提前在配置文件(XML或注解)中进行描述,Spring会根据配置文件将其中所描述生成 IoC容器并装配Bean,在需要使用时,再次获取即可

        这样一来,当需要对象时不用自己创建,而是通过框架获取即可,不用硬编码去new对象,自然就降低类和类之间的依赖关系,也就是耦合性。

 2.2 入门案例

        在Spring中允许我们通过XML或注解方式装配Bean,下面先来介绍Spring中通过XML方法如何装配Bean。

扩展:

idea里的project 相当于 eclipse里的 workspace

idea里的module 相当于eclipse里的project 为了迎合现在的模块形式开发。

module可以称之为模块或者是子项目,依赖于父项目(project) . 开发时一般都是开发的module,因此父项目的src可以删掉

隐藏不想看到的文件或者文件夹

File—>settings—>Editor—>File Types—>ignored Files and Folders —->点击+号, 输入想要隐藏的内容,回车

2.2.1 创建module

先创建父工程spring-demo,然后再创建module,命名spring-ioc1

pom.xml导入:

<dependencies>
   <!-- 添加commons-logging依赖 -->
   <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.3</version>
   </dependency>
   <!-- 添加junit依赖 -->
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
   </dependency>
   <!-- 添加spring的依赖 -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.10.RELEASE</version>
   </dependency>
</dependencies>

导入后保存pom文件,项目如图所示:

 

 spring-core:Spring的核心工具包 

        这个jar 文件包含Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
外部依赖Commons-Logging, Log4J。 

spring-beans:Spring IOC的基础实现,包含访问配置文件、创建和管理bean等。 

        这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean 以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。
外部依赖spring-core,cglib-nodep.jar。 

spring-context:在基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持。 

        这个jar文件为Spring核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。

外部依赖spring-core.jar,spring-beans.jar,spring-aop.jar,commons-collections.jar,aopalliance.jar。

spring-expression:Spring表达式语言 

spring-aop:Spring的面向切面编程,提供AOP(面向切面编程)的实现 

        这个jar文件包含在应用中使用Spring的AOP特性时所需的类。使用基于AOP的Spring特性,如声明型事务管理(Declarative Transaction Management),也要在应用里包含这个jar包。

外部依赖:spring-core.jar,spring-beans.jar,cglib-nodep.jar,aopalliance.jar

附:aopalliance.jar:这个包是AOP联盟的API包,里面包含了针对面向切面的接口。通常Spring等其它具备动态织入功能的框架依赖此包。

cglib-nodep.jar:这个包是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 当然这些实际的功能是asm所提供的,asm又是什么?Java字节码操控框架。cglib就是封装了asm,简化了asm的操作,实现了在运行期动态生成新的class。 实际上CGlib为spring aop提供了底层的一种实现;为hibernate使用cglib动态生成VO/PO (接口层对象)。

2.2.2 编写实体类

public class User {
    private String username;

    public User(){}
    public User(String username){
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        User user = (User) object;
        return Objects.equals(username, user.username);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(username);
    }

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

2.2.3 编写核心配置文件

在src/main/resources目录下 ,创建Spring的核心配置文件, 这里我们命名为beans.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">
</beans>

2.2.4 配置Bean

在 beans.xml 文件中添加如下配置:将User类作为Bean装配到Spring IoC容器中

<!--    注册java类的bean对象,id是唯一标识符, class用来指定类的全限定名。-->
<!-- 此时User类的对象就是Spring容器帮助我们创建对象,默认调用无参构造器-->
    <bean id="u1" class="com.springioc.pojo.User"  />

2.2.5 测试

@Test
    public void testIOC(){
        // 使用ClassPathXmlApplicationContext的构造器来加载spring的核心配置文件,返回的就是一个spring的ioc容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-ioc.xml");
        // 从容器中获取想要的bean对象: 在这里,程序员是通过了bean的配置,然后容器在启动后解析文件时,它主动创建的对象。
        // 注意: ioc 容器在帮程序员创建类的实例时,调用的是无参构造器。
        Object u1 = ac.getBean("u1");
        System.out.println(u1);
    }

2.2.6 Spring小结

1)在程序运行后,Spring会到配置文件中读取 Bean 配置信息,根据配置信息生产 Spring IoC容器对象

2)Spring IoC 容器根据读取到的Bean配置信息,到硬盘上加载相应的 Class 字节码文件,根据反射创建 Bean 实例。

3)Spring会将创建的Bean存储到 Spring 容器中,也就是所谓的 Bean Pool(Bean池,底层是一个Map)中

4) 根据 Spring 提供的 getBean 方法获取Bean实例并应用到我们的应用程序中。

思考:

User 对象是谁创建的 ?                     【User对象是由Spring创建的】

User 对象的属性是怎么设置的 ?         【User 对象的属性是由Spring容器设置的】     

这个过程就叫控制反转 :

控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的

反转 : 程序本身不创建对象 , 而变成被动的接收对象 。

2.2.7 扩展1

下面列出了BeanFactory接口的几个常用方法,可以在程序中试一试。

/* 根据bean的名称,返回指定bean的一个实例(如果有) */
Object getBean(String name) throws BeansException;

/* 根据Bean的类型(也可以是父类或父接口,可以创建一个Person接口,让User实现),返回唯一匹配给定对象类型的bean实例(如果有) */
<T> T getBean(Class<T> requiredType) throws BeansException;

/* 检索这个Bean工厂是否包含一个给定名称的bean实例  */
boolean containsBean(String name);

/* 获取Bean的类型  */
Class<?> getType(String name) throws NoSuchBeanDefinitionException;

/* 根据Bean的名称,检索当前Bean是不是单例,如果是则返回true */
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

/* 根据Bean的名称,检索当前Bean是不是多例,如果是则返回true */
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
import cn.huac.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTests {
    @Test
    public void testIOC(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) applicationContext.getBean("user");
        System.out.println(user);
        User user2 = applicationContext.getBean(User.class);
        System.out.println(user2);
        boolean b1 = applicationContext.containsBean("user");
        boolean b2 = applicationContext.containsBean("userInfo");
        System.out.println(b1 + ", " + b2);
        Class cls = applicationContext.getType("user");
        System.out.println(cls);
        boolean singleton = applicationContext.isSingleton("user");
        boolean prototype = applicationContext.isPrototype("user");
        System.out.println(singleton + ", " + prototype);
    }
}

2.2.8 扩展2(自学)

        每个<bean>元素配置一个由Spring IoC容器所管理的应用程序对象。在Spring框架的术语中,一个<bean>元素代表一个Bean。id属性指定bean的唯一名称,class属性指定bean的完全限定类名。

import导入
这个import,一般用于团队开发;

可以将多个配置文件,导入合并为一个;

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd">
   <import resource="beans.xml"></import>
   <import resource="beans2.xml"></import>
   <import resource="beans3.xml"></import>
</beans>

        项目多人开发,多个人复制不同的类开发,不同的类需要注册不同的bean中,我们就可以利用import将所有人的beans.xml合并成一个总的!

2.3 Bean的作用域

        在Spring容器中管理的Bean对象的作用域,可以通过scope属性或用相关注解指定其作用范围。

<bean id="user" scope="singleton" class="xxxxx.User"></bean>

2.3.1 作用域的种类

1)singleton

单实例(默认),这个作用域标识的对象具备全局唯一性。

  • 当把一个 bean 定义设置 scope 为singleton作用域时,那么Spring IoC容器只会创建该bean定义的唯一实例。也就是说,整个Spring IoC容器中只会创建当前类的唯一一个对象。
  • 这个单一实例会被存储到 bean池中并且所有针对该 bean 的后续请求和引用都将返回被缓存的、唯一的这个对象实例。
  • 单例的bean对象由spring负责创建、初始化、存储、销毁(在spring容器销毁时销毁)

配置如下

<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

 测试:

@Test
public void test03(){
   ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
   User user = (User) context.getBean("user");
   User user2 = (User) context.getBean("user");
   System.out.println(user==user2);
}
2)prototype

多实例。这个作用域标识的对象每次获取都会创建新的对象

         当把一个 bean 定义设置 scope 为prototype作用域时,Spring IoC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例(相当于new的操作),该实例不会被存储到bean池中,spring也不负责销毁,当程序不再使用这个对象时,由GC负责销毁。

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
3)Request

        当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

        针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

4)Session

        当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

 <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

        针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

2.3.2 作用域的选择

咱们从使用频率和线程安全的角度来思考

1)从使用频次上考虑,如果一个对象使用的频率非常高,建议使用单例,这样会将bean对象存储到bean池中,从始至终只有一个实例,可以减少对象创建,减少对资源的消耗。

2)在没有线程安全问题的前提下,没必要每次获取都创建一个对象,这样子既浪费CPU又浪费内存

3)从使用频次上考虑,如果一个对象使用的频率非常低,没有必要将对象存储到map中(存储到bean池中的对象会一直存在bean池中,在spring容器销毁时销毁),建议使用多例

4)如果一个对象被多个线程所共享,并且对象中存在共享数据,一旦这个数据被多个线程所操作,就可能会产生线程不安全问题,可以考虑使用多例

2.3.3 Bean的生命周期

生命周期:指一个对象何时创建、何时销毁以及从创建之后到销毁之前的所处的状态

 单实例对象(singleton)

出生:当spring容器对象创建时,bean对象就会被创建
活着:只要容器没有销毁,bean对象就会一直存活
死亡:当spring容器销毁,bean对象也会跟着消亡

总结:单例对象的生命周期和容器相同,spring容器负责singleton对象的创建、存储、销毁(随着spring容器销毁而销毁)。

多实例对象(prototype):

出生:当获取bean对象时,spring框架才会为我们创建bean对象
活着:只要对象是在使用过程中,就会一直存活
死亡:当对象长时间不用,且没有别的对象引用时,由Java垃圾回收机制负责回收

总结:spring容器只负责prototype对象的创建和初始化,不负责存储和销毁。当对象长时间不用时,由Java垃圾回收机制负责回收

2.4 依赖注入(DI)

        DI(Dependency Injection)依赖注入 。即组件之间的依赖关系由容器在应用系统(项目)运行期来决定,也就是由容器动态地将存在某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。

简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值

 如果对象由我们自己创建,这一切都变得很简单,例如:

User user = new User();
user.setName("小明"); //通过setName方法为name属性赋值
user.setAge(18); //通过setAge方法为age属性赋值

或者:

User user = new User("小明", 18); //在创建对象的同时,通过有参构造函数为对象的属性赋值

        如果对象由Spring IoC容器创建,那么Spring是怎么给属性赋值的?Spring主要提供两种方式为属性赋值:

即:set方法注入和构造方法注入。

        此外还有:@Autowired注解方式;静态工厂的方法注入;实例工厂的方法注入等。

2.4.1 set方法注入

        顾名思义,即要求被注入的属性 , 必须在类中有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is。spring框架底层会调用set方法为成员变量赋值。

例如:Spring框架负责创建User的Bean对象之后,会通过调用User对象的setName方法为name属性赋值。

1)数据准备
package com.shuilidianli.pojo;

public class Address {
    private String province;
    private String city;

    public void setProvince(String province) {
        this.province = province;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{" +
                "province='" + province + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

 Student.java

package com.shuilidianli.pojo;

import java.util.*;

public class Student {
    private int id;
    private String name;
    private Address addr;
    private String[] books;
    private List<String> hobbys;
    private Set<String> games;
    private Map<String,String> cards;
    private Properties info;
    private String mgr;


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

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

    public void setAddr(Address addr) {
        this.addr = addr;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public void setCards(Map<String, String> cards) {
        this.cards = cards;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    public void setMgr(String mgr) {
        this.mgr = mgr;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id + "\n"+
                ", name='" + name + '\'' +"\n"+
                ", addr=" + addr +"\n"+
                ", books=" + Arrays.toString(books) +"\n"+
                ", hobbys=" + hobbys +"\n"+
                ", games=" + games +"\n"+
                ", cards=" + cards +"\n"+
                ", info=" + info +"\n"+
                ", mgr=" + mgr +
                '}';
    }
}
 2)常量注入

在<bean>标签里面写 使用<property>标签。

<bean id="e1" class="com.shuilidianli.pojo.Employee">
   <property name="id" value="1001"/>
   <property name="name" value="小明"/>
</bean>

 测试:

package com.shuilidianli.test;

import com.shuilidianli.pojo.Employee;
import com.shuilidianli.pojo.User;
import com.shuilidianli.web.EmpController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTest {
    @Test
    public void test1() {
        ApplicationContext ctx =
                new ClassPathXmlApplicationContext("beans.xml");
        Employee con =
                ctx.getBean("emp1", Employee.class);
        System.out.println(con);

    }
}
3)Bean注入

注意:这里的值是一个引用,ref

<bean id="a1" class="com.shuilidianli.pojo.Address">
   <property name="province" value="吉林"/>
   <property name="city" value="长春"/>
</bean>

<bean id="e1" class="com.shuilidianli.pojo.Employee">
   <property name="id" value="1001"/>
   <property name="name" value="小明"/>
   <property name="addr" ref="a1"/>
</bean>
 4)数组注入
<bean id="e1" class="com.shuilidianli.pojo.Employee">
   <property name="id" value="1001"/>
   <property name="name" value="小明"/>
   <property name="addr" ref="a1"/>
   <property name="books">
      <array>
         <value>完美世界</value>
         <value>无限恐怖</value>
         <value>警世通言</value>
      </array>
   </property>
</bean>
 5)List注入
<property name="hobbys">
   <list>
      <value>读书</value>
      <value>电影</value>
      <value>音乐</value>
   </list>
</property>
 6)set注入
<property name="games">
   <set>
      <value>王者荣耀</value>
      <value>和平精英</value>
      <value>原神</value>
   </set>
</property>
 7)Map注入
<property name="cards">
   <map>
      <entry key="中国邮政" value="100100001111"/>
      <entry key="中国建设" value="200200002222"/>
   </map>
</property>
 8)Properties注入
<property name="info">
   <props>
      <prop key="A001">迈巴赫</prop>
      <prop key="A002">玛莎拉蒂</prop>
      <prop key="A003">劳斯莱斯</prop>
   </props>
</property>
9)Null注入
<property name="mgr">
   <null/>
</property>
10)p命名空间

什么是 P 命名空间?

        P命名空间 不是新的东西,它是对 IoC 和 DI 的简化。使用 p 命名空间 可以更加方便地完成 bean 的配置以及 bean 之间的依赖注入。

如何使用 p 命名空间? 

首先必须确保 xml 文件中引入了 p 命名空间。在xml文件的头部

.....
xmlns:p="http://www.springframework.org/schema/p"
.....

 修改bean

<bean id="a1" class="com.shuilidianli.pojo.Address">
   <property name="province" value="吉林"/>
   <property name="city" value="长春"/>
</bean>

<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="e1" class="com.shuilidianli.pojo.Employee" p:id="1002" p:name="小红" p:addr-ref="a1">
   <property name="books">
      <array>
         <value>完美世界</value>
         <value>无限恐怖</value>
         <value>警世通言</value>
      </array>
   </property>
   //.......
</bean>

2.4.2 构造方法注入

        顾名思义,就是使用类中的构造函数为成员变量赋值——即构造方法注入。

需要注意的是:为成员变量赋值是通过配置的方式,让spring框架在创建bean对象的同时,为成员变量赋值。而不是我们自己去实现。

1)准备数据
package com.shuilidianli.pojo;

public class Student {
    private int id;
    private String name;
    private int age;
    private Address addr;

    //提供无参构造器
    public Student() {
    }
    //提供有参数构造器              注:如果添加了有参构造器,无参构造器必须添加。 因为框架底层很多地方都用到了无参构造器的
    public Student(int id, String name, int age,Address addr) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

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

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

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

    public void setAddr(Address addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id + "\n" +
                ", name='" + name + '\'' +"\n" +
                ", age=" + age +"\n" +
                ", addr=" + addr +
                '}';
    }
}
 2)配置beans.xml文件
<!-- 将Student类作为bean装配到Spring IoC容器中(即Student类的实例由Spring创建) -->
<bean id="student" class="com.shuilidianli.pojo.Student">
   <!-- 通过构造方法为Student Bean的id、name、age、addr属性赋值 -->
   <constructor-arg name="id" value="2001"/>
   <constructor-arg name="name" value="小天"/>
   <constructor-arg name="age" value="23"/>
   <constructor-arg name="addr" ref="a1"/>
</bean>

其中,constructor-arg标签name属性的值必须和构造函数中参数的名字相同!

同样的,普通属性直接通过value注入即可;对象属性通过ref属性注入。

3)c 命名空间注入   

需要在头文件中加入约束文件

导入约束 : xmlns:c="http://www.springframework.org/schema/c"

 修改beans.xml

<!-- 将Student类作为bean装配到Spring IoC容器中(即Student类的实例由Spring创建) -->
<bean id="student" class="com.shuilidianli.pojo.Student" c:id="2002" c:name="小王" c:age="24">
   <!-- 通过构造方法为Student Bean的id、name、age、addr属性赋值 -->
   <!--<constructor-arg name="id" value="2001"/>
        <constructor-arg name="name" value="小天"/>
        <constructor-arg name="age" value="23"/>-->
   <constructor-arg name="addr" ref="a1"/>
</bean>

 这里也能知道,c 就是所谓的构造器注入!

5)其他构造方法注入写法

<bean id="student" class="com.shuilidianli.pojo.Student">
   <!-- index指构造方法 , 下标从0开始 -->
   <constructor-arg index="0" value="2001"/>
   <constructor-arg index="1" value="小舞"/>
   <constructor-arg index="2" value="23"/>
   <constructor-arg index="3" ref="a1"/>
</bean>

也可以使用c标签

<!-- 将Student类作为bean装配到Spring IoC容器中(即Student类的实例由Spring创建) -->
<bean id="student" class="com.shuilidianli.pojo.Student" c_2="2002" c_1="小舞" c:age="24">
   
</bean>

2.5 自动装配

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring的自动装配需要从两个角度来实现,或者说是两个操作:

1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显式的配置降低到最少。

2.5.1 数据准备

新建项目

新建两个实体类,Cat Dog 都有一个叫的方法

public class Cat {
   public void call() {
       System.out.println("miao~miao~miao");
  }
}
public class Dog {
   public void call() {
       System.out.println("wang~wang~wang");
  }
}
​User.java
package com.sldl.pojo;
​
public class User {
    private String name;
    private Dog dog;
    private Cat cat;
​
    public void setName(String name) {
        this.name = name;
    }
​
    public void setDog(Dog dog) {
        this.dog = dog;
    }
​
    public void setCat(Cat cat) {
        this.cat = cat;
    }
​
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

配置文件

<?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="dog" class="com.sldl.pojo.Dog"></bean>
    <bean id="cat" class="com.sldl.pojo.Cat"></bean>
    
    <bean id="user" class="com.sldl.pojo.User">
        <property name="name" value="小明"/>
        <property name="dog" ref="dog"/>
        <property name="cat" ref="cat"/>
    </bean>
</beans>

测试没问题,环境OK

public class IOCTest {
    ApplicationContext ctx = null;
​
    @Before
    public void testBefore(){
        ctx =
            new ClassPathXmlApplicationContext("beans.xml");
    }
​
    @Test
    public void test1(){
        //获取UserBean
        User user = ctx.getBean("user", User.class);
        System.out.println(user);
    }
}

2.5.2 byName

        autowire byName (按名称自动装配)。由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。

1)修改bean.xml配置

增加一个属性 autowire=“byName”

<?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="dog" class="com.sldl.pojo.Dog"></bean>
    <bean id="cat" class="com.sldl.pojo.Cat"></bean>
    
    <!-- User的属性叫cat和dog  ByName自动装配是通过相同的名字进行匹配-->
    <bean id="user" class="com.sldl.pojo.User" autowire="byName">
        <property name="name" value="小明"/>
        <!--<property name="dog" ref="dog"/>
        <property name="cat" ref="cat"/>-->
    </bean>
</beans>
2)再次测试

结果依旧成功输出!

 

3)修改dog和cat的bean的id值

如下

4)再次测试,结果如下,没有赋值成功

5)小结

当一个bean节点带有 autowire byName的属性时。

将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。

去spring容器中寻找是否有此字符串名称id的对象。

如果有,就取出注入;如果没有,就是null,没赋值上。

2.5.3 byType

autowire=”byType” (按类型自动装配)。使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。NoUniqueBeanDefinitionException 1

1)将user的bean配置修改一下 : autowire=“byType”

2)测试,正常输出

 

3)再注册一个cat 的bean对象!  

<bean id="dog" class="com.sldl.pojo.Dog"></bean>
<bean id="cat" class="com.sldl.pojo.Cat"></bean>
<bean id="cat2" class="com.sldl.pojo.Cat"></bean>

<bean id="user" class="com.sldl.pojo.User" autowire="byType">
   <property name="str" value="小明"/>
</bean>

 正常有警告如下:

Could not autowire. There is more than one bean of 'Cat' type. Beans: cat,cat2. Properties: 'cat'

 4)测试,报错:NoUniqueBeanDefinitionException

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.sldl.pojo.Cat' available: expected single matching bean but found 2: cat,cat2

表示,在容器中,找到了多个该类型的Bean,不是唯一的,因此赋值失败。

5)删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

这就是按照类型自动装配!

小结:

  • byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性set方法的值一致!

  • byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

2.6 使用注解

jdk1.5开始支持注解,spring2.5开始全面支持注解。

准备工作:利用注解的方式注入属性。

1、在spring配置文件中引入context文件头

<?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">   <---  

</beans>   

 用的时候,别忘记删掉 箭头!

2、开启属性注解支持!

<context:annotation-config/>

其实就是代替下面四个类型的bean注入

com.AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor

2.6.1 @Autowired

@Autowired是按类型自动注入的,不支持id匹配。 确认一下,是否引入 spring-aop的包!

测试:

1、将User类中的set方法去掉,使用@Autowired注解

package com.sldl.pojo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class User {
    private String name;

    @Autowired
    private Dog dog;

    @Autowired
    private Cat cat;

    public void setName(String name) {
        this.name = name;
    }
   /*
    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }*/

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

 2、此时配置文件内容

<context:annotation-config/>


<bean id="dog" class="com.sldl.pojo.Dog"></bean>
<bean id="cat" class="com.sldl.pojo.Cat"></bean>


<bean id="user" class="com.sldl.pojo.User">
   <property name="name" value="小明"/>
   <!--
   <property name="dog" ref="dog"/>
   <property name="cat" ref="cat"/>
   -->
</bean>

 3、测试,成功输出结果!

 

小结:

@Autowired(required=false) 说明:false,对象可以为null; true,不能为null。

 //如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;

2.6.2 @Qualifier

@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配

@Qualifier不能单独使用。

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!

<bean id="dog1" class="com.sldl.pojo.Dog"></bean>
<bean id="dog2" class="com.sldl.pojo.Dog"></bean>
<bean id="cat1" class="com.sldl.pojo.Cat"></bean>
<bean id="cat2" class="com.sldl.pojo.Cat"></bean>

2、没有加Qualifier测试,直接报错。因为类型不唯一。  

3、在属性上添加Qualifier注解  

public class User {
    private String name;

    @Autowired
    @Qualifier("dog2")
    private Dog dog;

    @Autowired
    @Qualifier("cat2")
    private Cat cat;
    
   //.......
}

 测试,成功输出!

2.6.3 @Resource

@Resource如有指定的name属性,先按该属性进行byName方式查找装配;

  • 其次再进行默认的byName方式进行装配;

  • 如果以上都不成功,则按byType的方式自动装配。

  • 都不成功,则报异常。

1)测试1

实体类:

public class User {
   private String name;
   
   //如果允许对象为null,设置required = false,默认为true
   @Resource(name = "cat2")
   private Cat cat;
   
   @Resource
   private Dog dog;

   //.....
}

beans.xml

<bean id="dog" class="com.sldl.pojo.Dog"/>
<bean id="cat1" class="com.sldl.pojo.Cat"/>
<bean id="cat2" class="com.sldl.pojo.Cat"/>

<bean id="user" class="com.sldl.pojo.User">
   <property name="name" value="小明"/>
<bean>

测试:结果OK

2)测试2

配置文件:beans.xml , 删掉cat2

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>

实体类上只保留注解

@Resource
private Cat cat;
@Resource
private Dog dog;

结果:OK

结论:先进行byName查找,失败;再进行byType查找,成功。

 

3)小结

@Autowired与@Resource异同:

  • @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

  • @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖的bean在容器中存在,否则报错。如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

  • @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

  • 它们的作用相同,都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,再byName,

    @Resource先byName,再byType

2.6.4 @Component

1)说明

在spring4之后,想要使用注解形式,必须得要引入aop的包,检查自己是否引入了aop依赖包

 在配置文件当中,还得要引入一个context约束

<?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">

</beans>
2)Bean的实现

我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

步骤1 配置扫描哪些包下的注解

<!--指定注解扫描包-->
<context:component-scan base-package="com.kuang.pojo"/>

步骤2 在指定包下编写类,增加注解  

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   public String name = "小明";
}

 步骤3 测试

@Test
public void test(){
   ApplicationContext applicationContext =
      new ClassPathXmlApplicationContext("beans.xml");
   User user = (User) applicationContext.getBean("user");
   System.out.println(user.name);
}
3)属性注入

使用注解注入属性

1、可以不用提供set方法,直接在属性名上添加@value(“值”)

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   @Value("小明")
   // 相当于配置文件中 <property name="name" value="小明"/>
   public String name;
}

 2、如果提供了set方法,最好在set方法上添加@value(“值”);

@Component("user")
public class User {

   public String name;

   @Value("小明")
   public void setName(String name) {
       this.name = name;
  }
}
4) 衍生注解

这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!

@Component三个衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller <====web层

  • @Service <====service层·

  • @Repository <==== dao层

写上这些注解,就相当于将这个类交给Spring管理装配了!

2.6.5 作用域@scope

singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。

prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

@Controller("user")
@Scope("prototype")
public class User {
   @Value("小明")
   public String name;
}

2.7 小结

XML与注解比较
  • XML可以适用任何场景 ,结构清晰,维护方便

  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践
  • xml管理Bean

  • 注解完成属性注入

  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

针对于下面的配置,再次说明一下:

<context:annotation-config/>      @Autowired  @Quilfi...  @Resource  
作用:
  • 进行注解驱动注册,从而使注解生效

  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显式的向Spring注册

  • 如果不扫描包,就需要手动配置bean

  • 如果不加注解驱动,则注入的值为null!

  • 如果开启了包扫描, 就不用再添加 注册驱动开关了

<context:component-scan base-package="com.springioc.pojo"/>
代替了
<context:annotation-config/> 

2.8 基于Java类进行配置

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

2.8.1 测试1

1、编写一个实体类,Phone

package com.sldl.pojo;

public class Phone {
    private String brand;
    private String model;
    private double price;

    public Phone(String brand, String model, double price) {
        this.brand = brand;
        this.model = model;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "brand='" + brand + '\'' +
                ", model='" + model + '\'' +
                ", price=" + price +
                '}';
    }
}

 2、新建一个config配置包,编写一个MyConfig配置类

package com.sldl.config;

import com.sldl.pojo.Phone;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件
@Configuration
public class MyConfig {

    @Bean   //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
    public Phone phone(){
        return new Phone("MI","14Pro",6800);
    }
}

3、测试

@Test
public void test2(){
   ApplicationContext app =
      new AnnotationConfigApplicationContext(MyConfig.class);

   Phone phone = app.getBean("phone", Phone.class);
   System.out.println(phone);
}

4、成功输出结果!

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!  

;