BeanFactory 和 ApplicationContext 的区别
BeanFacotry 是 spring 中比较原始的 Factory。如 XMLBeanFactory 就是一种典型的 BeanFactory。原始的 BeanFactory 无法支持 spring的许多插件,如 AOP 功能、Web 应用等。
ApplicationContext 接口,它由 BeanFactory 接口派生而来,ApplicationContext 包含 BeanFactory 的所有功能,通常建议比 BeanFactory 优先 BeanFactory 是不支持国际化功能的,因为 BeanFactory 没有扩展 Spring 中 MessageResource 接口。相反,由于 ApplicationContext 扩展了 MessageResource 接口,因而具有消息处理的能力(i18N)。
ApplicationContext 的事件机制主要通过 ApplicationEvent 和 ApplicationListener 这两个接口来提供的,当 ApplicationContext 中发布一个事件的时,所有扩展了 ApplicationListener 的 Bean 都将会接受到这个事件,并进行相应的处理。
ApplicationContext 扩展了 ResourceLoader (资源加载器)接口,从而可以用来加载多个 Resource,而 BeanFactory 是没有扩展 ResourceLoader。
与 BeanFactory 通常以编程的方式被创建不同的是,ApplicationContext 能以声明的方式创建,如使用 ContextLoader。当然你也可以使用 ApplicationContext 的实现之一来以编程的方式创建 ApplicationContext 实例 。
BeanFactroy 采用的是延迟加载形式来注入 Bean 的,这样,我们就不能发现一些存在的 spring 的配置问题。而 ApplicationContext 则相反,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误。
BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。
BeanFactory 和 FactoryBean 区别
BeanFactory,以 Factory 结尾,表示它是一个工厂类(接口), 它负责生产和管理 bean 的一个工厂。在 Spring 中,BeanFactory 是 IOC 容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
一般情况下,Spring 通过反射机制利用 < bean > 的 class 属性指定实现类实例化 Bean,在某些情况下,实例化 Bean 过程比较复杂,如果按照传统的方式,则需要在 < bean > 中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 org.springframework.bean.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 Bean 的逻辑。FactoryBean 接口对于 Spring 框架来说占用重要的地位,Spring 自身就提供了70多个 FactoryBean 的实现。它们隐藏了实例化一些复杂 Bean 的细节,给上层应用带来了便利。从 Spring3.0 开始,FactoryBean 开始支持泛型,即接口声明改为 FactoryBean< T > 的形式
以 Bean 结尾,表示它是一个 Bean,不同于普通 Bean 的是:它是实现了 FactoryBean
Spring Bean
Spring Bean 的作用域
singleton,当一个 bean 的作用域为 singleton , 那么 Spring IoC 容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例。
prototype,Prototype 作用域的 bean 会导致在每次对该bean请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的getBean() 方法)时都会创建一个新的 bean 实例。根据经验,对所有有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域
request,在一次 HTTP 请求中,一个 bean 定义对应一个实例;即每次 HTTP 请求将会有各自的 bean 实例, 它们依据某个 bean 定义创建而成。该作用 域仅在基于 web 的 Spring ApplicationContext 情形下有效。
session,在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
global session,在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。典型情况下,仅在使用 portlet context 的时候有效。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当销毁容器时,对象就被销毁了。
一句话总结:单例模式的 bean 对象生命周期与容器相同。
多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象⻓时间不用时,被 java 的垃圾回收器回收了。
一句话总结:多例模式的 bean 对象,spring 框架只负责创建,不负责销毁。
Spring Bean 的生命周期
Spring bean factory 负责管理在 spring 容器中被创建的 bean 的生命周期。Bean 的生命周期由两组回调(call back)方法(初始化之后调用的回调方法;销毁之前调用的回调方法)组成。
Spring 框架提供了以下四种方式来管理 bean 的生命周期事件:
InitializingBean 和 DisposableBean 回调接口
针对特殊行为的其他 Aware 接口
Bean 配置文件中的 Custom init()方法和 destroy()方法
@PostConstruct 和@PreDestroy 注解方式
①instantiate bean对象实例化(根据配置情况调用Bean构造方法或工厂方法实例化Bean)
②populate properties 封装属性(利用依赖注入完成Bean中所有属性的配置注入)
③如果 Bean 实现 BeanNameAware 执行 setBeanName
④如果 Bean 实现 BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext
⑤如果存在类实现 BeanPostProcessor(后处理Bean) ,执行 postProcessBeforeInitialization,BeanPostProcessor 接口提供钩子函数,用来动态扩展修改 Bean。
publicclassMyBeanPostProcessorimplementsBeanPostProcessor{
publicObject postProcessAfterInitialization(Object bean,String beanName)
throwsBeansException{
// 后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。
System.out.println("第八步:后处理Bean,after初始化。");
// 返回bean,表示没有修改,如果使用动态代理,返回代理对象,那么就修改了。
return bean;
}
publicObject postProcessBeforeInitialization(Object bean,String beanName)
throwsBeansException{
// 后处理Bean,在这里加上一个动态代理,就把这个Bean给修改了。
System.out.println("第五步:后处理Bean的:before初始化!!");
// 返回bean本身,表示没有修改。
return bean;
}
}
注意:这个前处理 Bean 和后处理 Bean 会对所有的 Bean 进行拦截。
⑥如果 Bean 实现 InitializingBean 执行 afterPropertiesSet
⑦调用 < bean init-method = "init”> 指定初始化方法 init
⑧如果存在类实现 BeanPostProcessor(处理 Bean ) ,执行 postProcessAfterInitialization
⑨执行业务处理
⑩如果Bean实现 DisposableBean 执行 destroy
⑪调用< bean destroy-method = "customerDestroy”> 指定销毁方法 customerDestroy
Spring Bean 实例化方式
第一种方式:无参构造器,另外两种方式,是为了自己 new 的对象加入到 springIOC 容器中
第二种方式:静态方法
在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供一个创建对象的方法,恰好这个方法是 static 修饰的 方法,即是此种情 况。
例如,我们在做 Jdbc 操作时,会用到 java.sql.Connection 接口的实现类,如果是mysql数据库,那么用的就是 JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection() ,因为我们要注册驱动,还要提供URL和凭证信息,用 DriverManager.getConnection 方法来获取连接。
第三种方式:使用实例化方法创建
此种方式和上面静态方法创建其实类似,区别是用于获取对象的方法不再是static修饰的了,而是 类中的一 个普通方法。此种方式比静态方法创建的使用几率要高一些。
<!--Spring ioc 实例化Bean的三种方式-->
<!--方式一:使用无参构造器(推荐)-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
<!--方式二:静态方法-->
<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
<!--方式三:实例化方法-->
<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory"></bean> <bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"/>
Spring 自动装载
no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系。
byName:该选项可以根据 bean 名称设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
byType:该选项可以根据bean类型设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
constructor:造器的自动装配和 byType 模式类似,但是仅仅适用于与有构造器相同参数的 bean ,如果在容器中没有找到与构造器参数类型一致的 bean,那么将会抛出异常。
autodetect:该模式自动探测使用构造器自动装配或者 byType 自动装配。首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在 bean 内部没有找到相应的构造器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式。
Spring 拦截器
在 springmvc 中,定义拦截器要实现 HandlerInterceptor 接口,并实现该接口中提供的三个方法,
// 测试拦截器1
public class HandlerInterceptor1 implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("HandlerInterceptor1....preHandle");
// false表示拦截,不向下执行;true表示放行
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor1....postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("HandlerInterceptor1....afterCompletion");
}
}
preHandle方法:进入 Handler 方法之前执行。可以用于身份认证、身份授权。比如如果认证没有通过表示用户没有登陆,需要此方法拦截不再往下执行(return false),否则就放行(return true)。
postHandle 方法:进入 Handler 方法之后,返回 ModelAndView 之前执行。可以看到该方法中有个 modelAndView 的形参。应用场景:从 modelAndView 出发:将公用的模型数据(比如菜单导航之类的)在这里传到视图,也可以在这里同一指定视图。
afterCompletion 方法:执行 Handler 完成之后执行。应用场景:统一异常处理,统一日志处理等。
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 多个拦截器,按顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/> <!-- 表示拦截所有的url包括子url路径 -->
<bean class="ssm.interceptor.HandlerInterceptor1"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="ssm.interceptor.HandlerInterceptor2"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="ssm.interceptor.HandlerInterceptor3"/>
</mvc:interceptor>
</mvc:interceptors>
preHandle 按拦截器定义顺序调用
postHandler 按拦截器定义逆序调用
afterCompletion 按拦截器定义逆序调用
postHandler 在拦截器链内所有拦截器返成功调用
afterCompletion 只有 preHandle 返回 true 才调用
Spring 动态代理
Spirng 当中动态代理有两种
JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理,需要指定一个类加载器,然后生成的代理对象实现类的接口或类的类型,接着处理额外功能。JDK 是基于接口。
Cglib 是动态代理利用 asm 的开源包,对代理对象的 Class 文件加载进来,通过修改其字节码生成的子类来处理,Cglib 是基于继承父类生成的代理类。
在 Spirng 当中动态代理的使用
1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理来实现AOP
2.如果目标对象实现了接口,也可以强制使用CGlib来实现AOP
3.如果目标对象没有实现接口,必须采用Cglib库,Spirng会自动在JDK和CGlib用切换
如何强制使用 CGlib 来实现 AOP?
添加 CGlibjar 包:SPRING_HOME/cglib/*.jar
在 Spring 的配置文件中加入 < aop:config proxy-target-class=”true” > 默认是false 也就是用 JDK 的,改为 true 就是用 Cglib 的
JDK 和动态代理和 CGlib 字节码的生成区别?
JDK 动态代理制能对实现了接口的类生成代理,而不是针对类
CGLIB 是针对类实现代理,主要对指定的类生成一个子类,覆盖其中的方法,添加额外功能,因为是继承,所以该类方法不能用 final 来声明.
懒加载机制实现
普通 Bean 的初始化是在容器启动初始化阶段执行的,而被 lazy-init = true 修饰的 bean 则是在从容器里 第一次进行 context.getBean() 时进行触发。Spring 启动的时候会把所有 bean 信息(包括 XML 和注解)解析转化成 Spring 能够识别的 BeanDefinition 并存到 Hashmap 里供下面的初始化时用,然后对每个 BeanDefinition 进行处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进 行初始化并依赖注入。
对于被修饰为 lazy-init 的 bean Spring 容器初始化阶段不会进行 init 并且依赖注入,当第一次进行 getBean 时候才进行初始化并依赖注入对于非懒加载的 bean,getBean 的时候会从缓存里头获取,因为容器初始化阶段 Bean 已经 初始化完成并缓存了起来。
IOC 和 DI 区别
IoC Inverse of Control 反转控制的概念,就是将原本在程序中手动创建 UserService 对象的控制权,交由 Spring 框架管理,简单说,就是创建 UserService 对象控制权被反转到了 Spring 框架
DI:Dependency Injection 依赖注入,在 Spring 框架负责创建 Bean 对象时,动态的将依赖对象注入到 Bean 组件
IoC 控制反转,指将对象的创建权,反转到 Spring 容器 , DI 依赖注入,指 Spring 创建对象的过程中,将对象依赖属性通过配置进行注入
IOC 和 DI 描述的是同一件事情(对象实例化及依赖关系维护这件事情),只不过⻆度不一样罢了。IOC 是站在对象的角度上,对象实例化及其管理的权利交给了(反转给了)容器。
DI 是在容器的角度上,容器会把对象依赖的其他对象注入。
IOC 是负责接下我们派送给它解耦等的任务,IOC 就会让 DI 实际去完成这些任务。
Spring循环依赖问题
循环依赖:
循环引用,也就是两个或者两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 C,C 又依赖于 A
Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖
其中,构造器的循环依赖问题无法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring 采用的是提前暴露对象的方法。
Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。(但是构造器必须是在获取引用之前)
Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多 bean 的依赖属性进行填充
(3)initializeBean:调用 spring xml 中的 init 方法。
从上面单例 bean 的初始化可以知道:循环依赖主要发生在第一、二步,也就是构造器循环依赖和 field 循环依赖。那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在 Spring 容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在 Cache 中,Spring 为了解决单例的循环依赖问题,使用了三级缓存。
三级缓存分别指:
singletonFactories :单例对象工厂的cache
earlySingletonObjects :提前暴光的单例对象的Cache
singletonObjects:单例对象的cache
在创建 bean 的时候,首先想到的是从 cache 中获取这个单例的 bean,这个缓存就是 singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存 earlySingletonObjects 中获取。如果还是获取不到且允许 singletonFactories 通过 getObject() 获取,就从三级缓存singletonFactory.getObject() (三级缓存)获取,如果获取到了则:从 singletonFactories 中移除,并放入 earlySingletonObjects 中。其实也就是从三级缓存移动到了二级缓存。
从上面三级缓存的分析,我们可以知道,Spring 解决循环依赖的诀窍就在于 singletonFactories 这个三级 cache。这个 cache 的类型是 ObjectFactory。这里就是解决循环依赖的关键,发生在 createBeanInstance 之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以 Spring 此时将这个对象提前曝光出来让大家认识,让大家使用。
这样做有什么好处呢?让我们来分析一下“A 的某个 field 或者 setter 依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 依赖了 A 的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中,此时进行初始化的第二步,发现自己依赖对象 B,此时就尝试去 get(B),发现B还没有被 create,所以走 create 流程,B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get(A),尝试一级缓存 singletonObjects (肯定没有,因为A还没初始化完全),尝试二级缓存 earlySingletonObjects(也没有),尝试三级缓存 singletonFactories,由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象(虽然 A 还没有初始化完全,但是总比没有好呀),B 拿到 A 对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。此时返回 A 中,A 此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存 singletonObjects 中,而且更加幸运的是,由于 B 拿到了 A 的对象引用,所以 B 现在 hold 住的 A 对象完成了初始化。
对于“prototype”作用域 Bean,Spring 容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的 Bean。
知道了这个原理时候,肯定就知道为啥 Spring 不能解决“A 的构造方法中依赖了 B 的实例对象,同时 B 的构造方法中依赖了 A 的实例对象”这类问题了!因为加入 singletonFactories 三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

基于setter属性的循环依赖
Spring 先是用构造实例化 Bean 对象 ,创建成功后,Spring 会提前将对象暴露出来,此时的对象 A 还没有完成属性注入,属于早期对象,此时 Spring 会将这个实例化结束的对象放到一个 Map 中,并且 Spring 提供了获取这个未设置属性的实例化对象引用的方法。结合我们的实例来看,当 Spring 实例化了A、B、C后,紧接着会去设置对象的属性,此时 A 依赖 B,就会去 Map 中取出存在里面的单例B对象,以此类推,不会出来循环的问题
基于构造器的循环依赖
Spring 容器会将每一个正在创建的 Bean 标识符放在一个“当前创建 Bean 池”中,Bean 标识符在创建过程中将一直保持在这个池中,因此如果在创建 Bean 过程中发现自己已经在“当前创建 Bean 池”里时将抛出 BeanCurrentlyInCreationException 异常表示循环依赖;而对于创建完毕的 Bean 将从“当前创建Bean池”中清除掉。
Spring 容器先创建单例 A,A 依赖 B,然后将 A 放在“当前创建 Bean 池”中,此时创建 B,B 依赖 C ,然后将 B 放在“当前创建 Bean 池”中,此时创建 C,C 又依赖 A, 但是,此时 A 已经在池中,所以会报错,,因为在池中的 Bean 都是未初始化完的,所以会依赖错误 ,(初始化完的 Bean 会从池中移除)
AOP
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点 总是 代表一个方法的执行。通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。
通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
切入点(Pointcut):匹配连接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
目标对象(Target Object):被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。注意:Spring 2.0最新引入的基于模式(schema-based)风格和@AspectJ注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。
织入(Weaving):把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
spring的事务
事务的特性
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
Spring事务的配置方式
Spring支持编程式事务管理以及声明式事务管理两种方式。
编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。声明式事务管理
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
事务的传播机制
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
常用的事务传播机制如下:
PROPAGATION_REQUIRED
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行PROPAGATION_REQUES_NEW
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可PROPAGATION_SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务PROPAGATION_NOT_SUPPORT
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码PROPAGATION_NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常PROPAGATION_MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常PROPAGATION_NESTED
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
事务的隔离级别
事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。
在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:
脏读(Dirty read)
脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。
不可重复读重点在修改。幻读(Phantom reads)
幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。
幻读重点在新增或删除。
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。
ISOLATION_READ_COMMITTED (Oracle 默认级别)允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。
ISOLATION_REPEATABLE_READ (MYSQL默认级别)对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻读仍可能发生。
ISOLATION_SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。
只读
如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
事务超时
为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。
假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。
由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。
回滚规则
在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。
不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
Spring声明式事务配置参考
事物配置中有哪些属性可以配置?以下只是简单的使用参考
事务的传播性:
@Transactional(propagation=Propagation.REQUIRED)事务的隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用只读:
@Transactional(readOnly=true)
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。事务的超时性:
@Transactional(timeout=30)回滚:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。
常用的注解
使用注解之前要开启自动扫描功能,其中base-package为需要扫描的包(含子包)。
@Component组件,没有明确的角色(可以指定bean的id名:Component(“yourBeanName”),同时,Spring支持将@Named作为@Component注解的替代方案。@Named可以有值,如果没有值生成的Bean名称默认和类名相同。)
@Service 在业务逻辑层使用(bean名默认小写,可以指定:@Service(“Zoo”),@Scope(“prototype”))
@Repository 在数据访问层使用,即DAO组件。
@Controller 在展现层使用(MVC -> Spring MVC)使用(如果@Controller不指定其value【@Controller】,则默认的bean名字为这个类的类名首字母小写,如果指定value【@Controller(value=”UserAction”)】或者【@Controller(“UserAction”)】,则使用value作为bean的名字。)
@Autowired 自动装配,其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property
@Autowired注解的required属性设置为false时,Spring容器不再抛出异常而是认为属性为null。
@Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。
@Qualifier(“beanName”),多个实现类时配合@Autowired使用来指定Bean的名称。
@Resource注解与@Autowired注解作用非常相似,
@Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配
指定了name或者type则根据指定的类型去匹配bean
指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错
@Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
@Autowired是Spring的注解,@Resource是J2EE的注解,Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。
@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
@Scope注解 作用域
@Lazy(true) 表示延迟初始化
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)
@DependsOn:定义Bean初始化及销毁时的顺序;
@DependsOn用于强制初始化其他Bean。可以修饰Bean类或方法,使用该Annotation时可以指定一个字符串数组作为参数,每个数组元素对应于一个强制初始化的Bean。(@DependsOn({“steelAxe”,”abc”}) @Component )
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@PostConstruct 初始化注解
@PreDestroy 摧毁注解 默认 单例 启动就加载
@Async异步方法调用
@Conditional 它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。
借助getRegistry()返回的BeanDefinitionRegistry检查bean定义
借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;
借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
@Bean,@Component,@Configuration区别
@Component (@Controller @Service @Respository)作用于类上,只有在SpringBoot应用程序启用了组件扫描并且包含了被注解的类时才有效。通过组件扫描,Spring将扫描整个类路径,并将所有@Component注释类添加到Spring Context,这里有的不足就是会把整个类当成bean注册到spring 容器上,如果这个类中并不是所有方法都需要注册为bean的话,会出现不需要的方法都注册成为bean,这时候必须确保这些不需要的方法也能注册为bean或者在扫描中加filter 过滤这些不需要的bean,否者spring将无法成功启动。
@Bean相对来说就更加灵活了,它可以独立加在方法或者类上,按需注册到spring容器,而且如果你要用到第三方类库里面某个类或者方法的时候,你就只能用@Bean把这个类或者方法注册到spring容器,因为用@Component你需要配置组件扫描到这个第三方类路径而且还要在别人源代码加上这个注解,很明显是不现实的。
@Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。
Spring 容器在启动时,会加载默认的一些 PostPRocessor,其中就有 ConfigurationClassPostProcessor,这个后置处理程序专门处理带有 @Configuration 注解的类,这个程序会在 bean 定义加载完成后,在 bean 初始化前进行处理。主要处理的过程就是使用 cglib 动态代理增强类,而且是对其中带有 @Bean 注解的方法进行处理。
@Autowired,@Resource,@Qualifier区别
@Resource 和 @Autowired 都是做 bean 的注入时使用,都可以写在字段上,或者是 setter 方法上
@Autowired 注解是按照类型(byType)装配依赖对象(由 Spring 提供),默认情况下,它要求依赖对象必须存在,如果允许为 null,则设置他的 required 属性值为 false
如果在此基础上想通过 byName 来装配,可以结合 @Qualifier 注解来一起使用。
@Resource 默认按照 byName 自动注入
@Resource 有两个重要的属性,name 和 type, @Resource 如果写属性 name,则按照 byName 来装配,如果属性为 type,则按照 byType 来装配,不写按照默认的方式来装配(byName)
spring中的设计模式
简单工厂
又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种 GOF 设计模式之一。
Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。工厂方法(Factory Method)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
Spring中的 FactoryBean 就是典型的工厂方法模式。单例(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Spring 中的单例模式完成了后半句话,即提供了全局的访问点 BeanFactory。但没有从构造器级别去控制单例,这是因为 Spring 管理的是是任意的 Java 对象。适配器(Adapter)
将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
由于 Advisor 链需要的是 MethodInterceptor 对象,所以每一个 Advisor 中的 Advice 都要适配成对应的 MethodInterceptor 对象。包装器(Decorator)
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵活。
Spring 中用到的包装器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有 Decorator。基本上都是动态地给一个对象添加一些额外的职责。代理(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。
从结构上来看和 Decorator 模式类似,但 Proxy 是控制,更像是一种对功能的限制,而 Decorator 是增加职责。
Spring 的 Proxy 模式在 aop 中有体现,比如 JdkDynamicAopProxy 和 Cglib2AopProxy。观察者(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
Spring中 Observer 模式常用的地方是 listener 的实现。如 ApplicationListener。策略(Strategy)
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
第一个地方,加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource 但他们都有共同的借口 Resource;
第二个地方就是在Aop的实现中,采用了两种不同的方式,JDK 动态代理和 CGLIB 代理;
第三个地方就是Spring的事务管理,PlatformTransactionManager 代表事务管理接口,但是它不知道底层如何管理事务,它只要求事务管理
提供开始事务( getTransaction(), commit(), rollback()三个方法,但是如何实现则交给具体实现类来完成—不同的实现类代表不同的事务管理策略。
一般来说,spring事务管理下面主要针对
1) JDBC(org.springframework.jdbc.datasource.DataSourceTransactionManager)
2) Hibernate (org.springframework.orm.hibernate3.HibernateTransactionManager)
3) JTA (org.springframework.transaction.jta.JtaTransactionManager)
4) JPA (org.springframework.orm.jpa.JpaTransactionManager)
四种具体的底层事务控制来包装的。模板方法(Template Method)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。Template Method模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。Spring中的JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。
Spring中的事件机制
Spring的ApplicationContext 提供了支持事件和代码中监听器的功能。
我们可以创建bean用来监听在ApplicationContext 中发布的事件。ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
public class AllApplicationEventListener implements ApplicationListener < ApplicationEvent >{
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
// process event
}
}
Spring 提供了以下5中标准的事件:
上下文更新事件(ContextRefreshedEvent):该事件会在 ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的 refresh() 方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用 ConfigurableApplicationContext 的 Start() 方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 Stop() 方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当 ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
请求处理事件(RequestHandledEvent):在 Web 应用中,当一个 http 请求(request)结束触发该事件。
除了上面介绍的事件以外,还可以通过扩展ApplicationEvent 类来开发自定义的事件。
public class CustomApplicationEvent extends ApplicationEvent{
public CustomApplicationEvent ( Object source, final String msg ){
super(source);
System.out.println("Created a Custom event");
}
}
为了监听这个事件,还需要创建一个监听器:
public class CustomEventListener implements ApplicationListener < CustomApplicationEvent >{
@Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
// handle event
}
}
之后通过 applicationContext 接口的 publishEvent() 方法来发布自定义事件。
CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);
SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决
是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段
SpringMVC怎么样设定重定向和转发的
在返回值前面加”forward:”就可以让结果转发,譬如”forward:user.do?name=method4” 在返回值前面加”redirect:”就可以让返回值重定向,譬如”redirect:http://www.baidu.com“
怎么样把ModelMap里面的数据放入Session里面
可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key
@ModelAtrribute:被ModelAttribute注释的方法会在此controller每个方法执行前被执行,因此对于一个controller映射多个URL的用法来说,要谨慎使用。
运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用;
运用在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中;
Spring MVC流程
1、用户发送请求至前端控制器 DispatcherServlet
2、DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3、处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4、DispatcherServlet 调用 HandlerAdapter 处理器适配器
5、HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller 执行完成返回 ModelAndView
7、HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet
8、DispatcherServle t将 ModelAndView 传给 ViewReslover 视图解析器
9、ViewReslover 解析后返回具体 View
10、DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet 响应用户




