源码辅助阅读:https://github.com/tripleDemo/spring.github.io
spring 技术概述
分层 一站式 java ee full-stack 轻量级框架
三层:
1:数据访问层(持久层):主要是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。
2:业务逻辑层:主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理。
3:界面层:主要表示WEB方式,也可以表示成WINFORM方式,WEB方式也可以表现成:aspx,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。
struts2 web:表现层的MVC框架
Hibernate:是数据访问层(持久化)的完整的orm框架
full-stack(spring 框架,包括javaee三层的每一层解决方案)
web:springmvc(servlet),springboot
业务层:bean管理AOP(面向切面,系统级功能,抽取)
持久化:jdbctemplate,ORM
特点:
1.方便解耦,简化开发
通过Spring提供的IoC容器,将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明事物的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5.方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
体系结构
IoC和DI思想
IoC:Inversionof Control(控制反转):读作“反转控制”更好理解,是一种设计思想。就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
正控:若调用者需要使用某个对象,其自身就得负责该对象及该对象所依赖对象的创建和组装。
返控:调用者只管负责从Spring容器中获取需要使用的对象,不关心对象的创建过程,也不关心该对象依赖对象的创建以及依赖关系的组装,也就是把创建对象的控制权反转给了Spring框架。
DI:Dependency Injection(依赖注入)从字面上分析:IoC指将对象的创建权,反转给了Spring容器;DI指Spring创建对象的过程中,将对象依赖属性(常量,对象,集合)通过配置设置给该对象。
IoC从字面意思上很难体现出谁来维护对象之间的关系,Martin Fowler提出一个新的概念DI,更明确描述了“被注入对象(Service对象)依赖IoC容器来配置依赖对象(Dao对象)”。
Spring IoC 容器(Container)
BeanFactory : Spring最底层的接口,只提供了的IoC功能,负责创建、组装、管理bean,在应用中,一般不使用BeanFactory,而推荐使用ApplicationContext(应用上下文)。
ApplicationContext接口继承了BeanFactory,除此之外还提供AOP集成、国际化处理、事件传播、统一资源加载等功能。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {}
bean的创建时机(此时不使用Spring Test)
- BeanFactory需要等到获取某一个bean的时候才会创建bean,延迟初始化
- ApplicationContext在启动Spring容器的时候就会创建所有的bean(Web应用建议)
bean 实例化方式
- 构造器实例化(无参数构造器),最标准,使用最多
- 静态工厂方法实例化:解决系统遗留问题
- 实例工厂方法实例化:解决系统遗留问题
- 实现FactoryBean接口实例化:实例工厂变种,如集成MyBatis框架使用:org.mybatis.spring.SqlSessionFactoryBean(掌握)
bean的作用域 : 在Spring容器中是指其创建的Bean对象相对于其他Bean对象的请求可见范围。<bean id=”” class=”” scope=”作用域”/>(在开发中主要使用scope=”singleton”、scope=”prototype”)
singleton : 单例,在Spring IoC容器中仅存在一个Bean实例(默认的scope)。
prototype : 多例,每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean():不会再容器启动时创建对象。
request : 用于web开发,将Bean放入request范围,request.setAttribute(“xxx”),在同一个request获得同一个 Bean。
session : 用于web开发,将Bean放入Session范围,在同一个Session获取同一个Bean。
globalSession : 一般用于Porlet应用环境,分布式系统存在全局session概念(单点登录),如果不是porlet环境,globalSession等同于Session。
application : Scopes a single bean definition to the lifecycle of a ServletContext.Only valid in the context of a web-aware Spring ApplicationContext.
websocket : Scopes a single bean definition to the lifecycle of a WebSocket.Only valid in the context of a web-aware Spring ApplicationContext.
总结:对于Struts1中的Action使用request,Struts2中的Action使用prototype类型,其他使用singleton
DI
DI从字面上分析:IoC指将对象的创建权,反转给了Spring容器。
DI指Spring创建对象的过程中,将对象依赖属性(常量、对象、集合)通过配置设值给该对象。
通过XML设置装配
- XML自动装配(不推荐)
- setter注入(开发用的较多)
1,使用bean元素的<property>子元素设置
常量类型,直接使用value赋值;
引用类型,使用ref赋值;
集合类型,直接使用对应的集合类型元素即可。
2,spring通过属性的setter方法注入值
3,属性的设置值是在init方法执行之前完成的
- 构造器注入
- bean元素继承
多个bean元素共同配置的抽取,实则是bean配置的拷贝,和Java的继承不同。
Java的继承:把多个类共同的代码抽取到父类中。
bean元素的继承(inheritance):把多个bean元素共同的属性配置抽取到另一个公用的bean元素中。
- property place holder
通过注解自动装配
DI注解
Autowired
1.可以让Spring自动的把属性需要的对象找出来,并注入到对象
2.可以贴在字段或者setter方法上面
3.可以同时注入多个对象
@Autowired
public void setter(OtherBean otherBean,OtherBean other2){}
4.可以注入一些Spring内置的重要对象,比如BeanFactory,ApplicationContext,ServletContext等;
5.默认情况下Autowired注解必须要能找到对应的对象,否则报错。通过required=false来避免这个问题:@Autowired(required=false)
6.第三方程序:Spring3.0之前,需要手动配置Autowired注解的解析程序:<context:annotation-config/>(在Web开发中必须配置)
7.Autowired注解寻找bean的方式:
1)首先按照依赖对象的类型找,如果找到,就使用setter或者字段直接注入;
2)如果在Spring上下文中找到多个匹配的类型,再按照名字去找,如果没有匹配报错;
3)可以通过使用@Qulifier(“other”)标签来规定依赖对象按照bean的id和类型的组合方式去找;
Resource
1.可以让Spring自动的把属性需要的对象找出来,并注入到对象
2.可以贴在字段或者setter方法上面
3.可以注入一些Spring内置的重要对象,比如BeanFactory,ApplicationContext,ServletContext等;
4.Resource注解必须要能找到对应的对象,否则报错。
5.第三方程序:Spring3.0之前,需要手动配置Resource注解的解析程序:<context:annotation-config/>(在Web开发中必须配置)
6.Resource注解寻找bean的方式:
1)首先按照名字去找,如果找到,就使用setter或者字段注入;
2)如果按照名字找不到,再按照类型去找,但如果找到多个匹配类型,报错;
3)可以直接使用name属性指定bean的名称@Resource(name=“”),但是如果指定的name就只能按照name去找,如果找不到就不会再按照类型去找;
Value
...
IoC注解
bean组件版型:四个组件的功能是相同的,只是用于标注不同类型的组件。
@Component泛指组件,当组件不好归类的时候,可以使用这个注解进行标注。
@Repository用于标注数据访问组件,即DAO组件。
@Service用于标注业务层组件。
@Controller用于标注控制层组件(如struts中的Action,SpringMVC的Controller)。
此时需要配置IoC注解的解析器:
使用<context:component-scan base-package=""/>
表示去哪些包中及其子包中去扫描组件注解
作用域注解
@Scope简单点说就是用来指定bean的作用域
初始和销毁注解
@PostConstruct//构建的对象之后
public void open() {
System.out.println("初始化方法");
}
@PreDestroy//销毁之前
public void close() {
System.out.println("销毁前扫尾方法");
}
AOP
代理模式:
客户端直接使用的是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。
- 代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;
- 代理模式的职责:把不是真实对象该做的事情从真实对象上掀开--职责清晰;
静态代理:
在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。
优点:
- 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
- 把真实对象隐藏起来了,保护真实对象。
缺点:(解决:动态代理)
- 代理对象的某个接口只服务与某一种类型的对象,也就是说每一个真实对象都得创建一个代理对象。
- 如果需要代理的方法很多,则要为每一种方法都进行代理处理。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。
动态代理:
动态代理类是在程序运行期间由JVM通过反射等机制动态生成的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的。
如何实现动态代理:
1)针对有接口:使用JDK动态代理
2)针对无接口:使用CGLIB或Javassist组件
JDK动态代理
JDK动态代理API分析:(必须要求真实对象是有接口)
1、java.lang.reflect.Proxy类:Java动态代理机制生成的所有动态代理类的父亲,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
主要方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
方法职责:为指定类加载器、一组接口及调用处理器生成动态代理类实例
参数:loader:类加载器,一般传递真实对象的类加载器
interfaces:代理类需要实现的接口
handler:代理对象如何做增强
返回:创建的代理对象
//创建一个代理对象
public <T> T getProxyObject(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),//类加载器,一般跟上真实对象的类加载器
target.getClass().getInterfaces(),//真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
this);//如何做事务增强的对象
}
2、java.lang.reflect.InvocationHandler接口:public Object invoke(Object proxy,Method method,Object[] args)
方法职责:负责集中处理动态代理类上的所有方法调用
参数:proxy:生成的代理对象
method:当前调用的真实方法对象
args:当前调用方法的实参
返回:真实方法的返回结果
操作步骤:
- 实现InvocationHandler接口,创建自己增强代码的处理器。
- 给Proxy类提供ClassLoader对象和代理接口类型数组,创建动态代理对象。
- 在处理器中实现增强操作
CGLIB动态代理
1、 //创建一个代理对象
public <T> T getProxyObject(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());//将继承于哪一个类,去做增强
enhancer.setCallback(this);//设置增强的对象
return (T) enhancer.create();//创建代理对象
}
2、org.springframework.cglib.proxy.InvocationHandler接口:public Object invoke(Object proxy,Method method,Object[] args)
拦截器思想
百度百科:java里的拦截器是动态拦截Action调用的对象。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。
拦截器与Filter过滤器很相似,Fileter就是对请求和响应做拦截。
Filter:Web领域的概念,只能针对于请求和响应做增强,离不开servlet-api.jar
Interceptor:整个Java领域的概念,不仅可以运用到servlet层,还可以用到Web层
代理总结:
JDK动态代理总结:
1.JAVA动态代理是使用java.lang.reflect包中Proxy类与InvocationHandler接口这两个来完成的。
2.要使用JDK动态代理,委托必须要定义接口。
3.JDK动态代理将会拦截所有public的方法(因为只能调用接口中定义的方法),这样即使接口中添加了新的方法,不用修改代码也会被拦截。
4.动态代理的最小单位是类(所有类中的方法都会被处理),如果只想拦截一部分方法,可以在invoke方法中对要执行的方法名进行判断。
CGLIB代理总结:
- CGLIB可以生产委托类的子类,并重写父类非final修饰符的方法。
- 要求类不能是final的,要拦截的方法要是非final、非static、非private的。
- 动态代理的最小单位是类(所有类中的方法都会被处理)。
关于性能:
JDK动态代理是基于实现接口的,CGLIB和Javassit是基于继承委托类的。
从性能上考虑:Javassit > CGLIB > JDK
Struts2的拦截器和Hibernate延迟加载对象,采用的是Javassit的方式。
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。
若委托类对象实现了干接口,优先选用JDK动态代理。
AOP思想
把一个个的横切关注点放到某个模块中去,称之为切面。那么每一个的切面都能影响业务的某一种功能,切面的目的就是功能增强,如日志切面就是一个横切关注点,应用中许多方法需要做日志记录的只需要插入日志的切面即可。这种面向切面编程的思想就是AOP思想。
行业术语
Joinpoint:连接点,被拦截到需要被增强的方法。where:去哪里做增强
Pointcut:切入点,哪些包中的哪些类中的哪些方法,可认为是连接点的集合。where:去哪些地方做增强
Advice:增强,当拦截到Joinpoint之后,在方法执行的什么时机(when)做什么样(what)的增强。根据时机分为:前置增强、后置增强、异常增强、最终增强、环绕增强
public void doWork() {
//前置增强
try {
//业务代码
//后置增强
} catch(Exception e) {
//异常处理
//异常增强
} finally {
//释放资源
//最终增强
}
}
Aspect:切面,Pointcut+Advice,去哪些地方+在什么时候+做什么增强
Target:目标对象,被代理的目标对象
Weaving:织入,把Advice加到Target上之后,创建出Proxy对象的过程
Proxy:一个类被AOP织入增强后,产生的代理类
Pointcut语法
- AspectJ是一个面向切面的框架:
AspectJ切入点语法如下(表示在哪些包下的哪些类的哪些方法上做切入增强):
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
翻译成中文:execution(<修饰符>? <返回类型> <声明类型>? <方法名>(<参数>) <异常>?)
举例:public static Class java.lang.Class.forName(String className)throws ClassNotFoundException
- 通配符:
*:匹配任何部分,只能表示一个单词
..:可用于全限定名中和方法参数中,分别表示子包和O到N个参数
Spring持久层支持
Spring事务
何为数据库事务
事务是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务(Transaction/tx)。
事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:
原子性(Atomicity):事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做。
一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据依然处于正确的状态,即数据完整性约束没有被破坏,如A给B转账,不论转账成功与否,转账之后的A和B的账户总额和转账之前是相同的。
隔离性(Isolation):当多个事务处于并发访问同一个数据库资源时,事务之间相互影响程度,不同的隔离级别决定了各个事务对数据资源访问的不同行为。
持久性(Durability):事务一旦执行成功,它对数据库的数据的改变是不可逆的。
数据库并发问题
并发问题类型 |
产生原因和效果 |
第一类丢失更新 |
两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚 |
脏读 |
第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,但第一个事务回滚,第二个事务操作脏数据 |
虚读(幻读) |
一个事务查询到了另一个事务已经提交的新数据,导致多次查询数据不一样 |
不可重复读 |
一个事务查询到另一事务已经修改的数据,导致多次查询数据不一致 |
第二类丢失更新 |
多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变 |
事务的隔离级别
为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:
READ UNCOMMITED < READ COMMITED < REPEATABLE READ < SERIALIZABLE
隔离级别 |
脏读 |
不可重复读 |
幻读 |
第一类丢失更新 |
第二类丢失更新 |
READ UNCOMMITED |
√ |
√ |
√ |
× |
√ |
READ COMMITED |
× |
√ |
√ |
√ |
√ |
REPEATABLE READ |
× |
× |
√ |
√ |
√ |
SERIALIZABLE |
× |
× |
× |
× |
× |
默认情况下:MySQL不会出现幻读,除非:select * from 表名 lock in share mode;
MySQL中锁基于索引机制,也不会出现第一类丢失更新。
Oracle支持READ COMMITED(缺省)和SERIALIZABLE。
MySQL支持四种隔离级别,缺省为REPEATABLE READ。
如何选用:
隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少,因此在实际项目开发中为了考虑并发性能一般使用READ COMMITED。它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,更多的情况下使用悲观锁或乐观锁来解决。
(悲观锁:FOR UPDATE。乐观锁:定义一个列,来表示每次修改的版本。)
事务的类型
本地事务和分布式事务:
本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上;
分布式事务:涉及多个数据库源的事务,及跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;
JDBC事务和JTA事务:
JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务;
JTA事务:JTA指(Java Transaction API),是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商提供实现,JTA事务比JDBC更强大,支持分布式事务。
按是否通过编程实现事务有声明式事务和编程式事务:
编程式事务:通过编写代码来管理事务。
声明式事务:通过注解或XML配置来管理事务。
Spring事务管理
Spring的事务管理主要包括3个接口:
PlatformTransactionManager:平台的事务管理器,是多种事务管理器的基类,涵盖了处理事务的方法。根据TransactionDefinition提供的事务属性配置信息,创建事务。
TransactionDefinition:封装事务的隔离级别和超时时间,是否为只读事务和事务的隔离级别和传播规则等事务属性。
TransactionStatus:封装了事务的具体运行状态。如是否是新开启事务,是否已经提交事务,设置当前事务为rollback-only等。
事务传播规则
事务是如何在这些方法间传播的,Spring共支持7种传播行为:
情况一:需要遵从当前事务
REQUIRED:必须存在有一个事务,如果当前存在一个事务,则加入到该事务中,否则,新建一个事务,使用比较多的情况。
SUPPORTS:支持当前事务,如果当前存在事务,则使用该事务,否则,以非事务形式运行。
MANDATORY:必须要存在事务,如果当存在事务,就使用该事务,否则,非法的事务状态异常(IllegalTranactionStatusException)。
情况二:不遵从当前事务的
REQUIRES_NEW:不管当前是否存在事务,都会新开启一个事务,必须是一个新的事务,使用的也比较多。
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,把当前事务挂起(暂停)。
NEVER:不支持事务,如果当前存在事务,抛出一个异常。
情况三:寄生事务(外部事务/内部事务/嵌套事务)
NESTED:寄生事务,如果当前存在事务,则在内部事务内执行;如果当前不存在事务,则创建一个新的事务。寄生事务可以通过数据库savePoint(保存点)来实现,寄生事务可以回滚的,但是他的回滚不影响外部事务,但是外部事务的回滚会影响寄生事务。
寄生事务并不是所有的事务管理器都支持,比如HibernateTransactionManager默认就不支持,需要手动去开启。Jdbc和MyBatis的事务管理器:DataSourceTransactionManager:默认就是支持的。
事务配置
<!-- 配置一个CRUD的通用事务的配置 -->
<tx:advice id="crudAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- service中的查询方法 -->
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<tx:method name="list*" read-only="true" propagation="REQUIRED"/>
<tx:method name="query*" read-only="true" propagation="REQUIRED"/>
<!-- service中其他方法(非查询) -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>