源码辅助阅读: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-stackspring 框架,包括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思想

IoCInversionof Control(控制反转):读作“反转控制”更好理解,是一种设计思想。就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。

正控:若调用者需要使用某个对象,其自身就得负责该对象及该对象所依赖对象的创建和组装。

返控:调用者只管负责从Spring容器中获取需要使用的对象,不关心对象的创建过程,也不关心该对象依赖对象的创建以及依赖关系的组装,也就是把创建对象的控制权反转给了Spring框架。

DIDependency Injection(依赖注入)从字面上分析:IoC指将对象的创建权,反转给了Spring容器;DISpring创建对象的过程中,将对象依赖属性(常量,对象,集合)通过配置设置给该对象。

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

  1. BeanFactory需要等到获取某一个bean的时候才会创建bean,延迟初始化
  2. ApplicationContext在启动Spring容器的时候就会创建所有的beanWeb应用建议)

bean 实例化方式

  1. 构造器实例化(无参数构造器),最标准,使用最多
  2. 静态工厂方法实例化:解决系统遗留问题
  3. 实例工厂方法实例化:解决系统遗留问题
  4. 实现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使用requestStruts2中的Action使用prototype类型,其他使用singleton

 

DI

DI从字面上分析:IoC指将对象的创建权,反转给了Spring容器。

DISpring创建对象的过程中,将对象依赖属性(常量、对象、集合)通过配置设值给该对象。

通过XML设置装配

  1. XML自动装配(不推荐)
  2. setter注入(开发用的较多)

1,使用bean元素的<property>子元素设置

常量类型,直接使用value赋值;

引用类型,使用ref赋值;

集合类型,直接使用对应的集合类型元素即可。

2,spring通过属性的setter方法注入值

3,属性的设置值是在init方法执行之前完成的

  1. 构造器注入
  2. bean元素继承

多个bean元素共同配置的抽取,实则是bean配置的拷贝,和Java的继承不同。

Java的继承:把多个类共同的代码抽取到父类中。

bean元素的继承(inheritance):把多个bean元素共同的属性配置抽取到另一个公用的bean元素中。

  1. 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”)标签来规定依赖对象按照beanid和类型的组合方式去找;

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中的ActionSpringMVCController)。

此时需要配置IoC注解的解析器:

使用<context:component-scan base-package=""/>

表示去哪些包中及其子包中去扫描组件注解

 

作用域注解

@Scope简单点说就是用来指定bean的作用域

 

初始和销毁注解

@PostConstruct//构建的对象之后

public void open() {

  System.out.println("初始化方法");

}

@PreDestroy//销毁之前

public void close() {

  System.out.println("销毁前扫尾方法");

}

 

AOP

代理模式:

客户端直接使用的是代理对象,不知道真实对象是谁,此时代理对象可以在客户端和真实对象之间起到中介的作用。

  1. 代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象没有直接关系;
  2. 代理模式的职责:把不是真实对象该做的事情从真实对象上掀开--职责清晰;

 

静态代理:

在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。

优点:

  1. 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
  2. 把真实对象隐藏起来了,保护真实对象。

缺点:(解决:动态代理)

  1. 代理对象的某个接口只服务与某一种类型的对象,也就是说每一个真实对象都得创建一个代理对象。
  2. 如果需要代理的方法很多,则要为每一种方法都进行代理处理。
  3. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,代理类也需要实现此方法。

 

动态代理:

动态代理类是在程序运行期间由JVM通过反射等机制动态生成的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的。

如何实现动态代理:

1)针对有接口:使用JDK动态代理

2)针对无接口:使用CGLIBJavassist组件

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:当前调用方法的实参

返回:真实方法的返回结果

操作步骤:

  1. 实现InvocationHandler接口,创建自己增强代码的处理器。
  2. Proxy类提供ClassLoader对象和代理接口类型数组,创建动态代理对象。
  3. 在处理器中实现增强操作

CGLIB动态代理

1、 //创建一个代理对象

public <T> T getProxyObject(){

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(target.getClass());//将继承于哪一个类,去做增强

enhancer.setCallback(this);//设置增强的对象

return (T) enhancer.create();//创建代理对象

}

2org.springframework.cglib.proxy.InvocationHandler接口:public Object invoke(Object proxy,Method method,Object[] args)

拦截器思想

百度百科:java里的拦截器是动态拦截Action调用的对象。在AOPAspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。

拦截器与Filter过滤器很相似,Fileter就是对请求和响应做拦截。

FilterWeb领域的概念,只能针对于请求和响应做增强,离不开servlet-api.jar

Interceptor:整个Java领域的概念,不仅可以运用到servlet层,还可以用到Web

 

代理总结:

JDK动态代理总结:

1.JAVA动态代理是使用java.lang.reflect包中Proxy类与InvocationHandler接口这两个来完成的。

2.要使用JDK动态代理,委托必须要定义接口。

3.JDK动态代理将会拦截所有public的方法(因为只能调用接口中定义的方法),这样即使接口中添加了新的方法,不用修改代码也会被拦截。

4.动态代理的最小单位是类(所有类中的方法都会被处理),如果只想拦截一部分方法,可以在invoke方法中对要执行的方法名进行判断。

CGLIB代理总结:

  1. CGLIB可以生产委托类的子类,并重写父类非final修饰符的方法。
  2. 要求类不能是final的,要拦截的方法要是非final、非static、非private的。
  3. 动态代理的最小单位是类(所有类中的方法都会被处理)。

关于性能:

JDK动态代理是基于实现接口的,CGLIBJavassit是基于继承委托类的。

从性能上考虑: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语法

  1. 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

  1. 通配符:

*:匹配任何部分,只能表示一个单词

..:可用于全限定名中和方法参数中,分别表示子包和ON个参数

 

Spring持久层支持

 

 

Spring事务

何为数据库事务

事务是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务(Transaction/tx)

事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:

原子性(Atomicity):事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做。

一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据依然处于正确的状态,即数据完整性约束没有被破坏,如AB转账,不论转账成功与否,转账之后的AB的账户总额和转账之前是相同的。

隔离性(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默认就不支持,需要手动去开启。JdbcMyBatis的事务管理器: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>

 

 

 posted on 2019-05-01 00:35  tripleWen  阅读(243)  评论(0编辑  收藏  举报