1.Spring FrameWork简介

1.1 什么是Spring Framework

  • Spring Framework是一个轻量级控制反转(Inversion Of Control)和面向切面编程(Aspect Oriented Programing)为内核的容器框架,

    它主要是为了解决企业应用开发的复杂性而诞生的:

    1. 目的:解决企业级应用开发的复杂性。
    2. 功能:使用基本的JavaBean代替EJB。
    3. 范围:任何Java应用。
  • Spring Framework提供了展现层Spring FrameworkMVC持久层Spring FrameworkJDBCTemplate以及业务层事务管理等众多的企业级应用技术,还可以整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用框架。

1.2 Spring Framework的优势

  1. 方便解耦,简化开发

    通过Spring Framework提供的Ioc容器,可以将对象间的依赖关系交由Spring Framework进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

  2. AOP编程的支持

    通过Spring Framework的AOP功能,方便进行面向切面的编程,许多不容易也用传统OOP(面向对象编程)实现的功能都可以通过AOP轻松应付。

  3. 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

  4. 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  5. 方便继承各种优秀的框架

    Spring Framework可以降低各种框架的使用步骤,提供了对各种框架(struts、Hibernate、Hessian、Quart2等)的直接支持。

  6. 降低JavaEEAPI的使用难度

    Spring Framework对JavaEE API(如JDBC、JavaMail、远程调试等)进行了薄薄的封装层,使这些API的使用难度大为降低。

  7. Spring Framework的源码是经典的学习范例

    Spring Framework的源代码设计精妙,结构清晰,匠心独具,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践典范。

1.3 Spring Framework的体系结构

现在我们所说的Spring Framework都是一个统称,Spring Framework框架至今已集成了20多个模块,这些模块分布在以下模块中:

  • 核心容器(Core Container)
  • 数据访问/集成(Data Access/Integration)层
  • Web层
  • AOP(Aspect Oriented Programming)模块
  • 植入(Instrumentation)模块
  • 消息传输(Messaging)
  • 测试(Test)模块

Spring Framework体系结构如下图:

img

2. Spring Framework快速开发

  1. 导包工具导入Spring Framework的坐标
  2. pom.xml

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
    </dependency>
  3. 编写业务层与表现层接口与实现类
  4. userDao接口

    public interface UserDao {
    public void save();
    }
  • userDaoImpl实现类

    package com.itheima.dao.impl;
    
    import com.itheima.dao.UserDao;
    import lombok.Setter;
    
    @Setter
    public class UserDaoImpl implements UserDao {
    private String username,password;
    
        public UserDaoImpl(String username, String password) {
        this.username = username;
        this.password = password;
        }
    
        public UserDaoImpl() {
    
        }
    
        @Override
        public void save() {
        System.out.println("userDaoImpl save...,username="+username+",password="+password);
        }
        
    }
  1. 建立Spring Framework配置文件,配置所需资源为Spring Framework控制的资源

    • 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:p="http://www.springframework.org/schema/p"
           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>
        <!--1.UserSerivce配置,通过Setter方法注入依赖-->
        <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
            <property name="userDao" ref="userDao"/>
        </bean>
        <!--2.在bean中读取读取配置文件中的值,beanid是xml唯一标识,class指定全路径名-->
        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </bean>
    </beans>
  2. 使用Spring Framework的API获取Bean的实例

    • UserController
    public class UserController {
        public static void main(String[] args) {
            //Application是父接口,但不是顶级接口,顶级接口是beanFactory
            //通过类路径获取bean核心容器配置文件信息
            ApplicationContext factory =
                    new ClassPathXmlApplicationContext("spring/applicationContext.xml");
            UserService userService = (UserService) factory.getBean("userService");
            userService.save();
        }
    }

image-20210709174050435

3. IOC容器

3.1 IOC容器和bean的介绍

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。该 BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContextBeanFactory的子接口。它有一些额外的功能:

  • 更容易与 Spring 的 AOP 特性集成
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层特定的上下文,例如WebApplicationContext 在 Web 应用程序中使用的 。

image-20210712123249778

