Spring企业开发核心框架(1)

目录

目录

框架前言

总体技术体系

框架概念和理解

Spring Framework简介

spring和springFramework

SpringFramework主要功能模块

SpringFramework的主要优势

SpringIoc容器

概念

Spring Ioc/DI的实现

1.实现步骤

2.基于XML方式管理Bean

声明配置文件和创建容器

获取bean

bean属性赋值,setter注入

bean属性赋值,引入其他bean

bean属性赋值:内部bean声明(了解)

  Bean 属性赋值:引入外部Properties配置参数

 高级特性:FactoryBean特性

 高级特性:Bean的作用域

 高级特性:Bean的生命周期

基于注解方式管理bean

bean注解的标记和扫描

bean属性赋值:引用类型自动装配(DI)

bean属性赋值:基本类型属性赋值(DI)

基于配置类方式管理bean

 完全注解开发理解

配置类和扫描注解

@Bean定义组件

高级特性:@Bean注解细节

高级特性:@Import扩展

高级特性:@Conditional扩展(了解)

三种配置方式总结

XML方式配置总结

 XML+注解方式配置总结

 完全注解方式配置总结



框架前言

总体技术体系

  • 单一架构

    一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one

        单一架构,项目主要应用技术框架为:Spring , SpringMVC , Mybatis等

  • 分布式架构

    一个项目(对应 IDEA 中的一个 project),拆分成很多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。

分布式架构,项目主要应用技术框架:SpringBoot , SpringCloud , 中间件等

框架概念和理解

框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。

框架的优缺点:

框架的优点包括以下几点:

  1. 提高开发效率:框架提供了许多预先设计好了的组件和工具,能够帮助开发人员快速进行开发。相较于传统手写代码,在框架提供的规范化环境中,开发者可以更快地实现项目的各种要求。

  2. 降低开发成本:框架的提供标准化的编程语言、数据操作等代码片段,避免了重复开发的问题,降低了开发成本,提供深度优化的系统,降低了维护成本,增强了系统的可靠性。

  3. 提高应用程序的稳定性:框架通常经过了很长时间的开发和测试,其中的许多组件、代码片段和设计模式都得到了验证。重复利用这些组件有助于减少bug的出现,从而提高了应用程序的稳定性。

  4. 提供标准化的解决方案:框架通常是针对某个特定领域的,通过提供标准化的解决方案,可以为开发人员提供一种共同的语言和思想基础,有助于更好地沟通和协作。

框架的缺点包括以下几个方面:

  1. 学习成本高:框架通常具有特定的语言和编程范式。对于开发人员而言,需要花费时间学习其背后的架构、模式和逻辑,这对于新手而言可能会耗费较长时间。

  2. 可能存在局限性:虽然框架提高了开发效率并可以帮助开发人员解决常见问题,但是在某些情况下,特定的应用需求可能超出框架的范围,从而导致应用程序无法满足要求。开发人员可能需要更多的控制权和自由度,同时需要在框架和应用程序之间进行权衡取舍。

  3. 版本变更和兼容性问题:框架的版本发布和迭代通常会导致代码库的大规模变更,进而导致应用程序出现兼容性问题和漏洞。当框架变更时,需要考虑框架是否向下兼容,以及如何进行适当的测试、迁移和升级。

  4. 架构风险:框架涉及到很多抽象和概念,如果开发者没有足够的理解和掌握其架构,可能会导致系统出现设计和架构缺陷,从而影响系统的健康性和安全性。

站在文件结构的角度理解框架,可以将框架总结:框架 = jar包+配置文件

莎士比亚说,"一千个观众眼中有一千个哈姆雷特" 即仁者见仁,智者见智.说每个人都会对作品有不同的理解,每个人对待任何事物都有自己的看法,同样的技术解决同样的问题会产生不同流程和风格的解决方案,而采用一种框架其实就是限制用户必须使用其规定的方案来实现,可以降低程序员之间沟通以及日后维护的成本!

常用的单一架构JavaEE项目框架演进,从SSH、SSH2过渡到了SSM:SpringMVC、Spring、MyBatis。

总之,框架已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。

Spring Framework简介

spring和springFramework

广义的 Spring:Spring 技术栈(全家桶)

广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。

经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

狭义的 Spring:Spring Framework(基础框架)

狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。

Spring Framework(Spring框架)是一个开源的应用程序框架,由SpringSource公司开发,最初是为了解决企业级开发中各种常见问题而创建的。它提供了很多功能,例如:依赖注入(Dependency Injection)、面向切面编程(AOP)、声明式事务管理(TX)等。其主要目标是使企业级应用程序的开发变得更加简单和快速,并且Spring框架被广泛应用于Java企业开发领域。

Spring全家桶的其他框架都是以SpringFramework框架为基础!

SpringFramework主要功能模块

功能模块功能介绍
Core Container核心容器,控制反转和依赖注入
AOP&Aspects面向切面编程
TX声明式事务管理
Testing快速整合测试环境
Data Access/Integration提供了对数据访问/集成的功能。
Spring MVC

提供了面向Web应用程序的集成功能。

                    SpringFramework框架结构图:

SpringFramework的主要优势

  1. 丰富的生态系统:Spring 生态系统非常丰富,支持许多模块和库,如 Spring Boot、Spring Security、Spring Cloud 等等,可以帮助开发人员快速构建高可靠性的企业应用程序。

  2. 模块化的设计:框架组件之间的松散耦合和模块化设计使得 Spring Framework 具有良好的可重用性、可扩展性和可维护性。开发人员可以轻松地选择自己需要的模块,根据自己的需求进行开发。

  3. 简化 Java 开发:Spring Framework 简化了 Java 开发,提供了各种工具和 API,可以降低开发复杂度和学习成本。同时,Spring Framework 支持各种应用场景,包括 Web 应用程序、RESTful API、消息传递、批处理等等。

  4. 不断创新和发展:Spring Framework 开发团队一直在不断创新和发展,保持与最新技术的接轨,为开发人员提供更加先进和优秀的工具和框架。

因此,这些优点使得 Spring Framework 成为了一个稳定、可靠、且创新的框架,为企业级 Java 开发提供了一站式的解决方案。

Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中采用 Java 语言所需的一切,支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种架构。从Spring Framework 6.0开始,Spring 需要 Java 17+。

SpringIoc容器

