1.Spring基本概述
1.1 什么是Spring
- Spring 是分层的 Java SE/EE 应用 full-stack(一站式、全站)轻量级开源框架,以 IoC(Inverse Of Control:控制反转)和 AOP(Aspect Oriented Programming:面向切面编程)为内核提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.2 Spring的优势(重点理解)
- 方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。 - AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过AOP 轻松应付。 - 声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。 - 方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。 - 方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架( Struts、 Hibernate、 Hessian、 Quartz等)的直接支持。 - 降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、 JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。 - Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
1.3 Spring架构组成
Spring架构由诸 多模块组成,可分类为:
- 核心技术: 依赖注入,事件,资源,i18n, 验证,数据绑定,类型转换,SpEL, AOP 。
- 测试: 模拟对象,TestContext框架, Spring MVC测试,WebTestClient。
- 数据访问: 事务,DAO支持,JDBC, ORM, 封送XML。
- Spring MVC和Spring WebFlux Web框架。
- 集成: 远程处理,JMS, JCA, JMX, 电子邮件,任务,调度,缓存。
- 语言: Kotlin, Groovy, 动态语言。
Spring 依赖
Groupld | Artifactld | 说明 |
---|---|---|
org.springframework | spring-beans | Beans支持,包含Groovy |
org.springframework | spring-aop | 基于代理的AOP支持 |
org.springframework | spring-aspects | 基于AspectJ的切面 |
org.springframework | spring-context | 应用上下文运行时,包括调度和远程抽象 |
org.springframework | spring-context-support | 支持将常见的第三方类库集成到Spring应用上下文 |
org.springframework | spring-core | spring-core |
org.springframework | spring-expression | Spring 表达式语言,SpEL |
org.springframework | spring-test | 单元测试和集成测试支持组件 |
org.springframework | spring-tx | 事务基础组件,包括对DAO的支持及JCA的集成 |
org.springframework | spring-web | web支持包,包括客户端及web远程调用 |
org.springframework | spring-webmvc | REST web服务及web应用的MVC实现 |
org.springframework | spring-jcl | Jakarta Commons Logging日志系统 |
2. 程序间耦合分析以及解决方案
2.1 相关概念
- 耦合
- 耦合指的是程序间的依赖关系
- 类的依赖关系(eg: service依赖dao层)
- 方法的依赖关系
- 耦合指的是程序间的依赖关系
- 解耦
- 我们不能消除程序间的依赖关系,只能尽可能的降低程序的依赖关系。这种降低程序间的依赖关系就叫做解耦。
- 如何解耦
- 在实际开发中,我们应该做到在编译期不依赖,在运行期依赖。
- Bean
- 在计算机英语中,有可重用组件(如持久层和业务层的接口和重用)的含义。
- JavaBean
- 其实JavaBean并不完全等于实体类,因为实体类只是可重用组件的一部分,也就是JavaBean的范围大于实体类的范围。
- JavaBean的含义:用Java语言编写的可重用组件。
2.2 使用工厂解耦(Spring容器原理)
- 降低解耦的思路
- 使用反射的机制,避免使用new关键字。
- 通过读取配置文件的方式来获取资源的全限定名。
- 工厂
- 就是创建我们的service和dao对象的(JavaBean)。
- 解决步骤:
- 需要一个配置文件来配置我们的service和dao。
- 配置的内容:唯一标志=全限定类名(key=value)。
- 通过读取配置文件中配置的内容,反射创建对象。
- 配置文件的选取:
- xml(当然后期spring框架用的肯定是xml)
- properties(读取更简单,我们这个例程先选用这种)
(1)工厂配置文件
(2)工厂单例模式——解耦
3.IOC的概念和作用
IOC概念:
- 反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。
- 变主动为被动,即反转
- bean对象保存在Spring容器里
- 反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。
IOC作用:
- 解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健
IOC实现:
- 通过解析配置文件
- 通过工厂用反射的方式创建对象
(1)IOC示例
- 配置文件(配置bean标签)
- 调用Spring工厂API(ApplicationContext接口)
public class testAccountService {
/**
程序中的对象都交由Spring的ApplicationContext工厂进行创建
*/
@Test
public void testFactory(){
//1.读取配置文件中所需创建的bean对象,并获得工厂对象
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("Bean.xml");
//2. 通过id获取bean对象
AccountService accountService= (AccountService) applicationContext.getBean("AccountService");
//3.使用对象
accountService.addAccount();
}
}
- 基于XML的IOC开发配置文件
(2)详解ApplicationContext
ApplicationContext是Spring给我们提供的核心容器,其依赖关系如下:
我们发现ApplicationContext继承了BeanFactory。BeanFactory才是顶级容器。
(1)这两个核心的容器有什么区别呢?
- ApplicationContext
ApplicationContext在构建核心容器时,创建对象采取的策略是采用立即加载的方式。 也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。 - BeanFactory
BeanFactory它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式. 也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
(2)ApplicationContext是一个接口,其重要实现类?
- ClassPathXmlApplicationContext
它可以加载类路径下的配置文件,要求配置文件必须在类路径下,否则加载不了(这种比较常用)。 - FileSystemXmlApplicationContext
它可以加载磁盘任意路径下的配置文件(必须有访问权限)。 - AnnotationConfigApplicationContext
它是用于读取注解创建容器的
4. Spring对bean的管理方式
4.1 Spring实例化bean的方式
(1) 无参数构造函数的实例化方式
- 无参数构造函数实例化方式是Spring默认的bean的实例化方式
(2)使用工厂中的普通方法实例化对象
- 我们设想一个场景:我们要使用别人写好的代码,别人写好的代码通常是封装在一个jar包中的,我们要怎么创建jar包中字节码文件的对象呢?(我们无法通过修改源码的方式来提供默认构造方法)
- 我们接下来模拟一个工厂类(它可能是存在于jar包中的),通常jar包都会暴露一个工厂类,给调用者创建对象。
(3)使用工厂中的静态方法实例化对象
4.2 Bean的细节
4.2.1Bean的作用域
(1)bean标签的scope属性:
- 作用:用于指定bean的作用范围
- 取值: 常用的就是单例的和多例的
- singleton:单例的(默认值)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session。
(2)单例与多例作用域的区别
- 单例的
- bean配置时,不指明scope则默认是单例配置
- 当bean配置是单例时,Spring容器在加载配置文件时,就会将此bean创建并保存在容器中
- 多例的
- 当bean配置是多例时,Spring容器在加载配置文件时,不会创建bean对象,只有在使用bean的id获取此对象时,才会创建此bean对象
- 当bean配置是多例时,Spring容器在加载配置文件时,不会创建bean对象,只有在使用bean的id获取此对象时,才会创建此bean对象
4.2.2Bean的生命周期
(1) 单例对象
单例对象的生命周期和容器的生命周期是一致的。 当容器创建时,对象就实例化好了。当容器还在的时候,对象也就一直存在。当容器销毁,对象也就消亡。
(2)多例对象
- 出生: 当我们使用对象时spring框架为我们创建时(调用配置bean的id)
- 活着: 对象只要是在使用过程中就一直活着
- 死亡: 当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
5. 依赖注入
5.1 依赖注入的特点及概念
依赖注入:
- 在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
- 依赖关系的维护就称为依赖注入
IOC的作用:
降低程序间的耦合(依赖关系)DI的作用:
依赖关系的管理,依赖都交给spring来维护依赖注入的数据类型
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
依赖注入的方式
- 第一种:使用构造函数提供
- 第二种:使用set方法提供
- 第三种:使用注解提供(后面再介绍)
5.2依赖注入的方式
(1)构造函数注入
- 使用的标签:constructor-arg
- 标签出现的位置:bean标签的内部
- 标签中的属性:
- name(最常用):用于指定给构造函数中指定名称的参数赋值
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据(它指的就是在spring的ioc核心容器中出现过的bean对象)
- 优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功(因为默认空参构造) - 弊端
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
(2)set方法注入(默认的)
- 涉及的标签:property
- 出现的位置:bean标签的内部
- 标签的属性
- name:用于指定注入时所调用的set方法名称(如:setName --> name)
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的ioc核心容器中出现过的bean对象
(3)复杂类型数据注入
- 创建实体类,并提供set方法
- 创建配置文件
<!-- 复杂集合set注入-->
<bean id="user" class="com.qfedu.pojo.User">
<!-- 数组set注入-->
<property name="nums">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<!-- 集合属性注入-->
<property name="list">
<list>
<value>tom</value>
<value>jack</value>
</list>
</property>
<!-- set集合注入-->
<property name="set">
<set>
<value>12</value>
<value>1</value>
</set>
</property>
<!-- map集合注入 -->
<property name="map">
<map>
<entry key="tom" value="20"></entry>
<entry key="jack" value="22"></entry>
</map>
</property>
<!-- properties注入-->
<property name="props">
<props>
<prop key="tom">man</prop>
<prop key="jack">woman</prop>
</props>
</property>
</bean>
6. IOC注解
IOC注解可以分为以下几类:
用于创建对象的
- 作用:他们的作用就和在XML配置文件中编写一个
<bean>
标签实现的功能是一样的
- 作用:他们的作用就和在XML配置文件中编写一个
用于注入数据的
- 作用:他们的作用就和在xml配置文件中的bean标签中写一个
<property>
标签的作用是一样的。
- 作用:他们的作用就和在xml配置文件中的bean标签中写一个
用于改变作用范围的
- 作用:他们的作用就和在bean标签中使用
scope
属性实现的功能是一样的
- 作用:他们的作用就和在bean标签中使用
和生命周期相关的
- 作用:他们的作用就和在bean标签中使用
init-method
和destroy-methode
的作用是一样的。
- 作用:他们的作用就和在bean标签中使用
配置相关注解
- 作用:用于配置相关项目配置文件信息(
包括配置jar包对象创建、注解包扫描、文件的读取
)
- 作用:用于配置相关项目配置文件信息(
6.1 创建对象注解
(1)@Component
- 用法: 定义在类名上。
- 作用: 用于把当前类对象存入spring容器中。
- 属性: value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
(2)Component衍生的注解
以下三个注解他们的作用和属性与@Component是一模一样,他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。
- @Controller:一般用在表现层。
- @Service:一般用在业务。
- @Repository:一般用在持久层。
衍生类注解源码解析
- @AliasFor注解作用(两个作用)
于为注解属性声明别名
它有两个属性name和value @AliasFor注解注释了自身,并且name和value 互为别名不定义新的属性而是复用其他注解已有的注解属性(通过此种方法@Component衍生出其他注解类)
- @AliasFor注解作用(两个作用)
6.2 注入数据注解
- @Autowired、@Qualifier、@Resource三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
- 集合类型的注入只能通过XML来实现。
- @Value用于注入基本类型和String类型的数据
6.2.1自动按照类型注入
(1)@Autowired
- 用法: 定义在属性(成员变量)上。
- 作用: 实现自动按照类型注入。
- 属性:无属性值,但可以结合 @Qualifier 实现spring容器中指定类型之后,再指定名称的bean对象注入
(2)实现原理:
- 情况一:如果有唯一一个类型匹配时,就直接注入。
- 容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
- 详细过程:首先会找到实现类,但是因为实现类继承了接口,所以可以把实现类看作接口(多态)
- 情况二: 如果有多个匹配时
- 首先按照类型筛选出来匹配的对象。
- 其次使用变量名称作为bean的id,然后继续筛选。
- 如果两次筛选后有唯一匹配的,可以注入成功;否则失败。
6.2.2 其他注入数据注解
(1)@Qualifier
- 作用:在按照类型注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用;但是在给方法参数注入时可以(这个我们后面讨论)。
- 属性:value:用于指定注入bean的id。
(2)@Resource
- 作用:直接按照bean的id注入。它可以独立使用。
- 属性:name:用于指定bean的id。
- 特性:
- 若未给出name属性值时(执行两步),首先按照变量名进行匹配对象,若匹配不成功再按照变量数据类型进行筛选
- 若给出name属性时(执行一步),无论成功与否,只会按照name给的名称进行筛选
- 属于jdk注解,不属于spring注解
(3)@Value
- 作用:用于注入基本类型和String类型的数据。
- 属性:value:用于指定数据的值。它也可以使用spring中SpEL(也就是spring的el表达式)。
- SpEL的写法:${表达式}。
6.3 改变作用域的注解
(1)@Scope
- 作用:用于指定bean的作用范围。
- 属性:value:指定范围的取值。
- 常用取值:singleton(单例)、prototype(多例)。
6.4 生命周期相关的注解
- 使用与生命周期相关的注解的作用跟在bean标签中使用init-method和destroy-methode的作用是一样的。
(1)@PostConstruct
- 作用:用于指定初始化方法。
(2)@PreDestroy
- 作用:用于指定销毁方法。
6.5 配置类相关注解
- 配置类的作用和bean.xml一样
(1)@Configuration
- 作用:指定当前类是一个配置类
- 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写(建议书写)
- 源码:
(2)@ComponentScan
- 作用:用于通过注解指定spring在创建容器时要扫描的包
- 属性:value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
- 类似xml配置文件如下:
(3)@Bean
- 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
- 属性: name:用于指定bean的id。当不写时,默认值是当前方法的名称
- 细节:
- 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。(推荐使用)
- 查找的方式和Autowired注解的作用是一样的。
(4)@Import
- 作用:用于导入其他的配置类
- 属性:value:用于指定其他配置类的字节码
- 细节:
- 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类。
- 此时子配置类不需要加Configuration注解,只需要在父配置类上添加Import注解
(5)@PropertySource
作用:用于指定properties文件的位置
类似配置文件中:
属性:value:指定文件的名称和路径
关键字:
- classpath:表示类路径下
- 可以结合子配置类和@Value使用,用于读取配置文件
示例:
(6)问题:@Configuration注解一定需要吗?
- 答案:@Configuration注解加不加都对程序的结果没有影响,不会因为@Configuration注解不加,造成bean注入失败(但是若配置文件中有方法相互依赖,可能会出现bean对象不是单例的情况)。
- 演示:Brunner依赖于ADataSource
- 测试结果:
- 疑问1:为什么ADataSource只创建了1次?
- 当我们使用@Bean注解修饰aDataSource方法的时候,会将ADataSource这个bean交给容器管理(就会调用ADataSource的无参数构造函数)。
- 我们使用@Bean注解修饰bRunner方法的时候,由于需要使用到ADataSource,这个时候,不再重新的创建ADataSource的对象,而是直接从spring容器(spring的单例池中获取)。所以ADataSource只会创建1次。
- 使用@Configuration修饰配置类,会在spring容器中产生一个配置类bean实例,其内部的@Bean修饰的方法产生的bean实例都是单例的
- 疑问2:如果去掉@Configuration注解只会会如何?
- 经过测试,程序不会报错,ADataSource和Brunner两个bean都会交给spring容器管理,但是区别在于ADataSource创建了2次:
- 为什么创建两次?
- 第一次创建是使用@Bean注解修饰aDataSource方法的时候,创建的。第二次创建是@Bean注解修饰bRunner方法的时候,由于BRunner对象的创建需要依赖于ADataSource。此时ADataSource这个bean不是从单例池中获取,而是重新又创建了1次。
- 不使用@Configuration修饰配置类,则不会在spring容器中产生一个配置类bean实例,在内部存在相互依赖时,使用方法名调用时可能会产生多个bean实例
- 经过测试,程序不会报错,ADataSource和Brunner两个bean都会交给spring容器管理,但是区别在于ADataSource创建了2次:
- 疑问3:如何不使用@Configuration也保证ADataSource只创建1次?
- 使用形参的方式 ,@Bean声明在有形参的方法前,为形参赋值时会从spring容器中寻找一个符合形参类型的bean实例为形参赋值(类似于@Autowired :存在符合条件的多个bean实例时,会先筛选类型,再需寻找形参名的bean实例,若二者筛选唯一,则为形参赋值成功,否则赋值失败)
- 测试结果:
6.6 Spring整合Junit
@Runwith
- 使用junit提供的一个注解(@Runwith)把原有的main方法替换成spring提供的main方法。
- 该注解的功能是替换Runner(运行器)
@ContextConfiguration
作用: 告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
属性:
- locations:指定xml文件的位置,加上classpath关键字,表示在类路径下。
- classes:指定配置类所在地位置。