​ 容器体系图

简而言之,它BeanFactory提供了配置框架和基本功能,并且ApplicationContext添加了更多企业特定的功能。该ApplicationContextBeanFactory的完全超集。

在 Spring 中,组成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。除此之外,bean 只是应用程序中众多对象之一。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。

3.2 容器概述

org.springframework.context.ApplicationContext接口代表负责实例化、配置和组装bean的Spring IoC容器。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注释或 Java 代码表示。它可以让您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

ApplicationContextSpring 提供了该接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext 或的实例 FileSystemXmlApplicationContext。虽然 XML 一直是定义配置元数据的传统格式,但您可以通过提供少量 XML 配置来声明性地启用对这些附加元数据格式的支持,从而指示容器使用 Java 注释或代码作为元数据格式。

在大多数应用场景中,不需要显式的用户代码来实例化一个或多个 Spring IoC 容器实例。例如,在 Web 应用程序场景中,应用程序文件中的简单八行(左右)样板 Web 描述符 XMLweb.xml通常就足够了(请参阅Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用 Spring Tools for Eclipse(一个 Eclipse 驱动的开发环境),您可以通过点击几下鼠标或按键轻松创建这个样板配置。

下图显示了 Spring 如何工作的高级视图。您的应用程序类与配置元数据相结合,因此,在ApplicationContext创建和初始化之后,您就有了一个完全配置且可执行的系统或应用程序。

容器魔法

​ 图 1. Spring IoC 容器

3.2.1 配置元数据

IoC容器支持多种配置元数据配置的格式,元数据的配置代表告诉Spring容器怎么初始化配置组装你的应用对象。Spring支持XML文件和代码上的注解方式(Spring 2.5引入)配置元数据。

  1. 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="..." class="...">  
            <!-- collaborators and configuration for this bean go here -->
        </bean>
    
        <bean id="..." class="...">
            <!-- collaborators and configuration for this bean go here -->
        </bean>
    
        <!-- more bean definitions go here -->
    
    </beans>
  2. 注解格式

    @Bean、@Componet等

3.2.2 初始化容器

ApplicationContext

提供给ApplicationContext构造函数的一个或多个路径资源字符串可以让容器加载来自不同外部资源(本地文件系统,Java的classpath等等)配置元数据。

1. 用`ClassPathXmlApllicationContext`类加载配置信息
   从类的跟路径下加载配置文件,相对路径读取,**推荐**使用此类
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
  1. FileSystemXmlApplicaitonContext类加载配置信息
    从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置,如果应用部署在不同机器上可能会需要修改路径。

    ApplicationContext ctx =
     new FileSystemXmlApplicationContext("conf/appContext.xml");

3.2.3 使用容器

ApplicationContext是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用 方法 T getBean(String name, Class<T> requiredType),您可以检索 bean 的实例。

ApplicationContext让你读bean定义和访问它们,如下例所示:

// 创建和配置bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// 取得配置的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用配置的实例
List<String> userList = service.getUsernameList();

也可以使用最灵活的变体GenericApplicationContext配合reader代理,比如XML文件就可以用XmlBeanDefinitionReader,以下是例子

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();

可以在同一个ApplicationContext使用混合和匹配类似的reader代理从不同的配置资源中去读取bean定义。

可以使用它getBean来检索 bean 的实例。该ApplicationContext 接口有一些其他方法来检索 bean,但理想情况下,您的应用程序代码永远不应该使用它们。实际上,您的应用程序代码根本不应该调用该 getBean()方法,因此完全不依赖于 Spring API。例如,Spring 与 Web 框架的集成为各种 Web 框架组件(例如控制器和 JSF 管理的 bean)提供了依赖注入,让您可以通过元数据(例如自动装@Autowired配注解)声明对特定 bean 的依赖。

//Controller中直接用@Autowired注解去让容器注入依赖,不需要调用api
@Autowired
    private DepartmentService departmentService;

3.3 配置文件详解

3.3.1 bean标签的基本属性

用于配置对象交给Spring创建。默认情况它调用的是类中的无参构造函数,如果没有无参构造函数泽不能创建成功。

基本属性

<bean id="userDao" class="com.org.test.dao.impl.UserDaoImpl" scope="singleton"></bean>

  • id

    bean在Spring容器中的唯一标识

  • class

    bean对应的类全路径名,提供给Spring容器反射创建对象使用。

  • scope

    指的是bean对象在spring容器中的作用范围

    取值范围说明
    singleton默认值,可以不写,单例
    prototype多例对象
    requestWeb项目中,Spring创建一个bean对象,将对象存入到request域中
    sessionWeb项目中,Spring创建一个bean对象,将对象存入到session域中
    global sessionWeb项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session
    • singleton

      1. spring容器创建的单例对象,但不是和设计模式的单例相同的概念
      2. 在初始化容器时就会进行加载,只会加载一次
      3. 当应用卸载,销毁容器,对象就会被销毁
    • prototype

      1. spring创建的多例对象,可以存在多个
      2. 通过容器获取对象(container.getBean(beanid))时才会创建,并且可以加载多次
      3. 对象长时间不使用,就会被GC回收
  • init-method

    指定类中的初始化方法名称,在类初始化后会调用

  • destroy-method
    指定类中的销毁方法名称,容器正常销毁前会调用

    <!--1.bean创建默认type为singleton,生命周期归container管,container容器启动时就会创建singleton的type对象,可以调用preDestory方法-->
    <bean id="userDao1" class="com.itheima.dao.impl.UserDaoImpl1" init-method="init" destroy-method="destroy" scope="singleton" />
    public class UserDaoImpl1 implements UserDao {
        public UserDaoImpl1() {
            System.out.println("UserDaoImpl1创建了");
        }
        @Override
        public void save() {
            System.out.println("user save");
        }
    
        /**
         * 类初始化后调用
         */
        private void init() {
            System.out.println("usrDaoImp1 init method...");
        }
    
         /**
         * 类销毁前调用
         */
        private void destroy() {
            System.out.println("usrDaoImp1 destroy method...");
        }
    }

3.3.2 bean实例化的三种方式(了解)

  • 无参构造方法实例化
  • 工厂静态方法实例化
  • 工厂实例方法实例化

3.3.3 bean的依赖注入分析

Spring Framework文档指出:

IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.

可以看到IOC的概念和DI其实是一样的,这是一个对象只有通过构造方法的参数、工厂方法的参数、设置在被创建后或被工厂方法返回后的对象实例的上的属性的这些方式去定义他们自己依赖(和他们合作的对象),然后容器在创建对象时注入这些依赖的一个过程。这个过程本质上就是bean对象通过使用class或者服务定位器模式(Service Locator Pattern)的直接构造去控制实例化和自己的依赖定位的一个反转(控制反转因此得名)。

传统方式获取对象,是直接new一个对象出来,在自己set依赖。现在有了Spring Framework框架,容器会帮我们省去自己new对象的一个过程,并会把定义好的依赖的对象注入依赖后给我们。

image-20210711005615732

3.3.4 依赖注入的方式

  1. setter注入,主流的方式

    容器通过bean对象内的set方法去设置依赖。必须要有一个无参构造方法,setXXX(参数),这里的XXX必须和配置文件bean中property标签的属性name相同

<!--set方式注入基本和引用类型,引用需要定义bean标签-->
<bean id="userService1" class="com.itheima.service.impl.UserServiceImpl">
  <!--ref指代引用对象,必须是bean的id-->
  <property name="userDao" ref="userDao"/>
  <!--value指代基本对象类型的值,直接写-->  
  <property name="num" value="666"/>
</bean>
  1. 构造方法注入

    容器通过构造方法传参的方式去设置依赖,bean对象要设置有参构造方法。

    <!--构造函数注入,可以用index和name避免注入时对应不知道给哪个导致模糊不清的问题-->
    <bean id="userService2" class="com.itheima.service.impl.UserServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="num" value="777"/>
    </bean>
  1. p命名空间

    p命名空间本质上也是setter注入
    需要在bean中引入xmlns:p属性

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           ...
           xmlns:p="http://www.springframework.org/schema/p"
    ...>
           
        <!--p命名空间注入,需要在xml文件开头引入xmlns:p="http://www.springframework.org/schema/p"-->
        <bean id="userService3" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao" p:num="888"/>
    
    </beans>
  2. SPEL
    Spring Expreesion Language 俗称EL表达式,所有的格式同意使用property的属性value='表达式',可以完成赋值。

    • 常量 #{10},#{3.14},#{2e5},#{'whyat'}
    • 引用bean #{beanId}
    • 引用bean方法 beanId.methodName().method2()
    • 引用bean方法 #{beanId.propertyName}
    • 引用静态方法 T(java.lang.Math).PI
    • 运算符支持 #{3 It 4 == 4 ge 3}
    • 正则表达式支持 #{user.name mathces '[a-z]{6,}'}
    • 集合支持 #{likes[3]}

3.3.5 bean依赖注入的数据类型

一共有三种类型可以注入

  1. 普通数据类型:value属性
  2. 引用数据类型:ref属性
  3. 集合数据类型; 省略...

    总共有 ``array list set map props``五种类型,可以放在property或construcor-arg标签中
    
<!--List集合类型注入数据-->
<property name="myList">
    <value>itheima</value>
    <value>666</value>
    <!--以下2个方式注入效果一样-->
    <ref bean="userService"/>
    <bean class="com.itheima.service.UserService"></bean>
</property>
<!--Properties集合类型注入数据-->
<property name="myProps">
    <props>
        <prop key="username">root</prop>
        <prop key="password">123</prop>
    </props>
</property>
<!--array集合类型注入数据,和list是互通的,可以互相替换标签-->
<property name="myArray">
    <value>itheima</value>
    <value>666</value>
    <!--以下2个方式注入效果一样-->
    <ref bean="userService"/>
    <bean class="com.itheima.service.UserService"></bean>
</property>

map set略,只作了解...

3.3.6 读取properties文件

Spring提供了读取外部properties文件的机制,使用读取到的数据为bean属性复制

步骤

  1. 准备外部properties文件

    username=root
    password=12345
  2. 在容器配置文件中开启context命名空间的支持

    <!--beans标签里的属性-->
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  3. 加载指定的properties文件

    <!--1.加载单个文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!--2.加载多个配置文件
    classpath*加载很慢,遍历所有的classpath
    *.properties加载所有的配置文件-->
    <context:property-placeholder location="classpath:*.properties"/>
  4. 使用加载数据

    <!--3.在bean中读取读取配置文件中的值-->
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl3">
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>

ℹ️注意

  1. 如果需要加载所有的配置文件,可以用classpath:*.properties表示所有的properties文件,但是缺点是加载很慢会遍历classpath*加载很慢
  2. 读取数据使用${propertiesName}格式进行,其中propertiesName指的是properties文件中的属性名

3.3.7 import标签

当配置的内容过多时,所有信息都在一个文件中,配置文件就会显得臃肿,不利于团队开发。可以将业务分层,使用一个主配置文件加载多个子配置文件,这就是import标签的作用。

  • 主配置文件

    <?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:p="http://www.springframework.org/schema/p"
           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">
        <!--1.加载context命名空间的支持-->
        <context:property-placeholder location="classpath:db.properties"/>
    
        <bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl3">
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </bean>
        <!--加载其他配置文件,进行分层方便团队开发/管理-->
        <import resource="applicationContext-user.xml"/>
    </beans>
  • applicationContext-user.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:p="http://www.springframework.org/schema/p"
           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">
    
        <bean id="userService1" class="com.itheima.service.impl.UserServiceImpl">
            <property name="userDao" ref="userDao"/>
        </bean>
    
    </beans>

ℹ️注意

使用import标签导入多个配置文件时,同id的bean在不同配置文件中会有冲突问题,后定义的覆盖先定义的,和引入配置文件的顺序有关。

3.3.8 第三方资源配置

如何将第三方的资源交给Spring进行管理?
按照之前的想法,先将对应的jar包导入,找到对应的类路径,然后使用bean标签创建对象,如果需要属性注入就使用property子标签。以整合阿里的durid连接池为例

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_ioc"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
</bean>

3.4 Spring注解配置

Spring注解是可以替代掉配置文件,在类中写注解可以专注于类本身,不用在类和配置文件中切换看配置了哪些项。

image-20210712133812498

但是注解无法设置在第三方jar包中,因为第三方资源是.class文件,并不能修改其源代码,所以只能依赖xml文件引入。

3.4.1 引入注解配置的方式

使用Spring注解,必须先注册bean,注册bean可以用配置文件或注解注册,注册的注解有

  • @Component, @Service, @Repository, @Controller, @Endpoint
  • @Configuration, @Bean, @Lazy, @Scope, @Order, @Primary, @Profile, @DependsOn, @Import, @ImportResource

然后再在xml中使用标签<context:annotation-config><context:component-scan>启动注解扫描

  • <context:annotation-config>
    是作用于那些已经在spring容器里注册过的bean,无论是通过xml的方式还是通过package sanning的方式(注意包含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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
    </beans>
  • <context:component-scan>
    除了具有<context:annotation-config>的功能之外,<context:component-scan>还可以在指定的package下扫描以及注册bean ,这就是和config标签的区别,config必须先注册bean才能用。(注意包含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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描驱动,指定了扫描目录-->
   <context:component-scan base-package="packageName"/>
</beans>

3.4.2 注解项

3.4.2.1 bean定义注解

  • @Component @Controller @Service @Repository

    类注解,用于替换bean标签的注解,可以在容器中注册bean

    ℹ️说明:

    @Controller、@Service 、@Repository是@Component的衍生注解,功能同@Component

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {

    // ...
}

@Controller、@Service 、@Repository是Spring的一个规范,分别对应web控制层、业务层、数据层
实际上与@Component相同,四个都可以混着用,但是建议按照规范书写,不然容易引发矛盾。

@Service("AccountServiceImpl")
public class AccountServiceImpl implements AccountService {
}
  • @Scope
    类注解,设置该类作为bean对应的scope属性
@Scope("prototype")
public class ClassName{}

相关属性
value(默认):定义bean的作用域,默认为singleton

  • @PostConstruct@PreDestroy
    作用在方法上,设置该类作为bean对应的生命周期方法
  • @Bean

    作用于方法,和@Controller、@Service 、@Repository作用相同类似@Component , @Repository , @ Controller , @Service 这些注册Bean的注解存在局限性,只能局限作用于自己编写的类,如果是一个jar包第三方库要加入IOC容器的话,这些注解就手无缚鸡之力了,是的,@Bean注解就可以做到这一点!当然除了@Bean注解能做到还有@Import也能把第三方库中的类实例交给spring管理

    @Configuration
    public class AppConfig {
    
         @Bean
         public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
               return new PropertySourcesPlaceholderConfigurer();
         }
    }

3.4.2.2 属性注入注解

  • 基本数据类型:@Value

    作用于基本数据类型上,一般用去注入外部properties文件里的属性值。可以是方法上,可以省略setter,因为Spring会用反射调用setAccessiable方法去掉属性的访问控制安全检查,可以直接获取私有属性,@Autowired同理。

    @Repository("userDao")
    public class UserDaoImpl implements UserDao {
        @Value("${username}")
        private String username;
        @Value("${password}")
        private String password;
    
        public UserDaoImpl() {
    
        }
        
        @Override
        public void save() {
            System.out.println("userDaoImpl save...,username="+username+",password="+password);
        }
    }

    db.properties文件:

    username=zckj
    password=12345

    Spring配置文件中要用占位符加载db.properties:

    <context:property-placeholder location="classpath:db.properties"/>
  • 引用数据类型:@Autowired@Qualifier

    类型:属性注解、方法注解
    位置:属性定义上方,方法定义上方
    作用:设置对应属性的对象或对方法进行引用类型传参

    ℹ️注意

    1. @Autowired默认按类型装配,指定@Qualifier后可以指定自动装配的bean的id
    @Autowired
    private UserDao userDao;
    @Repository("userDao")
    public class UserDaoImpl implements UserDao
    1. 如果想在多个没有id的实现类中使用@Autowired注解注入,就可以使用@Primary注解,Spring会根据类型和@Primary注解去自动找到装配的实现类
    @Autowired
    private UserDao userDao;
    @Repository
    @Primary
    public class UserDaoImpl implements UserDao
    @Repository
    public class UserDaoImpl1 implements UserDao

3.4.2.3 加载properties配置文件的注解

  • 名称:@PropertySource
  • 类型:类注解
  • 位置:类定义上方
  • 作用:加载properties文件中的属性值

范例:

@PropertySource(value = "classpath:filename.properties")
public class ClassName {
    @Value("${propertiesAttributeName}")
    private String attributeName;
}

ℹ️说明:
◆ 不支持*通配格式,一旦加载,所有spring控制的bean中均可使用对应属性值

◆ value(默认):设置加载的properties文件名
◆ ignoreResourceNotFound:如果资源未找到,是否忽略,默认为false

当你的配置文件都有,你可以在()里只写配置问价名,但是注意要是数组格式{1,2,3}

但是当你配置文件不存在会报错,如果想让有就加载没有就忽略的功能,那么加上 ignoreResourceNotFound=true,那么前面的数组必须对应value属性。

@PropertySource(value = {"classpath:filename.properties","abc.properties"},ignoreResourceNotFound=true)

结合之前基本类型的属性注入。就可以使用文件中的值,${} ,对应文件中的键

3.4.2.4 纯注解格式

使用纯注解替换掉Spring的xml配置文件

  • @Configuration

    作用在类上,声明这是一个配置类,就不用去加载spring的配置文件了

  • @ComponentScan

    作用在类上,作用等同于xml中<context:component-scan base-package=“packageName”/>,其value可以写成一个数组用于扫描多个文件夹

如下,声明了一个SpringConfig类,相当于xml文件:

package com.itheima.config;

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

@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.dao","com.itheima.service"})
@Import(...) //此处可以加载多个配置类,交给容器管理第三方bean
public class SpringConfig {
}

使用AnnotationConfigApplicationContext类去读取配置类:

AnnotationConfigApplicationContext ctx =
            new AnnotationConfigApplicationContext(SpringConfig.class);

3.4.2.5 加载bean的注解

  • @Import

    类注解,可以用于加载任何类到容器的注解,因为传统bean定义注解只能实现自己写的bean加载无法满足该要求了。
    @Import有三种使用方式

    1. class数组的方式,这些在容器中bean名称是该类的全类名

      @Import({ 类名.class , 类名.class... })
      public class TestDemo {
      
      }

      被导入的bean里面还可以再写@Import导入其他类

    2. 🚩自定义一个类实现ImportSelector接口

      可以导入任何类,可以结合配置文件导入类

      MyImportSelector.java

      import org.springframework.context.annotation.ImportSelector;
      import org.springframework.core.type.AnnotationMetadata;
      
      public class MyImportSelector implements ImportSelector {
          @Override
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              //直接把要导入的类全路径用数组写在这里即可
              return new String[] {"com.itheima.service.impl.ScoreServiceImpl"};
          }
      }

      SpringConfig.java

      import org.springframework.context.annotation.*;
      
      @Configuration
      @Import(MyImportSlecetor.class)
      public class SpringConfig {
      
      }

      selectImports(AnnotationMetadata annotationMetadata)

      返回值就是导入到容器中的组件全类名。

      • 返回值:就是我们实际上要导入到容器中的组件全类名
      • 参数:AnnotationMetadata表示当前被@Import注解给标注的类所有注解信息,也就是SpringConfig类的注解信息
    3. 实现ImportBeanDefinitionRegistrar接口的方式

      import.properties

      用该文件保存需要排除的类路径

      className=com.itheima.service.ScoreService

      MyImportBeanDefinitionRegistrar.java

      自定义一个用于注册类的类,实现该接口可以自定义动态的注册类,相当于@CompoentScan,只不过加载的类不需要用@Componet等注解

      package com.itheima.selector;
      
      import org.springframework.beans.factory.support.BeanDefinitionRegistry;
      import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
      import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
      import org.springframework.core.type.AnnotationMetadata;
      import org.springframework.core.type.classreading.MetadataReader;
      import org.springframework.core.type.classreading.MetadataReaderFactory;
      import org.springframework.core.type.filter.TypeFilter;
      
      import java.io.IOException;
      import java.util.ResourceBundle;
      
      public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
          private String classname;
          public MyImportBeanDefinitionRegistrar() {
              //加载导入类的配置文件
              ResourceBundle bundle = ResourceBundle.getBundle("import");
              String className = bundle.getString("className");
          }
      
          @Override
          public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
              //创建一个类路径bean定义读取器
              ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
              //添加一个类型过滤器,用于过滤不加载的类
              //排除掉import文件中定义的类
              scanner.addIncludeFilter(new TypeFilter() {
                  @Override
                  public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                      System.out.println(metadataReader.getClassMetadata().getClassName());
                      if (metadataReader.getClassMetadata().getClassName().equals(classname)){
                          return false;
                      }
                      return false;
                  }
              });
              //用配置好的读取器去扫描包
              scanner.scan("com.itheima");
          }
      }

      SpringConfig.java

      import com.itheima.selector.MyImportBeanDefinitionRegistrar;
      import org.springframework.context.annotation.*;
      
      @Configuration
      @PropertySource("classpath:jdbc.properties")
      @Import({JDBCConfig.class, MyBatisConfig.class, MyImportBeanDefinitionRegistrar.class
      })
      public class SpringConfig {
      }

