Spring5总结
什么是Spring?
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层
Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
为什么要使用Spring?
在软件设计中,如果项目的耦合程度高,那么维护项目所需要的崇本也高。设计一个软件,其耦合度和内聚度通常作为衡量模块独立的标准。而划分模块的一个准则就是高内聚低耦合。
耦合是不可能被完全消除的,只能降低一部分的耦合能用数据耦合(模块间通过参数来传递数据,最低的耦合关系)就不用控制耦合(传递控制信号来执行操作),能用公共耦合(多个模块共拥有给全局数据项)就不用内容耦合(一个模块直接操作另一个模块的数据,或不通过正常入口而转入另一个模块,最高程度的耦合)。
如何解决耦合?
所以使用配置文件配置key value的全限定类名字符串,就可以动态配置不同的驱动。
并且jdbc的这种方式不再依赖具体的驱动类。
所以我们可以使用工厂模式,通过配置文件来配置创建对象的条件,来使用一个读取配置文件,并能创建和获取三层对象的工厂来解耦。
比起直接创建对象的主动方式,用工厂来创建对象的方式是被动的。而这种被动接收的方式来获取对象,就是控制反转IOC的思想(降低程序的耦合),他是spring框架的核心之一。
如何使用Spring?
下面我们用一个银行转账的小demo用3种方式来进行配置(事务控制用了2种方式)
一、纯注解方式配置
项目结构:
config是配置目录:
由SpringConfiguration作为总配置类
JdbcConfig类和TransactionConfig类是配置数据库连接的类和事务控制的类
dao是持久层
domain存放的是实体类
service是服务层
resources
jdbcConfig.properties中存放连接数据库的连接数据
test
AccountServiceTest测试类
由运行顺序讲解代码
通过测试类
因为junit的原理,所以他是无法识别我们是否使用了Spring框架的,所以必须要用spring的@Runwith来替换Junit原有的运行器
然后使用@ContextConfiguration的classes属性指定 spring 配置文件的位置
@ContextConfiguration 注解:
locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
然后我们通过注解进入配置文件类
扫描所有包就可以识别出所有的注解配置
导入了2个配置类
开启对AOP注解的支持
扫描包的话给出几个配置的典型例子
持久层的实现类:
@Repository持久层注解,
与@Component功能一致
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
@Controller @Service @Repository
他们三个注解都是针对第一个的衍生注解,他们的属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
@Controller:一般用于表现层的注解。
@Service:一般用于业务层的注解。
@Repository:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
@Autowired
作用:
自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个
类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到
就报错。
服务层的实现类:
@Service:业务层的注解
@Autowired自动注入
@Transactional
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
进入导入的第一个配置类
@Value注入基本类型和String类型的数据
@Bean标签,
作用:
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性:
name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
然后看另一个子配置类
仍然是提供了一个获取txManager对象的方法
然后是读取jdbc属性的配置,参数为类路径下的配置文件位置
最后@EnableTransactionManagement的配置是开启AOP的支持,配置了就开启,不配置就关闭
然后就实现了完全基于注解的配置
<<<<<<<<<<<<<<<<<注解控制的细节<<<<<<<<<<<<<<<<<<
可以和上面进行对照更便于理解、使用
用于创建对象的
* 他们的作用就和在xml配置文件中编写一个<bean>标签实现的功能是一样的
* Component
* 作用:把当前对象存入spring容器中
* 属性:
* value:用于指定bean的id,当我们不写时,默认值是当前类名且首字母变为小写
* Controller:一般用在表现层
* Service:一般用在业务层
* Repository:一般用在持久层
* 以上三个注解他们的作用和属性与Component是一模一样的
* 他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
*
* 用于注入数据的
* 他们的作用就和在xml配置文件中的<bean>标签中写<property>标签的作用是一样的
* Autowired
* private IAccountDao accountDao1=null;
* 访问修饰符 数据类型 变量名称 数据类型
* 注入时主要考虑变量名称与id的匹配
* 作用:
* 自动按照类型注入(接口的名称,而非自己起的id)。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配就可以注入成功。
* 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配就报错。
* 出现位置:
* 变量、方法
* 细节:
* 在使用注解注入时,set方法不是必须的。
* Qualifier:
* 作用:
* 在按照类中注入的基础上再按照名称注入。他在给类成员注入时不能单独使用(要与Autowired配合)。但是给方法参数注入时可以
* Resource:
* 作用:
* 直接按照bean的id注入。他可以独立使用
* 属性:
* name:用于指定bean的id。
* 以上3中注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现
* 另外集合类型的注入只能通过xml来实现。
*
* value
* 作用:
* 用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
* SpEL的写法:${表达式},看el表达式出现的位置
*
* 用于改变作用范围的
* 他们的作用和在bean标签中使用scope属性实现的功能是一样的
* Scope
* 作用:
* 用于指定bean的作用范围
* 属性:
* value:指定范围的取值。常用取值:singleton prototype,不写默认是单例的
* 和生命周期相关的
* 他们的作用和在bean标签中使用init-method和destroy-method的作用是一样的
* PostConstruct
* 作用:
* 用于指定初始化方法
* PreDestroy
* 作用:
* 用于指定销毁方法(多例对象的销毁,spring并不负责)
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
二、下面是以xml为主的配置方式
事务控制方面选Z额了spring编程式事务控制
dao是持久层
domain存放的是实体类
service是服务层
resources
bean.xml中存放了基于xml的spring配置信息
test
AccountServiceTest测试类
以下所有重复的部分不做解释:
@ContextConfiguration 注解:使用locations 属性来读取配置文件
locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
然后我们进入配置文件
关于bean标签和依赖注入的细节在下面:
依赖注入使用的是下面依赖注入方法中的set方法
用TransactionTemplate手动管理事务
<<<<<<<<<<<<<<<<xml配置中的细节<<<<<<<<<<<<<<<<
bean标签把对象的创建交给spring来管理
spring对bean的管理细节
1、创建bean的三种方式
2、bean对象的作用范围
3、bean对象的生命周期
创建bean的三种方式
a.使用默认的构造函数创建
<!--此种方式是:
在spring配置文件中使用bean标签,配以id与class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,对象则无法创建。
-->
<!--<bean id="accountService" class="com.lky.service.impl.AccountServiceImpl"></bean>-->
b.使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存取容器)
<!-- 此种方式是:
先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<!--<bean id="instanceFactory" class="com.lky.factory.InstanceFactory"></bean>-->
<!--<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>-->
c.使用静态工厂中的静态方法创建对象(使用摸个类中的静态方法创建对象,并存入容器)
<!-- 此种方式是:
使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
-->
<!--<bean id="accountService" class="com.lky.factory.StaticFactory" factory-method="getAccountService"></bean>—>
bean的作用范围调整(spring对象默认为单例的)
<!--
bean标签中的scope属性:
作用:用于指定bean的作用范围
取值:
singleton:单例,默认值
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,就是session
-->
<!--<bean id="accountService" class="com.lky.service.impl.AccountServiceImpl" scope="prototype"></bean>—>
bean对象的生命周期
<!--
单例对象
出生:容器创建时
活着:容器还在
死亡:容器销毁
多例对象
出生:使用对象时,spring为我们创建
活着:对象在使用就活着
死亡:当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收
—>
sprig中的依赖注入
<!--
依赖注入:
Dependency Injection
IOC的作用:
降低程序间的耦合(依赖关系)
依赖关系的管理:
都交给spring来维护
当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:
称之为依赖注入
依赖注入:
能注入的数据有3类:
基本类型和String
其他bean类型(在配置文件中或注解配置过的bean)
复杂类型/集合类型
注入的方式:
1。使用构造函数提供
2。使用set方法
3。使用注解提供
—>
<!--构造函数注入
使用的标签:constructor-arg
出现的位置:bean标签内部
标签中的属性
type:用于指定要注入的数据的类型,该数据类型也是构造函数中某个或某些参数的类型(无法区分多个,所以不能独立实现)
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,从0开始(可以独立实现,但必须清楚类型)
name:用于指定给构造函数中指定名称的参数赋值(常用)
=============以上3个用于指定给构造函数中的参数赋值=================================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型的数据。指的是spring的IOC核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据时必须的操作,否则对象无法创建成功。
缺点:
改变了bean对象的实例化方式,使创建对象时,如果不使用这些数据,也必须提供。
-->
<bean id="accountService" class="com.lky.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="柯"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="time"></constructor-arg>
</bean>
<!--配置一个时间对象-->
<bean id="time" class="java.util.Date"></bean>
<!--set方法注入 更常用
使用的标签:property
出现的位置:bean标签内部
标签中的属性:
name:用于指定给构造函数中指定名称的参数赋值(常用),从set方法中寻找
=============以上3个用于指定给构造函数中的参数赋值=================================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型的数据。指的是spring的IOC核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
缺点:
如果摸个成员必须有值,则获取对象时,set方法无法保证一定注入(可能不执行set),
-->
<bean id="accountService2" class="com.lky.service.impl.AccountServiceImpl2">
<property name="name" value="柯雨"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="time"></property>
</bean>
<!--复杂类型的注入,集合类型
用于给list集合注入的标签有:
list array set
用于给Map集合注入的标签有:
map props
结构相同,标签可以互换
-->
<bean id="accountService3" class="com.lky.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="a" value="AAA"></entry>
<entry key="b">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="c">CCC</prop>
<prop key="d">DDD</prop>
</props>
</property>
</bean>
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
因为采用xml文件配置,所以类中都是基本代码。
重点帖一下服务层实现的部分,因为服务层进行着事务控制
spring编程式事务控制,通过使用transactionTemplate中的excute方法
excute在执行时会使用doInTransaction(status)的方法,并且在执行时,这个doInTransaction的部分实现了rollback,commit等事务操作。
因为通过代码来实现事务非常麻烦,增加开发难度,并且会增加业务层重复的代码。所以一般很少使用。
为什么使用银行转账为例子?
是因为银行转账实际上是进行事务控制,而事务控制是许多业务的必要部分。
持久层connection 对象的 setAutoCommit(true)方法会使单条sql语句的事务得到成功的执行,但如果业务层同时执行多个持久层方法,并且在中间产生了异常,业务层的这次调用在不进行处理的情况下是无法进行整体自动回滚的,他会执行能成功的语句,回滚失败的语句。此时我们的银行转账会出现严重的问题。我们也可以是用try catch finally的方式去处理,但是因为许多业务都涉及了事务控制,这样去处理,重复的代码量极大,是会增加开发成本的。
如果解决代码重复的问题?
动态代理,我们可以通过动态代理,使需要进行事务控制的方法经过代理类,从而实现事务控制。
而动态代理的实现方式有两种,基于接口和基于子类。
而在spring中,通过配置,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
这这种解决上述问题的spring配置,称之为AOP
<<<<<<<<<<<这里是aop的相关术语解释<<<<<<<<<<<
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
三、基于AOP方式的声明式事务管理的配置过程:
重复的部分不再赘述
首先是执行的测试类
然后是读取的bean.xml配置文件
这里的spring关于aop的配置约束可以在spring官网的Core包中找到
首先配置事务管理器
然后配置事务的通知(增强)去引用事务管理器,而这个通知,
就是公共重复的代码部分,也是我们之前想通过动态代理去增强的部分。
对转账方法设定了一定有事务的事务传播行为
对所有的find开头方法进行了查询方式的事务增强
切入点代表我们对业务层实现下的所有方法(连接点)进行拦截
然后把切入点与上述的通知建立关系。
即通过使用AOP的方式实现了事务的控制。