在学习Spring框架时,我们遇到的第一个也是最核心的概念就是容器。

什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。类似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux进程。

通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。早期的JavaEE服务器提供的EJB容器最重要的功能就是通过声明式事务服务,使得EJB组件的开发人员不必自己编写冗长的事务处理代码,所以极大地简化了事务处理。

Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。

概念

IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责,这样,应用程序只需要直接使用已经创建好并且配置好的组件。

IoC又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。

因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件来实现

Spring Ioc/DI的实现

1.实现步骤

  1. 配置元数据(配置)

    配置元数据,既是编写交给SpringIoC容器管理组件的信息,配置方式有三种。

    基于 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="..." [1] class="..." [2]>  
         <!-- 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>

    Spring IoC 容器管理一个或多个组件。这些 组件是使用你提供给容器的配置元数据(例如,以 XML <bean/> 定义的形式)创建的。

    <bean /> 标签 == 组件信息声明

    • id 属性是标识单个 Bean 定义的字符串。

    • class 属性定义 Bean 的类型并使用完全限定的类名。

  2. 实例化IoC容器

    提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。

    我们应该选择一个合适的实现类,进行IoC容器的实例化:

     ApplicationContext context = 
                new ClassPathXmlApplicationContext("services.xml", "daos.xml");

  3. 使用容器,获取Bean(组件)

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

    允许读取 Bean 定义并访问它们,如以下示例所示:

     //创建ioc容器对象,指定配置文件,ioc也开始实例组件对象
     ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
     //获取ioc容器的组件对象
     PetStoreService service = context.getBean("petStore", PetStoreService.class);
     //使用组件对象
     List<String> userList = service.getUsernameList();

2.基于XML方式管理Bean

声明配置文件和创建容器