3.4.2.6 bean加载控制

  • @DependsOn

    类、方法注解,控制bean的加载顺序,使其在指定bean加载完毕后再加载

    • 微信订阅号,发布消息和订阅消息的bean的加载顺序控制
    • 双11活动期间,零点前是结算策略A,零点后是结算策略B,策略B操作的数据为促销数据。策略B加载顺序与促销数据的加载顺序
    @DependsOn("beanId")
    public class ClassName {
    
    }
  • @Order

    类注解,控制配置类的加载顺序,值越小优先级越高。
    使用场景:有多种配置文件的话,优先加载系统级的,再加载业务级的。

    @Order(1)//值越小优先级越高
    public class SpringConfigClassName {
    }
  • @Lazy

    类方法注解,控制bean的加载时机,使其延迟加载
    应用场景:某个业务灾难出现后,对应的应急预案处理bean可以延迟加载。

    @Lazy
    public class ClassName {
    }

3.4.3 整合三方资源

3.4.3.1 纯注解整合Mybatis

xml配置文件改造成纯注解代码

image-20210714101126083

SpringConfig

把传统的xml注解全部替换成注解的形式,们只需要将对应部分换成注解就行了,其他的思想和思路和配置文件时一样的,只要你会配置文件整合,那么注解你就只需要多记忆几个注解。
首先我们制作一个主配置类,所以需要@Configuration申明这时一个著配置类,然后我们需要包扫描,所以需要@ConponentScan(“包路径”)
在实际开发中,一个外部资源,一个模块一个配置类,所以我们需要将其他配置类导入进来,使用@Import

