一、框架前言
1、总体技术体系
- 单一架构
一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one.
单一架构,项目主要应用技术框架为:Spring,SpringMVC,Mybatis等
- 分布式架构
一个项目(对应IDEA中的一个 project),拆分成很多个模块,每个模块是一个IDEA中的一个module。每一个工程都是运行在自己的Tomcat上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。
分布式架构,项目主要应用的技术框架:SpringBoot,SpringCloud,中间件等
2、框架概念和理解
框架(Framework)是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。
站在文件结构的角度理解框架,可以将框架总结:框架 = jar包 + 配置文件
总之,框架已经对基本的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。
二、SpringFramework简介
1、Spring 和 SpringFramework
广义上的Spring:Spring技术栈(全家桶)
广义上的Spring泛指以Spring Framework为基础的Spring技术栈。
经过十多年的发展,Spring已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如Spring Framework、SpringMVC、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框架为基础!
2、SpringFramework主要功能模块
功能模块 | 功能介绍 |
---|---|
Core Container | 核心容器,控制反转和依赖注入 |
AOP&Aspects | 面向切面编程 |
TX | 声明式事务管理 |
Testing | 快速整合测试环境 |
Data Access/Integration | 提供了对数据访问/集成的功能。 |
Spring MVC | 提供了面向Web应用程序的集成功能。 |
3、Spring主要优势
生态好
三、Spring IoC容器概念
1、组件和组件管理概念
什么是组件?
组件承担着程序不可获缺的功能,像工具类util,实体类pojo都不是组件,租件一定是对象,对象不一定是组件。
整个项目就是由各个组件搭建而成的:
Spring充当组件管理角色(IoC)
组件可以完全交给Spring框架进行管理,Spring框架代替了程序员原有的new对象和对象赋值动作。
Spring具体的组件管理动作包含:
- 组件对象实例化
- 组件属性辅赋值
- 组件对象之间的引用
- 组件对象存活周期管理
- ......我们只需要编写元数据(配置文件)告知Spring管理哪些类组件和他们的管理即可!注意:组件是映射到应用程序中所有可重用组件的Java对象,应该是可复用的功能对象!
- 组件一定是对象,对象不一定是组件
- 综上所述,Spring充当一个组件容器,创建、管理、存储组件,减少了我们的编码压力,让我们更加专注进行业务编写。
2、Spring IoC容器和容器实现
Spring IoC 容器,负责实例化、配置和组装bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、注解表现。它允许表达组成应用程序的组件以及这些组件之间丰富的互相依赖关系。
SpringIoC容器接口:
org.springframework.beans和org.springframework.context包是Spring Framework的IoC容器的基础包。BeanFactory接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口。
ApplicationContext是BeanFactory的子接口。
- 更容易与Spring的AOP功能集成
- 消息资源处理(用于国际化)
- 特定于应用程序给予此接口实现,例如Web应用程序的WebApplicationContext。简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多特定于企业的功能。ApplicationContext是BeanFactory的完整超集!
ApplicationContext 容器实现类:
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
AnnotationConfigApplicationContext | 通过读取Java配置类创建 IOC 容器对象 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
SpringIoC 容器管理配置方式:
Spring IoC 容器使用多种形式的配置元数据。此配置元数据表示您作为应用程序开发人如何告诉 Spring 容器实例化、配置和组装应用程序的对象。
Spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式
3、Spring IoC/DI概念总结
- IoC容器
Spring IoC 容器,负责实例化、配置和组装bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。
- IoC(Inversion of Control)控制反转
IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。
- DI(Dependency Injection)依赖注入
DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在Spring中,DI 是通过XML配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter方法注入和接口注入。
四、 Spring IoC / DI 实现
1、Spring IoC / DI 实现步骤
1)配置元数据(配置)
配置元数据,既是编写交给 Spring IoC 容器管理组件的信息,配置方式有三种。
基于 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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="h1" [1] class="com.xxx.xxx.HappyComponent" [2]> </bean><bean id="..." class="..."></bean>
</beans>
Spring IoC 容器管理一个或多个组件,这些组件是使用你提供给容器的配置元数据创建的。
<bean/>标签 == 组件信息声明
id 属性是标识单个 Bean 定义的字符串
class 属性定义 Bean 的类型并使用完全限定的类名
2)实例化 IoC 容器
提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、JavaClassPath等)加载配置元数据。
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
3)使用容器,获取Bean(组件)
//创建ioc容器对象,指定配置文件,ioc也开始实例组件对象
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
//获取ioc容器的组件对象
HappyComponent happyComponent = ioc.getBean("h1",HappyComponent.class);
//使用组件对象
happyComponent.doWork();
2、基于XML方式管理Bean
1)实验一:声明配置文件和创建容器
①创建maven工程
②导入SpringIoC相关依赖
③准备组件类
④创建SpringIoC配置(XML)
⑤创建IoC容器
⑥获取Bean
2)实验二:获取Bean
①根据 id 获取
//方式1: 根据id获取
//没有指定类型,返回为Object,需要类型转化!
HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("h1");
happyComponent.doWork();
②根据类型获取
//方式2: 根据类型获取
//根据类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理
//配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题
HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);
happyComponent.doWork();
③根据 id 和类型获取
//方式3: 根据id和类型获取
HappyComponent happyComponent = iocContainer.getBean("h1", HappyComponent.class);
happyComponent.doWork();
总结:根据类型来获取 bean 时,在满足 bean 唯一性的前提下,其实只要看:【对象 instanceof 指定的类型】的返回结果,只要返回的是 true 就可以认定为和类型匹配,能够获取到。
3)实验三:Bean属性赋值:setter注入
①组件类添加属性(必须配置set方法,属性设置,ioc容器是通过set方法调用,进行属性赋值)
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");}
}
②配置时给属性指定值
<!-- 实验三 [重要]给bean的属性赋值:setter注入 -->
<bean id="happyComponent3" class="com.atguigu.ioc.HappyComponent"><!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 --><!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) --><!-- value属性:指定属性值 --><property name="componentName" value="veryHappy"/>
</bean>
③测试属性输出
@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());
}
4)实验四:Bean属性赋值:引用其他Bean
①声明新组件
public class HappyMachine {private String machineName;public String getMachineName() {return machineName;}public void setMachineName(String machineName) {this.machineName = machineName;}
}
②原组件引用新组件
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");}
}
③配置新组件
<bean id="happyMachine" class="com.atguigu.ioc.HappyMachine"><property name="machineName" value="makeHappy"/>
</bean>
④组件之间引用配置
<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>
⑤测试
@Test
public void testExperiment04() {ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-04.xml");HappyComponent happyComponent = iocContainer.getBean("happyComponent4", HappyComponent.class);//获取另一个beanSystem.out.println(happyComponent.getHappyMachine().getMachineName());
}
注意事项:
- 声明 bean,不分先后顺序,spring 容器内部有缓存机制,先实例化后属性赋值
- ref 容易错写成 value,会抛出Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 异常!
- 只有声明到 IoC 容器,方可被其他 bean 引用!
5)实验五:Bean属性赋值:内部Bean声明
声明内部bean配置
在bean里面配置的bean就是内部bean,内部bean只能在当前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>
6)实验六: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配置文件中名称空间。
⑤配置连接池信息
<!-- 实验六 [重要]给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>
⑥读取测试
@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);
}
7)实验七:高级特性: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使用场景
a.代理类的创建
b.第三方框架整合
c.复杂对象实例化等
③FactoryBean应用
a.准备FactoryBean实现类
// 实现FactoryBean接口时需要指定泛型
// 泛型类型就是当前工厂要生产的对象的类型
public class HappyFactoryBean implements FactoryBean<HappyMachine> {private String machineName; public String getMachineName() {return machineName;} public void setMachineName(String machineName) {this.machineName = machineName;} @Overridepublic HappyMachine getObject() throws Exception { // 方法内部模拟创建、设置一个对象的复杂过程HappyMachine happyMachine = new HappyMachine(); happyMachine.setMachineName(this.machineName); return happyMachine;} @Overridepublic Class<?> getObjectType() { // 返回要生产的对象的类型return HappyMachine.class;}
}
b.配置FactoryBean实现类
<!-- FactoryBean机制 -->
<!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
<bean id="h1" class="com.atguigu.ioc.HappyFactoryBean"><!-- property标签仍然可以用来通过setXxx()方法给属性赋值 --><property name="machineName" value="张三"/>
</bean>
c.测试读取FactoryBean和FactoryBean.getObject对象
@Test
public void testExperiment07() {ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-bean-07.xml");//注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象HappyMachine happyMachine = ioc.getBean("h1",HappyMachine.class);System.out.println("happyMachine = " + happyMachine);//如果想要获取FactoryBean对象, 直接在id前添加&符号即可! &happyMachine7 这是一种固定的约束Object bean = ioc.getBean("&h1");System.out.println("bean = " + bean);
}
④FactoryBean和BeanFactory区别
FactoryBean 是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以定义任何所需的初始化逻辑,生产出一些定制化的 bean.
一般情况下,整合第三方框架,都是通过定义 FactoryBean 实现!!!
8)实验八:高级特性:Bean的作用域
①Bean作用域概念
<bean 标签声明Bean,只是将 Bean 的信息配置给 SpringIoC 容器!
在IoC容器中,这些 <bean 标签对应的信息转成 Spring 内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)这意味着,BeanDefinition 与类概念一样,SpringIoC 容器可以根据 BeanDefinition 对象反射创建多个 Bean 对象实例。具体创建多少个 Bean 的实例对象,由 Bean 的作用域 Scope 属性指定。
默认情况:我们全局只需要实例化一个 Bean 对象,绝大情况我们也仅需创建一个对象!
②作用域配置和测试
配置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);//多例对比 falseSystem.out.println(bean == bean1);HappyComponent bean2 = iocContainer.getBean(HappyComponent.class);HappyComponent bean3 = iocContainer.getBean(HappyComponent.class);//单例对比 trueSystem.out.println(bean2 == bean3);
}
9)实验九:高级特性:Bean的生命周期
①理解Bean的生命周期作用
Spring Framework的Bean生命周期是指一个Bean对象从它的创建、初始化到销毁的整个过程。
理解Spring Bean 的生命周期可以帮助开发者更好地管理Bean,可以实现以下目的:
a.避免重复初始化Bean,提高Bean实例化的效率;
b.在Bean初始化前后做些额外的处理,如日志记录、权限检查等;
c.实现自定义的操作,如在Bean销毁时释放资源、设置缓存等;
d.理解Bean的整个生命周期有助于排查问题,提高应用程序的可维护性;
e.理解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生命周期定义
a.测试BeanPostProcessor 接口
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("HappyBeanPostProcessor的before执行了");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("HappyBeanPostProcessor的after执行了");return bean;}
}
b.测试 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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="h1" class="com.atguigu.spring.pojo.HappyComponent" init-method="init" destroy-method="destroy"><property name="componentName" value="张三"/></bean><!--创建后置处理器对象后置处理器对象针对的是当前整个容器中的所有对象的init方法的前后--><bean id="happyBeanPostProcessor" class="com.atguigu.spring.processor.HappyBeanPostProcessor"/>
</beans>
c.测试读取配置即可
@Testpublic void testLifeCycle(){ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-09.xml");HappyComponent h1 = ioc.getBean("h1", HappyComponent.class);System.out.println("4、使用:"+h1);ioc.close();//关闭的是容器,容器在关闭之前,统一销毁所有的单例对象}
执行结果:
3、基于注解方式管理Bean
1)实验一:Bean注解标记和扫描(IoC)
①注解理解
和XML配置文件一样,注解本身并不能执行,注解本身仅仅只能做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。
②扫描理解
Spring为了知道程序员在哪里地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。
③准备组件
④组件添加标记注解
a.组件标记注解和区别
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 这三个注解只是给开发人员看的,然我们能够便于分辨组件的作用。 注意:虽然它们本质上一样,但是为了代码的可读性、程序结构严谨!我们肯定不能随便胡乱标记。
b.使用标记组件
Controller类型组件@Controller
public class XxxController {
}
service类型组件@Service
public class XxxService {
}
dao类型组件@Repository
public class XxxDao {
}
⑤配置文件确定扫描范围
a.情况一:基本扫描配置
<!-- 配置自动扫描的包 --><!-- 1.包要精准,提高性能!2.会扫描指定的包和子包内容3.多个包可以使用,分割 例如: com.atguigu.spring.anno.controller,
com.atguigu.spring.anno.service--><context:component-scan base-package="com.atguigu.spring.controller"/>
b.情况二:指定排除组件
<context:component-scan base-package="com.atguigu.spring.anno"><!-- context:exclude-filter标签:指定排除规则 --><!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 --><!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 --><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
c.情况三:指定扫描组件
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.atguigu.spring.anno" 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. 例如:NewsUserController类对应的bean的id就是newsUserController.
使用value属性指定:
@Controller(value = "h1")
public class NewsUserController {
}
当注解中只设置一个属性时,value属性的属性名可以省略:
@Service("h1")
public class NewsUserService {
}
⑦总结
- 注解方式 IoC 只是标记哪些类要被 Spring 管理
- 最终,我们还需要 XML 方式或者后面讲解 Java 配置类方式指定注解生效的包
- 现阶段配置方式为 注解(标记)+ XML(扫描)
2)实验二:Bean属性赋值:引用类型自动装配(DI)
①设定场景
- NewsUserController 需要 NewsUserService
- NewsUserService 需要 NewsUserDao 同时在各个组件中声明要调用的方法
NewsUserController中声明方法
@Controller
public class NewsUserController {private NewsUserService newsUserService;public void showAllUser(){System.out.println("NewsUserController的showAllUser方法执行了!");newsUserService.showAllUser();}
}
NewsUserService中声明的方法
@Service
public class NewsUserServiceImpl implements NewsUserService {private NewsUserDao newsUserDao;@Overridepublic void showAllUser() {System.out.println("NewsUserServiceImpl的showAllUser方法执行了!");newsUserDao.selectAll();}
}
NewsUserDao中声明的方法
@Repository
public class NewsUserDaoImpl implements NewsUserDao {@Overridepublic void selectAll() {System.out.println("NewsUserImpl的selectAll方法执行了!");}
}
②自动装配实现
a.前提
参与自动装配的组件(需要装配、被装配)全部都必须在 IoC 容器中。
注意:不区分 IoC 的方式! XML和注解都可以!
b.@Autowired注解
在成员变量上直接标记@Autowired注解即可,不需要提供 setxxx()方法。以后我们在项目中的正式用法就是这样。
c.装配
@Autowiredprivate NewsUserService newsUserService;@Autowiredprivate NewsUserDao newsUserDao;
③@Autowired注解细节
a.标记位置
Ⅰ.成员变量
这种最主要的使用方式!与xml进行bean ref 应用不同,它不需要set方法!
@Controller
public class NewsUserController {@Autowiredprivate NewsUserService newsUserService;......
Ⅱ.构造器
@Controller
public class NewsUserController {private NewsUserService newsUserService;@Autowiredpublic NewsUserController(NewsUserService newsUserService){this.newsUserService = newsUserService;}......
Ⅲ.setXxx() 方法
@Controller
public class NewsUserController {private NewsUserService newsUserService;@Autowiredpublic void setNewsUserService(NewsUserService newsUserService) {this.newsUserService = newsUserService;}......
b.工作流程
- 首先根据所需要的组件类型到 IoC 容器中查找
- 能够找到唯一的 bean:直接执行装配
- 如果完全找不到匹配这个类型的 bean:装配失败
- 和所需类型匹配的 bean 不止一个
- 没有 @Qualifier 注解:根据@Autowired 标记位置成员变量的变量名作为 bean 的 id进行匹配
- 能找到:执行装配
- 找不到:装配失败
- 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的 id 进行匹配
- 能够找到:执行装配
- 找不到:装配失败
- 没有 @Qualifier 注解:根据@Autowired 标记位置成员变量的变量名作为 bean 的 id进行匹配
注意:我们两个类(NewsUserServiceImpl、NewsUserServicePlusImpl)都实现了NewsUserService接口。假若需要装配 newsUserService,它的实现 bean不唯一,程序就会出错。这时我们就要用到@Qualifier 指定 bean的id进行匹配。
@Controller
public class NewsUserController {@Autowired @Qualifier("newsUserServicePlusImpl")private NewsUserService newsUserService;......
④佛系装配
给@Autowired 注解设置required = false 属性表示:能装就装,装不上就不装。但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性。
⑤扩展JSR-250 注解@Resource
@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别?
- @Resource注解时JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。
- @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 NewsUserController {/*** 1. 如果没有指定name,先根据属性名查找IoC中组件NewsUserService* 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找* 3. 可以指定name名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')*/@Resourceprivate NewsUserService newsUserService;//@Resource(name = "指定beanName")//private NewsUserService newsUserService;public void show(){System.out.println("NewsUserController.show");newsUserService.show();}
}
3)实验三:Bean属性赋值:基本类型属性赋值(DI)
@Value 通常用于注入外部化属性
①声明外部配置
config.properties
config.name=azhen
config.age=38
②XML 引入外部配置
<!-- 引入外部配置文件-->
<context:property-placeholder location="classpath:config.properties"/>
③@Value注解读取配置
@Component
public class BaseConfig {
/*** 情况1: ${key} 取外部配置key对应的值!* 情况2: ${key:defaultValue} 没有key,可以给与默认值*/@Value("${config.name}")private String name;@Value("${config.age}")private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}
输出结果:
bean = BaseConfig{name='azhen', age=38}
4、基于配置类方式管理Bean
1)完全注解开发理解
Spring 完全注解配置(Fully Annotation-based Configuration)是通过Java配置类代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。
2)实验一:配置类和扫描注解
- 回顾:xml+注解方式
配置spring-anno.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:component-scan base-package="com.atguigu.spring.anno"/><!-- 引入外部配置文件--><context:property-placeholder location="classpath:config.properties"/>
</beans>
测试创建 IoC 容器
@Testpublic void testComponentAnno(){ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-anno.xml");}
- 配置类+注解方式(完全注解方式)
配置类
使用@Configuration 注解将一个普通的类标记为 Spring 的配置类。
@Configuration //标识当前类,是配置类,取代xml配置文件
@ComponentScan(basePackages = "com.atguigu.spring.allAnno") //代替<context:component-scan
@PropertySource({"classpath:config.properties","classpath:jdbc.properties"})
@Import({StudentConfig.class,ConditionalConfig.class})
public class SpringConfig {}
测试创建IoC容器
@Test
public void testAllAnno() {AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);
}
- 总结
@Configuration指定一个类为配置类,可以添加配置注解,代替配置xml文件
@ComponentScan(basePackages = {"包","包"}) 替代 <context:component-scan 标签实现注解扫描
@PropertySource("classpath:配置文件地址") 替代 <context:property-placeholder 标签配合 IoC/DI注解,可以进行完整给注解开发!
3)实验二:@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 id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean>
</beans>
- 配置类实现:
@Bean注解用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉 Spring 的 <beans/> xml 配置的人来说,@Bean 注释与 <bean/> 元素起着相同的作用。
@Configuration //标识当前类,是配置类,取代xml配置文件
@ComponentScan(basePackages = "com.atguigu.spring.allAnno") //代替<context:component-scan
@PropertySource({"classpath:config.properties","classpath:jdbc.properties"})
@Import({StudentConfig.class,ConditionalConfig.class})
public class SpringConfig {/*** @Bean注解标识的方法,意味着该方法返回的对象是要存储在ioc容器里的。故方法为bean的id名* @return*/@Beanpublic DruidDataSource dataSource(@Value("${jdbc.driverClassName}")String driverClassName,@Value("${jdbc.url}")String url,@Value("${jdbc.username}")String username,@Value("${jdbc.password}")String password){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}
}
4)实验三:高级特性:@Bean注解细节
①@Bean生成 BeanName 问题
@Bean源码
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名称与方法名称相同。
@Configuration
public class AppConfig {@Beanpublic TransferServiceImpl transferService() {return new TransferServiceImpl();}
}
相当于xml
<beans><bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
② @Bean初始化和销毁方法指定
@Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-method 和 destroy-method 属性。
public class SpringConfig {@Bean(initMethod = "init",destroyMethod = "destroy")public NewsUser getNewsUser(){return new NewsUser();}
}
public class NewsUser {
......public void init(){System.out.println("NewsUser的初始方法!");}public void destroy(){System.out.println("NewsUser的销毁方法!");}
}
③ @Bean Scope作用域
可以指定使用 @Bean 注释定义的bean 应具有特定范围。你可以使用在Bean 作用域部分中指定的任何标准作用域。默认作用域为 singleton,但你可以使用 @Scope 注释覆盖此范围。
@Scope("prototype") //默认为 singleton
public NewsUser getNewsUser(){return new NewsUser();
}
④ @Bean方法之间依赖
参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系,
当需要的对象也在 Spring 容器里时,可以采取形参的方式,让 Spring 给我们注入。
@Configuration //标识当前类,是配置类,取代xml配置文件
@ComponentScan(basePackages = "com.atguigu.spring.allAnno") //代替<context:component-scan
public class SpringConfig {//创建HappyComponent对象//当需要的对象也在Spring容器里时,可以采取形参的方式,让Spring让我们进入注入@Beanpublic HappyComponent happyComponent(HappyMachine happyMachine){HappyComponent happyComponent = new HappyComponent();happyComponent.setComponentName("嘿嘿嘿");happyComponent.setHappyMoney(9700);happyComponent.setHappyMachine(happyMachine);return happyComponent;}//创建HappyMachine对象@Beanpublic HappyMachine happyMachine(){HappyMachine happyMachine = new HappyMachine();happyMachine.setMachineName("回村的诱惑");return happyMachine;}}
5)实验四:高级特性:@Import扩展
@Import 注释允许从另一个配置类加载 @Bean 定义
@Configuration
public class ConfigA {@Beanpublic A a() {return new A();}
}@Configuration
@Import(ConfigA.class)
public class ConfigB {@Beanpublic B b() {return new B();}
}
现在,在实例化上下文时不需要同时指定 ConfigA.class 和 ConfigB.class,只需显式提供 ConfigB。
public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);A a = ctx.getBean(A.class);B b = ctx.getBean(B.class);
}
此方法简化了容器实例化,因为只需要处理一个类,而不是要求你在构造期间记住可能大量的 @Configuration 类。
6)实验五:高级特性:@Conditional扩展(了解)
@Conditional 是Spring4 新提供的注解,能够根据一定的条件进行判断,满足条件就给容器注入bean。了解@Conditional辅助我们更灵活的进行 Bean 注入和更好的解读SpringBoot原理代码。
Condition 是一个接口,返回true 就注入 bean,false 则不注入。如果有多个实现类,必须全部满足方可注入。
5、三种配置方式总结
1)XML方式配置总结
- 所有内容写到xml格式配置文件中
- 声明bean通过<bean标签
- <bean标签包含基本信息(id,class)和属性信息<property name value/ref
- 引入外部的properties 文件可以通过<context:property-placeholder
- IoC具体容器实现选择 ClassPathXmlApplicationContext对象
2)XML+注解方式配置总结
- 注解负责标记IoC的类和进行属性装配
- xml文件依然需要,需要通过<context:component-scan标签指定注解范围
- 标记IoC注解:@Component,@Service,@Controller,@Repository
- 标签DI注解:@Autowired,@Qualifier,@Resource,@Value
- IoC具体容器实现选择 ClassPathXmlApplicationContext对象
3)完全注解方式配置总结
- 完全注解方式指的是去掉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对象
6、整合Spring5-Test5搭建测试环境
①整合测试环境作用
好处1:不需要自己创建 IoC 容器对象了
好处2:任何需要的 bean 都可以在测试类中直接享受自动装配
②导入依赖
<!--junit5测试-->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version>
</dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>6.0.6</version><scope>test</scope>
</dependency>
③整合测试注解使用
//@SpringJUnitConfig(locations = {"classpath:spring-context.xml"}) //指定配置文件xml
@SpringJUnitConfig(value = {BeanConfig.class}) //指定配置类
public class Junit5IntegrationTest {@Autowiredprivate User user;@Testpublic void testJunit5() {System.out.println(user);}
}