创建项目,准备组件类,创建xml文件并进行配置,创建ioc容器,获取Bean

    public class HappyComponent {

        public void doWork() {
            System.out.println("HappyComponent.doWork");
        }

    }
    ```
<!-- 实验一 [重要]创建bean -->
<bean id="happyComponent" class="com.atguigu.ioc.HappyComponent"/>

public class IoCTest {
    
    @Test
    public void testExperiment01() {

        //方式1: 创建IoC容器,并读取配置文件 注意: 构造函数是可变参数,可以传入一个或者多个配置
        ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-01.xml");

        //方式2: 先创建容器,后配置文件!
        ClassPathXmlApplicationContext iocContainer1 = new ClassPathXmlApplicationContext();
        //设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
        iocContainer1.setConfigLocations("spring-bean-01.xml");
        //后配置的文件,需要调用refresh方法,触发刷新配置
        iocContainer1.refresh();
    }
}

@Test
public void testExperiment01() {

    //方式1: 创建IoC容器,并读取配置文件 注意: 构造函数是可变参数,可以传入一个或者多个配置
    ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-01.xml");

    //获取bean
    HappyComponent happyComponent = iocContainer.getBean("happyComponent", HappyComponent.class);
    happyComponent.doWork();

    //方式2: 先创建容器,后配置文件!
    ClassPathXmlApplicationContext iocContainer1 = new ClassPathXmlApplicationContext();
    //设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
    iocContainer1.setConfigLocations("spring-bean-01.xml");
    //后配置的文件,需要调用refresh方法,触发刷新配置
    iocContainer1.refresh();

    //获取bean
    HappyComponent happyComponent1 = iocContainer1.getBean("happyComponent", HappyComponent.class);
    happyComponent1.doWork();

}

注意事项

  1. bean的id值,必须唯一!

  2. bean需要包含无参数构造函数!

获取bean

  1. 根据id获取

     //方式1: 根据id获取
     //没有指定类型,返回为Object,需要类型转化!
     HappyComponent happyComponent = 
             (HappyComponent) iocContainer.getBean("happyComponent");
     happyComponent.doWork();

  2. 根据类型获取

     
    //方式2: 根据类型获取
     //根据类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理
     //配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题
     HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);
     happyComponent.doWork();

  3. 根据id和类型获取

     //方式3: 根据id和类型获取
     HappyComponent happyComponent = iocContainer.getBean("happyComponent", HappyComponent.class);
     happyComponent.doWork();

总结:

根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

bean属性赋值,setter注入

1.组件类添加属性

public class HappyComponent {
    
    //添加属性
    private String componentName;

    public String getComponentName() {
        return componentName;
    }

    //必须配置set方法,属性设置,ioc容器是通过set方法调用,进行属性赋值!!!
    public void setComponentName(String componentName) {
        this.componentName = componentName;
    }

    public void doWork() {
        System.out.println("HappyComponent.doWork");
    }

}

2.xml配置文件给属性指定值

<!-- 实验三 [重要]给bean的属性赋值:setter注入 -->
<bean id="happyComponent3" class="com.atguigu.ioc.HappyComponent">
    
    <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
    <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
    <!-- value属性:指定属性值 -->
    <property name="componentName" value="veryHappy"/>
</bean>

3.测试类输出

@Test
public void testExperiment03() {

    //创建IoC容器,并读取配置文件 注意: 构造函数是可变参数,可以传入一个或者多个配置
    ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-03.xml");

    HappyComponent happyComponent = iocContainer.getBean("happyComponent3", HappyComponent.class);
    System.out.println(happyComponent.getComponentName());
}

bean属性赋值,引入其他bean

1.声明新组件

public class HappyMachine {
    
    private String machineName;
    
    public String getMachineName() {
        return machineName;
    }
    
    public void setMachineName(String machineName) {
        this.machineName = machineName;
    }
}

2.原组件引入新组件

public class HappyComponent {

    //添加属性
    private String componentName;

    public String getComponentName() {
        return componentName;
    }

    //必须配置set方法,属性设置,ioc容器是通过set方法调用,进行属性赋值!!!
    public void setComponentName(String componentName) {
        this.componentName = componentName;
    }
    
    //引用新组件
    private HappyMachine happyMachine;
    
    public HappyMachine getHappyMachine() {
        return happyMachine;
    }

    public void setHappyMachine(HappyMachine happyMachine) {
        this.happyMachine = happyMachine;
    }

    public void doWork() {
        System.out.println("HappyComponent.doWork");
    }
}

3.配置新组件

 <bean id="happyMachine" class="com.atguigu.ioc.HappyMachine">
    <property name="machineName" value="makeHappy"/>
</bean>

4.组件之间引用配置

<bean id="happyComponent3" class="com.atguigu.ioc.HappyComponent">

    <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
    <!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
    <!-- value属性:指定属性值 -->
    <property name="componentName" value="veryHappy"/>

    <!-- ref 属性:通过 bean 的 id 引用另一个 bean -->
    <property name="happyMachine" ref="happyMachine"/>
</bean>

5.测试类进行测试输出,看看是否能得到类中的属性值

@Test
public void testExperiment04() {
    ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-04.xml");

    HappyComponent happyComponent = iocContainer.getBean("happyComponent4", HappyComponent.class);
    //获取另一个bean
    System.out.println(happyComponent.getHappyMachine().getMachineName());
}

6.注意事项

  1. 声明bean,不分先后顺序,spring容器内部有缓存机制,先实例化后属性赋值!

  2. ref 容易错写成value,会抛出Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 异常!

  3. 只有声明到ioc容器,方可被其他bean引用!

bean属性赋值:内部bean声明(了解)

  1. 声明内部bean配置

    在bean里面配置的bean就是内部bean,内部bean只能在当前bean内部使用,在其他地方不能使用。

    不会在ioc容器中,实例和存储内部bean,只会缓存类信息,每次获取的时候再实例化!!

     
    <!-- 实验五 [重要]给bean的属性赋值:内部bean -->
     <bean id="happyComponent5" class="com.atguigu.ioc.HappyComponent">
         <property name="happyMachine">
             <!-- 在一个 bean 中再声明一个 bean 就是内部 bean -->
             <!-- 内部 bean 可以直接用于给属性赋值,可以省略 id 属性 -->
             <bean class="com.atguigu.ioc.HappyMachine">
                 <property name="machineName" value="makeHappy"/>
             </bean>
         </property>
     </bean>

  2. 测试读取

     @Test
     public void testExperiment05() {
     ​
         ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-05.xml");
     ​
         HappyComponent happyComponent = iocContainer.getBean("happyComponent5", HappyComponent.class);
         //通过外部bean,可以获取专属内部bean
         System.out.println(happyComponent.getHappyMachine().getMachineName());
     ​
         //直接获取内部bean,输出! [[报错]]
         //NoSuchBeanDefinitionException: No qualifying bean of type 'com.atguigu.ioc.HappyMachine' available
         HappyMachine happyMachine = iocContainer.getBean(HappyMachine.class);
         System.out.println("happyMachine = " + happyMachine);
     }

  Bean 属性赋值:引入外部Properties配置参数

  1. 实现目标

    将Druid连接池对象交给SpringIoC容器管理!

  2. 加入数据库依赖

     <!-- 数据库驱动 和 连接池-->
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.25</version>
     </dependency>
     ​
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.2.8</version>
     </dependency>

  3. 创建外部属性文件

    文件位置:resources/jdbc.properties

     
    # 配置成你的数据信息
     jdbc.user=root
     jdbc.password=root
     jdbc.url=jdbc:mysql:///数据库名
     jdbc.driver=com.mysql.cj.jdbc.Driver

  4. 引入属性文件

     
    <!-- 引入外部属性文件 -->
     <context:property-placeholder location="classpath:jdbc.properties"/>

    在 IDEA 中引入 Spring 配置文件中名称空间的两种操作方式:

    在打字标签名的过程中根据提示选择一个正确的名称空间 对于直接复制过来的完整标签,可以在名称空间上点击,然后根据提示引入

        5.配置连接池信息

 <!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 -->
 <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
     <property name="url" value="${jdbc.url}"/>
     <property name="driverClassName" value="${jdbc.driver}"/>
     <property name="username" value="${jdbc.user}"/>
     <property name="password" value="${jdbc.password}"/>
 </bean>

        6.读取测试

 @Test
 public void testExperiment06() throws SQLException {
     ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-06.xml");
     DataSource dataSource = iocContainer.getBean(DataSource.class);
     Connection connection = dataSource.getConnection();
     System.out.println("connection = " + connection);
 }

 

 高级特性:FactoryBean特性

  1. FactoryBean简介

    FactoryBean 接口是Spring IoC容器实例化逻辑的可插拔性点。

    用于配置复杂的Bean对象,可以将创建过程存储在FactoryBean 的getObject方法!

    FactoryBean<T> 接口提供三种方法:

    • T getObject():

      返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!

    • boolean isSingleton():

      如果此 FactoryBean 返回单例,则返回 true ,否则返回 false 。此方法的默认实现返回 true (注意,lombok插件使用,可能影响效果)。

    • Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null

  2. FactoryBean使用场景

    1. 代理类的创建

    2. 第三方框架整合

    3. 复杂对象实例化等

  3. Factorybean应用

    1. 准备FactoryBean实现类

       // 实现FactoryBean接口时需要指定泛型
       // 泛型类型就是当前工厂要生产的对象的类型
       public class HappyFactoryBean implements FactoryBean<HappyMachine> {
           
           private String machineName;
           
           public String getMachineName() {
               return machineName;
           }
           
           public void setMachineName(String machineName) {
               this.machineName = machineName;
           }
           
           @Override
           public HappyMachine getObject() throws Exception {
           
               // 方法内部模拟创建、设置一个对象的复杂过程
               HappyMachine happyMachine = new HappyMachine();
           
               happyMachine.setMachineName(this.machineName);
           
               return happyMachine;
           }
           
           @Override
           public Class<?> getObjectType() {
           
               // 返回要生产的对象的类型
               return HappyMachine.class;
           }
       }

    2. 配置FactoryBean实现类

       <!-- FactoryBean机制 -->
       <!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
       <bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean">
           <!-- property标签仍然可以用来通过setXxx()方法给属性赋值 -->
           <property name="machineName" value="iceCreamMachine"/>
       </bean>

    3. 测试读取FactoryBean和FactoryBean.getObject对象

       @Test
       public void testExperiment07()  {
       ​
           ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-07.xml");
       ​
           //注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象
           HappyMachine happyMachine = iocContainer.getBean("happyMachine7",HappyMachine.class);
           System.out.println("happyMachine = " + happyMachine);
       ​
           //如果想要获取FactoryBean对象, 直接在id前添加&符号即可!  &happyMachine7 这是一种固定的约束
           Object bean = iocContainer.getBean("&happyMachine7");
           System.out.println("bean = " + bean);
       }

  4. FactoryBean和BeanFactory区别

    **FactoryBean **是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。

    一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!

    BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。

    总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

 高级特性:Bean的作用域

  1. Bean作用域概念

    <bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器!

    在IoC容器中,这些<bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)!

    这意味着,BeanDefinition概念一样,SpringIoC容器可以可以根据BeanDefinition对象反射创建多个Bean对象实例。

    具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!

    默认情况:我们全局只需要实例化一个Bean对象,绝大情况我们也仅需创建一个对象!

  2. 常用作用域

    取值含义创建对象的时机默认值
    singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时
    prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时
    如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):
    取值含义创建对象的时机默认值
    ---------------------------
    request请求范围内有效的实例每次请求
    session会话范围内有效的实例每次会话
  3. 作用域配置和测试

    配置scope范围

     <!--bean的作用域 -->
     <!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
     <!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
     <bean id="happyMachine8" scope="prototype" class="com.atguigu.ioc.HappyMachine">
         <property name="machineName" value="happyMachine"/>
     </bean>
     ​
     <bean id="happyComponent8" scope="singleton" class="com.atguigu.ioc.HappyComponent">
         <property name="componentName" value="happyComponent"/>
     </bean>

    测试读取

     @Test
     public void testExperiment08()  {
         ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-08.xml");
     ​
         HappyMachine bean = iocContainer.getBean(HappyMachine.class);
         HappyMachine bean1 = iocContainer.getBean(HappyMachine.class);
         //多例对比 false
         System.out.println(bean == bean1);
     ​
         HappyComponent bean2 = iocContainer.getBean(HappyComponent.class);
         HappyComponent bean3 = iocContainer.getBean(HappyComponent.class);
         //单例对比 true
         System.out.println(bean2 == bean3);
     }

 高级特性:Bean的生命周期

  1. 理解Bean的生命周期作用

    Spring Framework的Bean生命周期是指一个Bean对象从它的创建、初始化到销毁的整个过程。

    理解Spring Bean的生命周期可以帮助开发者更好地管理Bean,可以实现以下目的:

    1. 避免重复初始化Bean,提高Bean实例化的效率;

    2. 在Bean初始化前后做些额外的处理,如日志记录、权限检查等;

    3. 实现自定义的操作,如在Bean销毁时释放资源、设置缓存等;

    4. 理解Bean的整个生命周期有助于排查问题,提高应用程序的可维护性;

    5. 理解Spring Aop 等功能的实现原理,并参与定制过程;

  2. Bean生命周期清单和步骤内容

    Spring Bean 的生命周期指从 Spring 容器创建 Bean 实例开始,到 Bean 销毁的整个过程,可以按照以下流程分为以下几个阶段:

    1. 实例化Bean实例:Spring 容器使用指定的实例化策略创建 bean,该策略可以是无参构造、工厂方法等。当 Spring 加载 Bean 的配置文件并且 Bean 标签被解析后,这个类(Bean)就会被实例化。

    2. Bean实例属性设置:Spring 通过调用 setter 方法或直接设置字段的方式来注入 Bean 的属性。

    3. Aware 相关接口的回调:Spring 通过 Aware 接口来把一些 Bean 相关的资源注入到 Bean 中。例如,BeanNameAware 接口可获取到 Bean 的名称;ApplicationContextAware 接口可获取到 ApplicationContext 对象实例;BeanFactoryAware 接口可获取到 BeanFactory 对象实例等。

    4. Bean初始化前的操作:在 Bean 的初始化之前,Spring 允许用户自定义 Bean 实例化后的一些操作。

      • 如果有BeanPostProcessor注册,先执行beforeInitialization()方法;

      • 如果Bean实现了InitializingBean接口,则执行afterPropertiesSet()方法

    5. Bean 的初始化方法调用:如果在配置文件中使用init-method属性声明了初始化方法,则执行该方法;

    6. Bean初始化后的操作:在 Bean 的初始化之后,如果有BeanPostProcessors注册,执行afterInitialization()方法;

      此方法中,Bean实例已经完成了实例化和初始化工作,最终会将afterInitialization()方法修改后返回的对象存储到IoC容器中!

      Spring Aop的实现,通过定义BeanPostProcessor(AbstractAutoProxyCreator),在后置方法中添加动态代理技术,进行Bean的动态代理对象生成!

    7. 使用 Bean:即在 IoC 容器中调用 getBean() 方法获取 Bean 实例,使用 Bean 的过程。

    8. 销毁 Bean:当 Bean 不再被使用时,Spring 容器会自动释放 Bean 占有的资源,关闭 IoC 容器。 开发人员可以自己实现 DisposableBean 接口或者为 Bean 配置一个指定的 destroy-method 方法来实现自定义销毁的逻辑。

    9. 关闭IoC容器 在整个生命周期过程中,Spring 提供了各种监听器和钩子函数,允许开发人员在不同的 Bean 生命周期阶段添加自己的处理逻辑以实现更加灵活和智能的控制。

  3. 参与Bean生命周期定义

    1. 测试 BeanPostProcessor 接口

       public class MyBeanPostProcessor implements BeanPostProcessor {
       ​
           @Override
           public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
               System.out.println("MyBeanPostProcessor.postProcessBeforeInitialization");
               System.out.println("bean = " + bean + ", beanName = " + beanName);
               return bean;
           }
       ​
           @Override
           public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
               System.out.println("MyBeanPostProcessor.postProcessAfterInitialization");
               System.out.println("bean = " + bean + ", beanName = " + beanName);
               return bean;
           }
       }
       ​

    2. 测试 init-method / destroy-method 配置

       <?xml version="1.0" encoding="UTF-8"?>
       <beans xmlns="http://www.springframework.org/schema/beans"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns:context="http://www.springframework.org/schema/context"
              xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
               <!-- 声明BeanPostProcessor处理器 -->
               <bean class="com.atguigu.lifecycle.MyBeanPostProcessor" />
       ​
               <!-- 声明bean,并且指定初始化和销毁方法
                     init-method 指定初始化方法
                     destroy-method 指定销毁方法
               -->
               <bean id="myBean"  class="com.atguigu.lifecycle.MyBean" init-method="init" destroy-method="destroy"/>
       ​
           </beans>
           ```
       3.  测试读取配置即可
         
        ```java
           @Test
           public void testExperiment09()  {
               ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-09.xml");
           }
           ```