import com.itheima.postprocessor.MyBeanFactoryPostProcessor;
import com.itheima.postprocessor.MyBeanPostProcessor;
import com.itheima.selector.MyImportSelector;
import org.springframework.context.annotation.*;

@Configuration//声明为配置类
@ComponentScan({"com.itheima"})//注解扫描
@PropertySource("classpath:jdbc.properties") //导入配置文件
@Import({JDBCConfig.class,MyBatisConfig.class})
public class SpringConfig {
    
}

JDBCConfig

需要读取properties配置文件,所以需要@PropertySource,那么和xml配置文件一样,同意在主类上加

我们需要从配置文件中读取数据@Value

我们需要类似工厂模式的方法加载第三方资源,所以需要@Bean,因为Import导入了,所以类上面不需要@Component

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

public class JDBCConfig {
    @Value("${mysql.drvier}")//直接使用${driver}读取不到值
    private String driver;
    @Value("${url}")
    private String url;
    @Value("${jdbc.username}")//直接使用${username}读不到值
    private String username;
    @Value("${password}")
    private String password;


    //告诉Spring容器从这里获取bean
    @Bean("dataSource")
    public DataSource getDataSource(){
        System.out.println(driver);
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

}

MybatisConfig

我们需要Sqlsession对象,所以使用工厂方法@Bean。反正第三方的资源,需要我们创建都对象才能使用的都使用@Bean注解工厂模式进行创建,因为源码我们无法进行修改。

ℹ️在此如果使用ssfb.setTypeAliasesPackage("mybatis/mapper_type-alias.xml");Mybatis会报Invalid bound statement (not found): com.itheima.dao.ScoreDao.selectByPrimaryKey异常。说找不到mybatis的主配置文件,可能是因为我的mapper_type-alias.xml放在的是resource目录下,而不是java目录下。

在这里需要使用setConfigLocation

注:使用Mybatis时,可以使用Idea插件MyBatisCodeHelper-Pro,db表生成java实体类,dao<->mapper跳转等。

image-20210714101828203

public class MyBatisConfig {
    @Bean
    //@Autowired可以从上面的Datasource获取后直接装配到这里
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setDataSource(dataSource);
        //找到mybatis的类别名和注册mapper的文件
        //ssfb.setTypeAliasesPackage("mybatis/mapper_type-alias.xml");
        ssfb.setConfigLocation(new ClassPathResource("mybatis/mapper_type-alias.xml"));
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        //设置dao接口层主目录
        msc.setBasePackage("com.itheima.dao");
        // msc.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
        return msc;
    }
}

