目录
目录
框架前言
总体技术体系
单一架构
一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one
单一架构,项目主要应用技术框架为:Spring , SpringMVC , Mybatis等
分布式架构
一个项目(对应 IDEA 中的一个 project),拆分成很多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。
分布式架构,项目主要应用技术框架:SpringBoot , SpringCloud , 中间件等
框架概念和理解
框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。
框架的优缺点:
框架的优点包括以下几点:
提高开发效率:框架提供了许多预先设计好了的组件和工具,能够帮助开发人员快速进行开发。相较于传统手写代码,在框架提供的规范化环境中,开发者可以更快地实现项目的各种要求。
降低开发成本:框架的提供标准化的编程语言、数据操作等代码片段,避免了重复开发的问题,降低了开发成本,提供深度优化的系统,降低了维护成本,增强了系统的可靠性。
提高应用程序的稳定性:框架通常经过了很长时间的开发和测试,其中的许多组件、代码片段和设计模式都得到了验证。重复利用这些组件有助于减少bug的出现,从而提高了应用程序的稳定性。
提供标准化的解决方案:框架通常是针对某个特定领域的,通过提供标准化的解决方案,可以为开发人员提供一种共同的语言和思想基础,有助于更好地沟通和协作。
框架的缺点包括以下几个方面:
学习成本高:框架通常具有特定的语言和编程范式。对于开发人员而言,需要花费时间学习其背后的架构、模式和逻辑,这对于新手而言可能会耗费较长时间。
可能存在局限性:虽然框架提高了开发效率并可以帮助开发人员解决常见问题,但是在某些情况下,特定的应用需求可能超出框架的范围,从而导致应用程序无法满足要求。开发人员可能需要更多的控制权和自由度,同时需要在框架和应用程序之间进行权衡取舍。
版本变更和兼容性问题:框架的版本发布和迭代通常会导致代码库的大规模变更,进而导致应用程序出现兼容性问题和漏洞。当框架变更时,需要考虑框架是否向下兼容,以及如何进行适当的测试、迁移和升级。
架构风险:框架涉及到很多抽象和概念,如果开发者没有足够的理解和掌握其架构,可能会导致系统出现设计和架构缺陷,从而影响系统的健康性和安全性。
站在文件结构的角度理解框架,可以将框架总结:框架 = 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的主要优势
丰富的生态系统:Spring 生态系统非常丰富,支持许多模块和库,如 Spring Boot、Spring Security、Spring Cloud 等等,可以帮助开发人员快速构建高可靠性的企业应用程序。
模块化的设计:框架组件之间的松散耦合和模块化设计使得 Spring Framework 具有良好的可重用性、可扩展性和可维护性。开发人员可以轻松地选择自己需要的模块,根据自己的需求进行开发。
简化 Java 开发:Spring Framework 简化了 Java 开发,提供了各种工具和 API,可以降低开发复杂度和学习成本。同时,Spring Framework 支持各种应用场景,包括 Web 应用程序、RESTful API、消息传递、批处理等等。
不断创新和发展: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.实现步骤
-
配置元数据(配置)
配置元数据,既是编写交给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 的类型并使用完全限定的类名。
-
-
实例化IoC容器
提供给
ApplicationContext
构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、JavaCLASSPATH
等)加载配置元数据。我们应该选择一个合适的实现类,进行IoC容器的实例化:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
-
使用容器,获取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();
}
注意事项
-
bean的id值,必须唯一!
-
bean需要包含无参数构造函数!
获取bean
根据id获取
//方式1: 根据id获取 //没有指定类型,返回为Object,需要类型转化! HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("happyComponent"); happyComponent.doWork();
根据类型获取
//方式2: 根据类型获取 //根据类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理 //配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题 HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class); happyComponent.doWork();
根据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.注意事项
声明bean,不分先后顺序,spring容器内部有缓存机制,先实例化后属性赋值!
ref 容易错写成value,会抛出Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 异常!
只有声明到ioc容器,方可被其他bean引用!
bean属性赋值:内部bean声明(了解)
声明内部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>
测试读取
@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配置参数
实现目标
将Druid连接池对象交给SpringIoC容器管理!
加入数据库依赖
<!-- 数据库驱动 和 连接池--> <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>
创建外部属性文件
文件位置:resources/jdbc.properties
# 配置成你的数据信息 jdbc.user=root jdbc.password=root jdbc.url=jdbc:mysql:///数据库名 jdbc.driver=com.mysql.cj.jdbc.Driver
引入属性文件
<!-- 引入外部属性文件 --> <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特性
FactoryBean简介
FactoryBean
接口是Spring IoC容器实例化逻辑的可插拔性点。用于配置复杂的Bean对象,可以将创建过程存储在
FactoryBean
的getObject方法!
FactoryBean<T>
接口提供三种方法:
T getObject()
:返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!
boolean isSingleton()
:如果此
FactoryBean
返回单例,则返回true
,否则返回false
。此方法的默认实现返回true
(注意,lombok插件使用,可能影响效果)。
Class<?> getObjectType()
: 返回getObject()
方法返回的对象类型,如果事先不知道类型,则返回null
。FactoryBean使用场景
代理类的创建
第三方框架整合
复杂对象实例化等
Factorybean应用
准备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; } }
配置FactoryBean实现类
<!-- FactoryBean机制 --> <!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 --> <bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean"> <!-- property标签仍然可以用来通过setXxx()方法给属性赋值 --> <property name="machineName" value="iceCreamMachine"/> </bean>
测试读取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); }
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的作用域
Bean作用域概念
<bean
标签声明Bean,只是将Bean的信息配置给SpringIoC容器!在IoC容器中,这些
<bean
标签对应的信息转成Spring内部BeanDefinition
对象,BeanDefinition
对象内,包含定义的信息(id,class,属性等等)!这意味着,
BeanDefinition
与类
概念一样,SpringIoC容器可以可以根据BeanDefinition
对象反射创建多个Bean对象实例。具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!
默认情况:我们全局只需要实例化一个Bean对象,绝大情况我们也仅需创建一个对象!
常用作用域
取值 含义 创建对象的时机 默认值 singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时 是 prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 时 否 如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用): 取值 含义 创建对象的时机 默认值 ------- ---------- ------- --- request 请求范围内有效的实例 每次请求 否 session 会话范围内有效的实例 每次会话 否 作用域配置和测试
配置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的生命周期
理解Bean的生命周期作用
Spring Framework的Bean生命周期是指一个Bean对象从它的创建、初始化到销毁的整个过程。
理解Spring Bean的生命周期可以帮助开发者更好地管理Bean,可以实现以下目的:
避免重复初始化Bean,提高Bean实例化的效率;
在Bean初始化前后做些额外的处理,如日志记录、权限检查等;
实现自定义的操作,如在Bean销毁时释放资源、设置缓存等;
理解Bean的整个生命周期有助于排查问题,提高应用程序的可维护性;
理解Spring Aop 等功能的实现原理,并参与定制过程;
Bean生命周期清单和步骤内容
Spring Bean 的生命周期指从 Spring 容器创建 Bean 实例开始,到 Bean 销毁的整个过程,可以按照以下流程分为以下几个阶段:
实例化Bean实例:Spring 容器使用指定的实例化策略创建 bean,该策略可以是无参构造、工厂方法等。当 Spring 加载 Bean 的配置文件并且 Bean 标签被解析后,这个类(Bean)就会被实例化。
Bean实例属性设置:Spring 通过调用 setter 方法或直接设置字段的方式来注入 Bean 的属性。
Aware 相关接口的回调:Spring 通过 Aware 接口来把一些 Bean 相关的资源注入到 Bean 中。例如,BeanNameAware 接口可获取到 Bean 的名称;ApplicationContextAware 接口可获取到 ApplicationContext 对象实例;BeanFactoryAware 接口可获取到 BeanFactory 对象实例等。
Bean初始化前的操作:在 Bean 的初始化之前,Spring 允许用户自定义 Bean 实例化后的一些操作。
如果有BeanPostProcessor注册,先执行beforeInitialization()方法;
如果Bean实现了InitializingBean接口,则执行afterPropertiesSet()方法
Bean 的初始化方法调用:如果在配置文件中使用init-method属性声明了初始化方法,则执行该方法;
Bean初始化后的操作:在 Bean 的初始化之后,如果有BeanPostProcessors注册,执行afterInitialization()方法;
此方法中,Bean实例已经完成了实例化和初始化工作,最终会将afterInitialization()方法修改后返回的对象存储到IoC容器中!
Spring Aop的实现,通过定义BeanPostProcessor(AbstractAutoProxyCreator),在后置方法中添加动态代理技术,进行Bean的动态代理对象生成!
使用 Bean:即在 IoC 容器中调用 getBean() 方法获取 Bean 实例,使用 Bean 的过程。
销毁 Bean:当 Bean 不再被使用时,Spring 容器会自动释放 Bean 占有的资源,关闭 IoC 容器。 开发人员可以自己实现 DisposableBean 接口或者为 Bean 配置一个指定的 destroy-method 方法来实现自定义销毁的逻辑。
关闭IoC容器 在整个生命周期过程中,Spring 提供了各种监听器和钩子函数,允许开发人员在不同的 Bean 生命周期阶段添加自己的处理逻辑以实现更加灵活和智能的控制。
参与Bean生命周期定义
测试 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; } }
测试 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注解的标记和扫描
注解理解
和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上:所有一切的操作都是 Java 代码来完成的,XML 和注解只是告诉框架中的 Java 代码如何执行。
扫描理解
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
准备Spring项目和组件
准备项目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>
准备组件类
普通组件 /** * 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 { }
组件添加标记注解
组件标记注解和区别
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这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记。
使用注解标记
普通组件 /** * 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 { }
配置文件确定扫描范围
情况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>
组件BeanName问题
在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。
默认情况:
类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。
使用value属性指定:
@Controller(value = "tianDog") public class SoldierController { }当注解中只设置一个属性时,value属性的属性名可以省略:
@Service("smallDog") public class SoldierService { }总结
注解方式IoC只是标记哪些类要被Spring管理
最终,我们还需要XML方式或者后面讲解Java配置类方式指定注解生效的包
现阶段配置方式为 注解 (标记)+ XML(扫描)
bean属性赋值:引用类型自动装配(DI)
设定场景
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"); } }
自动装配实现
前提
参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。
注意:不区分IoC的方式!XML和注解都可以!
@Autowired注解
在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。
给Controller装配Service
@Controller(value = "tianDog") public class SoldierController { @Autowired private SoldierService soldierService; public void getMessage() { soldierService.getMessage(); } }
给Service装配Dao
@Service("smallDog") public class SoldierService { @Autowired private SoldierDao soldierDao; public void getMessage() { soldierDao.getMessage(); } }
@Autowired注解细节
标记位置
成员变量
这是最主要的使用方式!
与xml进行bean ref引用不同,他不需要有set方法!
@Service("smallDog") public class SoldierService { @Autowired private SoldierDao soldierDao; public void getMessage() { soldierDao.getMessage(); } }
构造器
@Controller(value = "tianDog") public class SoldierController { private SoldierService soldierService; @Autowired public SoldierController(SoldierService soldierService) { this.soldierService = soldierService; } ……
setXxx()方法
@Controller(value = "tianDog") public class SoldierController { private SoldierService soldierService; @Autowired public void setSoldierService(SoldierService soldierService) { this.soldierService = soldierService; } ……
工作流程
首先根据所需要的组件类型到 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;佛系装配
给 @Autowired 注解设置 required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性
@Controller(value = "tianDog") public class SoldierController { // 给@Autowired注解设置required = false属性表示:能装就装,装不上就不装 @Autowired(required = false) private ISoldierService soldierService;扩展JSR-250注解@Resource
理解JSR系列注解
JSR(Java Specification Requests)是Java平台标准化进程中的一种技术规范,而JSR注解是其中一部分重要的内容。按照JSR的分类以及注解语义的不同,可以将JSR注解分为不同的系列,主要有以下几个系列:
JSR-175: 这个JSR是Java SE 5引入的,是Java注解最早的规范化版本,Java SE 5后的版本中都包含该JSR中定义的注解。主要包括以下几种标准注解:
@Deprecated
: 标识一个程序元素(如类、方法或字段)已过时,并且在将来的版本中可能会被删除。
@Override
: 标识一个方法重写了父类中的方法。
@SuppressWarnings
: 抑制编译时产生的警告消息。
@SafeVarargs
: 标识一个有安全性警告的可变参数方法。
@FunctionalInterface
: 标识一个接口只有一个抽象方法,可以作为lambda表达式的目标。
JSR-250: 这个JSR主要用于在Java EE 5中定义一些支持注解。该JSR主要定义了一些用于进行对象管理的注解,包括:
@Resource
: 标识一个需要注入的资源,是实现Java EE组件之间依赖关系的一种方式。
@PostConstruct
: 标识一个方法作为初始化方法。
@PreDestroy
: 标识一个方法作为销毁方法。
@Resource.AuthenticationType
: 标识注入的资源的身份验证类型。
@Resource.AuthenticationType
: 标识注入的资源的默认名称。
JSR-269: 这个JSR主要是Java SE 6中引入的一种支持编译时元数据处理的框架,即使用注解来处理Java源文件。该JSR定义了一些可以用注解标记的注解处理器,用于生成一些元数据,常用的注解有:
@SupportedAnnotationTypes
: 标识注解处理器所处理的注解类型。
@SupportedSourceVersion
: 标识注解处理器支持的Java源码版本。
JSR-330: 该JSR主要为Java应用程序定义了一个依赖注入的标准,即Java依赖注入标准(javax.inject)。在此规范中定义了多种注解,包括:
@Named
: 标识一个被依赖注入的组件的名称。
@Inject
: 标识一个需要被注入的依赖组件。
@Singleton
: 标识一个组件的生命周期只有一个唯一的实例。
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=MovieCatalogxml引入外部配置
<!-- 引入外部配置文件--> <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注解细节
@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>@Bean 初始化和销毁方法指定
@Bean
注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在bean
元素上的init-method
和destroy-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(); } }@Bean Scope作用域
可以指定使用
@Bean
注释定义的 bean 应具有特定范围。您可以使用在 Bean 作用域部分中指定的任何标准作用域。默认作用域为
singleton
,但您可以使用@Scope
注释覆盖此范围,如以下示例所示:@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }@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.class
和ConfigB.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扩展(了解)
@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则不注入。如果有多个实现类,必须全部满足方可注入!
@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方式配置总结
所有内容写到xml格式配置文件中
声明bean通过<bean标签
<bean标签包含基本信息(id,class)和属性信息 <property name value / ref
引入外部的properties文件可以通过<context:property-placeholder
IoC具体容器实现选择ClassPathXmlApplicationContext对象
XML+注解方式配置总结
注解负责标记IoC的类和进行属性装配
xml文件依然需要,需要通过<context:component-scan标签指定注解范围
标记IoC注解:@Component,@Service,@Controller,@Repository
标记DI注解:@Autowired @Qualifier @Resource @Value
IoC具体容器实现选择ClassPathXmlApplicationContext对象
完全注解方式配置总结
完全注解方式指的是去掉xml文件,使用配置类 + 注解实现
xml文件替换成使用@Configuration注解标记的类
标记IoC注解:@Component,@Service,@Controller,@Repository
标记DI注解:@Autowired @Qualifier @Resource @Value
<context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {"com.atguigu.components"})替代
<context:property-placeholder引入外部配置文件使用@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})替代
<bean 标签使用@Bean注解和方法实现
IoC具体容器实现选择AnnotationConfigApplicationContext对象