基于注解方式管理bean

bean注解的标记和扫描

  1. 注解理解

    和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

    本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。

  2. 扫描理解

    Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

  3. 准备Spring项目和组件

    1. 准备项目pom.xml

       <dependencies>
           <!--spring context依赖-->
           <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
           <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-context</artifactId>
               <version>6.0.6</version>
           </dependency>
       ​
           <!--junit5测试-->
           <dependency>
               <groupId>org.junit.jupiter</groupId>
               <artifactId>junit-jupiter-api</artifactId>
               <version>5.3.1</version>
           </dependency>
       </dependencies>

    2. 准备组件类

      普通组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: 普通的组件
        */
       public class CommonComponent {
       }
       ​
      Controller组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: controller类型组件
        */
       public class XxxController {
       }
       ​
      Service组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: service类型组件
        */
       public class XxxService {
       }
       ​
      Dao组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: dao类型组件
        */
       public class XxxDao {
       }
       ​

  4. 组件添加标记注解

    1. 组件标记注解和区别

      Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

      注解说明
      @Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
      @Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
      @Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
      @Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
      通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

      对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

      注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记。

    2. 使用注解标记

      普通组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: 普通的组件
        */
       @Component
       public class CommonComponent {
       }
       ​
      Controller组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: controller类型组件
        */
       @Controller
       public class XxxController {
       }
       ​
      Service组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: service类型组件
        */
       @Service
       public class XxxService {
       }
       ​
      Dao组件
      
       /**
        * projectName: com.atguigu.components
        *
        * description: dao类型组件
        */
       @Repository
       public class XxxDao {
       }
       ​

  5. 配置文件确定扫描范围

    情况1:基本扫描配置

     <?xml version="1.0" encoding="UTF-8"?>
     <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context"
            xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
         <!-- 配置自动扫描的包 -->
         <!-- 1.包要精准,提高性能!
              2.会扫描指定的包和子包内容
              3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service等
         -->
         <context:component-scan base-package="com.atguigu.components"/>
       
     </beans>

    情况2:指定排除组件

     <!-- 情况三:指定不扫描的组件 -->
     <context:component-scan base-package="com.atguigu.components">
         
         <!-- context:exclude-filter标签:指定排除规则 -->
         <!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
         <!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
     </context:component-scan>

    情况3:指定扫描组件

     <!-- 情况四:仅扫描指定的组件 -->
     <!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
     <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
     <context:component-scan base-package="com.atguigu.ioc.components" use-default-filters="false">
         
         <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
         <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
     </context:component-scan>

  6. 组件BeanName问题

    在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。

    默认情况:

    类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。

    使用value属性指定:

     @Controller(value = "tianDog")
     public class SoldierController {
     }

    当注解中只设置一个属性时,value属性的属性名可以省略:

     @Service("smallDog")
     public class SoldierService {
     }
  7. 总结

    1. 注解方式IoC只是标记哪些类要被Spring管理

    2. 最终,我们还需要XML方式或者后面讲解Java配置类方式指定注解生效的包

    3. 现阶段配置方式为 注解 (标记)+ XML(扫描)