mapper_type-alias.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--把数据库带下划线的数据映射成驼峰属性-->
    <settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--实体别名-->
    <typeAliases>
        <typeAlias type="com.itheima.entity.Score" alias="Score"/>
        <!--vo-->
    </typeAliases>
    <!--映射文件所在的位置-->
    <mappers>
        <mapper resource="mybatis/mapper/ScoreDao.xml"/>
    </mappers>

</configuration>

ScoreDaoMapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.ScoreDao">
  <resultMap id="BaseResultMap" type="com.itheima.entity.Score">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="sno" jdbcType="INTEGER" property="sno" />
    <result column="score" jdbcType="INTEGER" property="score" />
  </resultMap>
  <sql id="Base_Column_List">
    id, sno, score
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from score
    where id = #{id,jdbcType=INTEGER}
  </select>

</mapper>

`ScoreServiceImpl`

@Service("scoreService")
public class ScoreServiceImpl implements ScoreService {
    @Autowired
    ScoreDao scoreDao;
    @Override
    public Score selectUserById(int id) {
        return scoreDao.selectByPrimaryKey(id) ;
    }
}

ScoreDao

public interface ScoreDao {

    Score selectByPrimaryKey(Integer id);

}

App

public class App {
    public static void main(String[] args) {
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
        ScoreService scoreService= (ScoreService) app.getBean("scoreService");
        Score score = scoreService.selectUserById(1);
        System.out.println(score);
    }
}

3.4.3.2 整合Junit

在Junit中使用junit的资源

  1. Spring接管Junit的运行权,使用Spring提供的专门的Junit类加载器
  2. 为Junit测试用例设定对应的Spring容器

ℹ️注意

  1. 从Spring5.0以后,要求Junit的版本必须是4.12及以上
  2. Junit仅用于单元测试,不能把Junit测试类配置成bean,否则该配置将会被打包到工程中

pom.xml导入Spring整合Junit坐标

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

测试目录

image-20210714104545524

测试代码

//设定Spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//设定加载的Soring上下文对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class ScoreServiceTest {
    @Autowired
    private ScoreService scoreService;

    @Test
    public void testScoreFindById(){
        Score sc = scoreService.selectUserById(1);
        //断言测试
        Assert.assertNotNull(sc);
        System.out.println(sc);
    }
}