bean属性赋值:引用类型自动装配(DI)

  1. 设定场景

    • SoldierController 需要 SoldierService

    • SoldierService 需要 SoldierDao 同时在各个组件中声明要调用的方法。

    • SoldierController中声明方法

       @Controller(value = "tianDog")
       public class SoldierController {
       ​
           private SoldierService soldierService;
       ​
           public void getMessage() {
               soldierService.getMessage();
           }
       ​
       }
      
      

    • SoldierService中声明方法

       @Service("smallDog")
       public class SoldierService {
       ​
           private SoldierDao soldierDao;
       ​
           public void getMessage() {
               soldierDao.getMessage();
           }
       }

    • SoldierDao中声明方法

       @Repository
       public class SoldierDao {
       ​
           public void getMessage() {
               System.out.print("I am a soldier");
           }
       ​
       }

  2. 自动装配实现

    1. 前提

      参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。

      注意:不区分IoC的方式!XML和注解都可以!

    2. @Autowired注解

      在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。

    3. 给Controller装配Service

       
      @Controller(value = "tianDog")
       public class SoldierController {
           
           @Autowired
           private SoldierService soldierService;
           
           public void getMessage() {
               soldierService.getMessage();
           }
           
       }

    4. 给Service装配Dao

       @Service("smallDog")
       public class SoldierService {
           
           @Autowired
           private SoldierDao soldierDao;
           
           public void getMessage() {
               soldierDao.getMessage();
           }
       }

  3. @Autowired注解细节

    1. 标记位置

      1. 成员变量

        这是最主要的使用方式!

        与xml进行bean ref引用不同,他不需要有set方法!

         
        @Service("smallDog")
         public class SoldierService {
             
             @Autowired
             private SoldierDao soldierDao;
             
             public void getMessage() {
                 soldierDao.getMessage();
             }
         }

      2. 构造器

         @Controller(value = "tianDog")
         public class SoldierController {
             
             private SoldierService soldierService;
             
             @Autowired
             public SoldierController(SoldierService soldierService) {
                 this.soldierService = soldierService;
             }
             ……

      3. setXxx()方法

         
        @Controller(value = "tianDog")
         public class SoldierController {
         ​
             private SoldierService soldierService;
         ​
             @Autowired
             public void setSoldierService(SoldierService soldierService) {
                 this.soldierService = soldierService;
             }
             ……

    2. 工作流程

      • 首先根据所需要的组件类型到 IOC 容器中查找

        • 能够找到唯一的 bean:直接执行装配

        • 如果完全找不到匹配这个类型的 bean:装配失败

        • 和所需类型匹配的 bean 不止一个

          • 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配

            • 能够找到:执行装配

            • 找不到:装配失败

          • 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配

            • 能够找到:执行装配

            • 找不到:装配失败

       @Controller(value = "tianDog")
       public class SoldierController {
           
           @Autowired
           @Qualifier(value = "maomiService222")
           // 根据面向接口编程思想,使用接口类型引入Service组件
           private ISoldierService soldierService;
  4. 佛系装配

    给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性

     @Controller(value = "tianDog")
     public class SoldierController {
     ​
         // 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装
         @Autowired(required = false)
         private ISoldierService soldierService;
  5. 扩展JSR-250注解@Resource

    • 理解JSR系列注解

      JSR(Java Specification Requests)是Java平台标准化进程中的一种技术规范,而JSR注解是其中一部分重要的内容。按照JSR的分类以及注解语义的不同,可以将JSR注解分为不同的系列,主要有以下几个系列:

      1. JSR-175: 这个JSR是Java SE 5引入的,是Java注解最早的规范化版本,Java SE 5后的版本中都包含该JSR中定义的注解。主要包括以下几种标准注解:

      • @Deprecated: 标识一个程序元素(如类、方法或字段)已过时,并且在将来的版本中可能会被删除。

      • @Override: 标识一个方法重写了父类中的方法。

      • @SuppressWarnings: 抑制编译时产生的警告消息。

      • @SafeVarargs: 标识一个有安全性警告的可变参数方法。

      • @FunctionalInterface: 标识一个接口只有一个抽象方法,可以作为lambda表达式的目标。

      1. JSR-250: 这个JSR主要用于在Java EE 5中定义一些支持注解。该JSR主要定义了一些用于进行对象管理的注解,包括:

      • @Resource: 标识一个需要注入的资源,是实现Java EE组件之间依赖关系的一种方式。

      • @PostConstruct: 标识一个方法作为初始化方法。

      • @PreDestroy: 标识一个方法作为销毁方法。

      • @Resource.AuthenticationType: 标识注入的资源的身份验证类型。

      • @Resource.AuthenticationType: 标识注入的资源的默认名称。

      1. JSR-269: 这个JSR主要是Java SE 6中引入的一种支持编译时元数据处理的框架,即使用注解来处理Java源文件。该JSR定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有:

      • @SupportedAnnotationTypes: 标识注解处理器所处理的注解类型。

      • @SupportedSourceVersion: 标识注解处理器支持的Java源码版本。

      1. JSR-330: 该JSR主要为Java应用程序定义了一个依赖注入的标准,即Java依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括:

      • @Named: 标识一个被依赖注入的组件的名称。

      • @Inject: 标识一个需要被注入的依赖组件。

      • @Singleton: 标识一个组件的生命周期只有一个唯一的实例。

      1. JSR-250: 这个JSR主要是Java EE 5中定义一些支持注解。该JSR包含了一些支持注解,可以用于对Java EE组件进行管理,包括:

      • @RolesAllowed: 标识授权角色

      • @PermitAll: 标识一个活动无需进行身份验证。

      • @DenyAll: 标识不提供针对该方法的访问控制。

      • @DeclareRoles: 声明安全角色。 但是你要理解JSR是Java提供的技术规范,也就是说,他只是规定了注解和注解的含义,JSR并不是直接提供特定的实现,而是提供标准和指导方针,由第三方框架(Spring)和库来实现和提供对应的功能。

    • JSR-250 @Resource注解

      @Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?

      • @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)

      • @Autowired注解是Spring框架自己的。

      • @Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配。

      • @Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。

      • @Resource注解用在属性上、setter方法上。

      • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。 @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。

       <dependency>
           <groupId>jakarta.annotation</groupId>
           <artifactId>jakarta.annotation-api</artifactId>
           <version>2.1.1</version>
       </dependency>
    • @Resource使用

       @Controller
       public class XxxController {
           /**
            * 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService
            * 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找
            * 3. 可以指定name名称查找!  @Resource(name='test') == @Autowired + @Qualifier(value='test')
            */
           @Resource
           private XxxService xxxService;
       ​
           //@Resource(name = "指定beanName")
           //private XxxService xxxService;
       ​
           public void show(){
               System.out.println("XxxController.show");
               xxxService.show();
           }
       }

bean属性赋值:基本类型属性赋值(DI)

@Value 通常用于注入外部化属性

声明外部配置

application.properties

 catalog.name=MovieCatalog

xml引入外部配置

 <!-- 引入外部配置文件-->
 <context:property-placeholder location="application.properties" />

@Value注解读取配置

 package com.atguigu.components;
 ​
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 ​
 /**
  * projectName: com.atguigu.components
  *
  * description: 普通的组件
  */
 @Component
 public class CommonComponent {
 ​
     /**
      * 情况1: ${key} 取外部配置key对应的值!
      * 情况2: ${key:defaultValue} 没有key,可以给与默认值
      */
     @Value("${catalog:默认值}")
     private String name;
 ​
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 }
 ​

基于配置类方式管理bean

 完全注解开发理解

Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

两种方式思维转化

配置类和扫描注解

xml+注解方式

配置文件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:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
 ​
 ​
     <!-- 配置自动扫描的包 -->
     <!-- 1.包要精准,提高性能!
          2.会扫描指定的包和子包内容
          3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service等
     -->
     <context:component-scan base-package="com.atguigu.components"/>
 ​
     <!-- 引入外部配置文件-->
     <context:property-placeholder location="application.properties" />
 </beans>

测试创建IoC容器

  // xml方式配置文件使用ClassPathXmlApplicationContext容器读取
  ApplicationContext applicationContext =
                 new ClassPathXmlApplicationContext("application.xml");

配置类+注解方式(完全注解方式)

配置类

使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类。

 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.PropertySource;
 ​
 //标注当前类是配置类,替代application.xml    
 @Configuration
 //使用注解读取外部配置,替代 <context:property-placeholder标签
 @PropertySource("classpath:application.properties")
 //使用@ComponentScan注解,可以配置扫描包,替代<context:component-scan标签
 @ComponentScan(basePackages = {"com.atguigu.components"})
 public class MyConfiguration {
     
 }

测试创建IoC容器

 // AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
 ApplicationContext iocContainerAnnotation = 
 new AnnotationConfigApplicationContext(MyConfiguration.class);

总结:

@Configuration指定一个类为配置类,可以添加配置注解,替代配置xml文件

@ComponentScan(basePackages = {"包","包"}) 替代<context:component-scan标签实现注解扫描

@PropertySource("classpath:配置文件地址") 替代 <context:property-placeholder标签

配合IoC/DI注解,可以进行完整注解开发!

@Bean定义组件

场景需求:将Druid连接池对象存储到IoC容器

需求分析:第三方jar包的类,添加到ioc容器,无法使用@Component等相关注解!因为源码jar包内容为只读模式!

xml方式实现

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
 ​
 ​
     <!-- 引入外部属性文件 -->
     <context:property-placeholder location="classpath:jdbc.properties"/>
 ​
     <!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 -->
     <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
         <property name="url" value="${jdbc.url}"/>
         <property name="driverClassName" value="${jdbc.driver}"/>
         <property name="username" value="${jdbc.user}"/>
         <property name="password" value="${jdbc.password}"/>
     </bean>
 ​
 </beans>

配置类方式实现

@Bean 注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉 Spring 的 <beans/> XML 配置的人来说, @Bean 注释与 <bean/> 元素起着相同的作用。

 //标注当前类是配置类,替代application.xml    
 @Configuration
 //引入jdbc.properties文件
 @PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
 @ComponentScan(basePackages = {"com.atguigu.components"})
 public class MyConfiguration {
 ​
     //如果第三方类进行IoC管理,无法直接使用@Component相关注解
     //解决方案: xml方式可以使用<bean标签
     //解决方案: 配置类方式,可以使用方法返回值+@Bean注解
     @Bean
     public DataSource createDataSource(@Value("${jdbc.user}") String username,
                                        @Value("${jdbc.password}")String password,
                                        @Value("${jdbc.url}")String url,
                                        @Value("${jdbc.driver}")String driverClassName){
         //使用Java代码实例化
         DruidDataSource dataSource = new DruidDataSource();
         dataSource.setUsername(username);
         dataSource.setPassword(password);
         dataSource.setUrl(url);
         dataSource.setDriverClassName(driverClassName);
         //返回结果即可
         return dataSource;
     }
 }

高级特性:@Bean注解细节

  1. @Bean生成BeanName问题

     public @interface Bean {
         //前两个注解可以指定Bean的标识
         @AliasFor("name")
         String[] value() default {};
         @AliasFor("value")
         String[] name() default {};
       
         //autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。
         //autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标,
         //可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。
         boolean autowireCandidate() default true;
     ​
         //指定初始化方法
         String initMethod() default "";
         //指定销毁方法
         String destroyMethod() default "(inferred)";
     }
     ​

    指定@Bean的名称:

     @Configuration
     public class AppConfig {
     ​
       @Bean("myThing") //指定名称
       public Thing thing() {
         return new Thing();
       }
     }

    @Bean 注释注释方法。使用此方法在指定为方法返回值的类型的 ApplicationContext 中注册 Bean 定义。缺省情况下,Bean 名称与方法名称相同。下面的示例演示 @Bean 方法声明:

     @Configuration
     public class AppConfig {
     ​
       @Bean
       public TransferServiceImpl transferService() {
         return new TransferServiceImpl();
       }
     }

    前面的配置完全等同于下面的Spring XML:

     <beans>
       <bean id="transferService" class="com.acme.TransferServiceImpl"/>
     </beans>
  2. @Bean 初始化和销毁方法指定

    @Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-methoddestroy-method 属性,如以下示例所示:

     public class BeanOne {
     ​
       public void init() {
         // initialization logic
       }
     }
     ​
     public class BeanTwo {
     ​
       public void cleanup() {
         // destruction logic
       }
     }
     ​
     @Configuration
     public class AppConfig {
     ​
       @Bean(initMethod = "init")
       public BeanOne beanOne() {
         return new BeanOne();
       }
     ​
       @Bean(destroyMethod = "cleanup")
       public BeanTwo beanTwo() {
         return new BeanTwo();
       }
     }
  3. @Bean Scope作用域

    可以指定使用 @Bean 注释定义的 bean 应具有特定范围。您可以使用在 Bean 作用域部分中指定的任何标准作用域。

    默认作用域为 singleton ,但您可以使用 @Scope 注释覆盖此范围,如以下示例所示:

     @Configuration
     public class MyConfiguration {
     ​
       @Bean
       @Scope("prototype")
       public Encryptor encryptor() {
         // ...
       }
     }
  4. @Bean方法之间依赖

    准备组件

     public class HappyMachine {
         
         private String machineName;
         
         public String getMachineName() {
             return machineName;
         }
         
         public void setMachineName(String machineName) {
             this.machineName = machineName;
         }
     }
     public class HappyComponent {
         //引用新组件
         private HappyMachine happyMachine;
     ​
         public HappyMachine getHappyMachine() {
             return happyMachine;
         }
     ​
         public void setHappyMachine(HappyMachine happyMachine) {
             this.happyMachine = happyMachine;
         }
     ​
         public void doWork() {
             System.out.println("HappyComponent.doWork");
         }
     ​
     }

    Java配置类实现:

    方案1:

    直接调用方法返回 Bean 实例:在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean,例如:

     @Configuration
     public class JavaConfig {
     ​
         @Bean
         public HappyMachine happyMachine(){
             return new HappyMachine();
         }
     ​
         @Bean
         public HappyComponent happyComponent(){
             HappyComponent happyComponent = new HappyComponent();
             //直接调用方法即可! 
             happyComponent.setHappyMachine(happyMachine());
             return happyComponent;
         }
     ​
     }

    方案2:

    参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系,例如:

     package com.atguigu.config;
     ​
     import com.atguigu.ioc.HappyComponent;
     import com.atguigu.ioc.HappyMachine;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
     ​
     /**
      * projectName: com.atguigu.config
      * description: 配置HappyComponent和HappyMachine关系
      */
     ​
     @Configuration
     public class JavaConfig {
     ​
         @Bean
         public HappyMachine happyMachine(){
             return new HappyMachine();
         }
     ​
         /**
          * 可以直接在形参列表接收IoC容器中的Bean!
          *    情况1: 直接指定类型即可
          *    情况2: 如果有多个bean,(HappyMachine 名称 ) 形参名称等于要指定的bean名称!
          *           例如:
          *               @Bean
          *               public Foo foo1(){
          *                   return new Foo();
          *               }
          *               @Bean
          *               public Foo foo2(){
          *                   return new Foo()
          *               }
          *               @Bean
          *               public Component component(Foo foo1 , Foo foo2 通过此处指定引入的bean)
          */
         @Bean
         public HappyComponent happyComponent(HappyMachine happyMachine){
             HappyComponent happyComponent = new HappyComponent();
             //赋值
             happyComponent.setHappyMachine(happyMachine);
             return happyComponent;
         }
     ​
     }

高级特性:@Import扩展

@Import 注释允许从另一个配置类加载 @Bean 定义,如以下示例所示:

 @Configuration
 public class ConfigA {
 ​
   @Bean
   public A a() {
     return new A();
   }
 }
 ​
 @Configuration
 @Import(ConfigA.class)
 public class ConfigB {
 ​
   @Bean
   public B b() {
     return new B();
   }
 }

现在,在实例化上下文时不需要同时指定 ConfigA.classConfigB.class ,只需显式提供 ConfigB ,如以下示例所示:

 public static void main(String[] args) {
   ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
 ​
   // now both beans A and B will be available...
   A a = ctx.getBean(A.class);
   B b = ctx.getBean(B.class);
 }

此方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造期间记住可能大量的 @Configuration 类。

高级特性:@Conditional扩展(了解)

  1. @Conditional介绍

    @Conditional是Spring4新提供的注解,能够根据一定的条件进行判断,满足条件就给容器注入bean。

    了解@Conditional辅助我们更灵活的进行Bean注入和更好的解读SpringBoot原理代码!

     @Target({ElementType.TYPE, ElementType.METHOD})
     @Retention(RetentionPolicy.RUNTIME)
     @Documented
     public @interface Conditional {
       Class<? extends Condition>[] value();
     }

    从代码中可以看到,需要传入一个Class数组,并且需要继承Condition接口:

     public interface Condition {
       boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2)
     }

    Condition是一个接口,返回true就注入bean,false则不注入。如果有多个实现类,必须全部满足方可注入!

  2. @Conditional 使用示例

     package com.atguigu.bean;
     ​
     public class User {
       
         private String name;
         private int age;
         
         public User(String name, int age) {
           this.name = name;
             this.age = age;
         }
         
         @Override
         public String toString() {
           return "User{" + "name=" + name + ", age=" + age + "}";
         }
         
         // getter/setter方法省略
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public int getAge() {
         return age;
     }
 ​
     public void setAge(int age) {
         this.age = age;
     }
 }
 ​
 ```
 编写配置类BeanConfig,注入两个User实例:
 ```java
 @Configuration
 public class BeanConfig {
   
     @Bean(name = "bill")
     public User user1() {
       return new User("Bill Gates", 62);
     }
     
     @Bean(name = "linus")
     public User user2() {
       return new User("Linus", 48);
     }
 }
 ​
 ```
 编写测试类,查看User实例是否注入:
 ```java
 public class ConditionalTest {
   
   @Test
   public void test1() {
       AnnotationConfigApplicationContext applicationContext =
               new AnnotationConfigApplicationContext(BeanConfig.class);
       //根据类型,获取全部bean,返回 id - bean实例 组成的map!
       Map<String, User> map = applicationContext.getBeansOfType(User.class);
       System.out.println(map);
   }
    
 }
 ​
 ```
 输出结果如下:
 ```java
 {bill=User{name=Bill Gates, age=62}, linus=User{name=Linus, age=48}}
 ```
 这时候问题来了,如果我们想根据当前操作系统来注入User实例(windows系统下注入bill, linux系统下注入linus),那么该怎么做呢?
 ​
 这就需要我们用到@Conditional注解了。
 ​
 首先我们先继承Condition接口,自定义判断条件
 ```java
 public class WindowsCondition implements Condition {
   
     /**
     * @param conditionContext:判断条件能使用的上下文环境
     * @param annotatedTypeMetadata:注解所在位置的注解信息
     */
     @Override
     public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
       //获取ioc使用的beanFactory
         ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
         //获取类加载器
         ClassLoader classLoader = conditionContext.getClassLoader();
         //获取当前环境信息
         Environment environment = conditionContext.getEnvironment();
         //获取bean定义的注册类
         BeanDefinitionRegistry registry = conditionContext.getRegistry();
         
         //获取当前系统名
          String property = environment.getProperty("os.name");
         //如果包含 Windows则说明是windows系统,返回true,否则返回false
         if(property.contains("Windows")) {
           return true;
         }
         return false;
     }
 }
 ​
 ```
 ```java
 public class LinuxCondition implements Condition {
   
     @Override
     public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
     Environment environment = conditionContext.getEnvironment();
     
     String property = environment.getProperty("os.name");
     if(property.contains("Linux")) {
       return true;
     }
     return false;
     
     }
 }
 ```
 将上面配置的Condition子类传递给@Conditonal注解
 ```java
 @Configuration
 public class BeanConfig {
   
     //只有一个类时,大括号可以省略
     @Conditional({WindowsCondition.class})
     @Bean(name = "bill")
     public User user1() {
       return new User("Bill Gates", 62);
     }
     
     @Conditional({LinuxCondition.class})
     @Bean(name = "linus")
     public User user2() {
       return new User("Linus", 48);
     }
 }
 ```
 测试:
 ```java
 public class ConditionalTest {

@Test public void test1() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class); String osname = applicationContext.getEnvironment().getProperty("os.name"); System.out.println("当前系统为" + osname); Map<String, User> map = applicationContext.getBeansOfType(User.class); System.out.println(map); }

 }
 ```
 运行结果:
 ```java
 当前系统为Windows 10
 {bill=User{name='Bill Gates', age=62}}
 

三种配置方式总结

XML方式配置总结

  1. 所有内容写到xml格式配置文件中

  2. 声明bean通过<bean标签

  3. <bean标签包含基本信息(id,class)和属性信息 <property name value / ref

  4. 引入外部的properties文件可以通过<context:property-placeholder

  5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

 XML+注解方式配置总结

  1. 注解负责标记IoC的类和进行属性装配

  2. xml文件依然需要,需要通过<context:component-scan标签指定注解范围

  3. 标记IoC注解:@Component,@Service,@Controller,@Repository

  4. 标记DI注解:@Autowired @Qualifier @Resource @Value

  5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

 完全注解方式配置总结

  1. 完全注解方式指的是去掉xml文件,使用配置类 + 注解实现

  2. xml文件替换成使用@Configuration注解标记的类

  3. 标记IoC注解:@Component,@Service,@Controller,@Repository

  4. 标记DI注解:@Autowired @Qualifier @Resource @Value

  5. <context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {"com.atguigu.components"})替代

  6. <context:property-placeholder引入外部配置文件使用@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})替代

  7. <bean 标签使用@Bean注解和方法实现

  8. IoC具体容器实现选择AnnotationConfigApplicationContext对象

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值