Spring中的设计模式
控制反转(原则)
-
依赖注入:实现控制反转的一种设计模式
-
将想要创建对象的类(Person.class)和其依赖交给容器,让容器帮你创建对象
工厂模式
-
创建bean对象
-
BeanFactory
- 延迟注入(使用到某个 bean 的时候才会注入)
- 占用内存少,程序启动速度快
-
ApplicationContext
-
容器启动时一次性创建所有bean
-
是BeanFactory的扩展
-
一般使用
-
实现类
ClassPathXmlApplication
:把上下文文件当成类路径资源。FileSystemXmlApplication
:从文件系统中的 XML 文件载入上下文定义信息。XmlWebApplicationContext
:从Web系统中的XML文件载入上下文定义信息。
-
-
单例模式
-
类只有一个实例
-
优点:
- 对于频繁使用的对象可以节省创建对象的时间
- 减轻GC压力、缩短GC停顿的时间
-
是Spring 中 bean 的默认作用域
-
实现方式
- xml :
<bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
- 注解:
@Scope(value = "singleton")
- xml :
代理模式(在Spring中为AOP的运用)
-
将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来
-
优点
- 减少系统的重复代码
- 降低模块间的耦合度
- 有利于未来的可拓展性和可维护性。
-
实现(动态代理的方式)
- 若对象实现了接口:JDK Proxy(JDK 字节码增强技术)
- 若对象无接口:Cglib
- 生成被代理的子类
Spring AOP 和 AspectJ AOP 有什么区别?
1、Spring AOP 属于运行时增强,而 AspectJ 是编译时增强
2、Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)
3、Spring AOP 已经集成了 AspectJ
Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器
4、AspectJ 相对来说功能更加强大, Spring AOP 相对来说更简单
5、AspectJ 在有很多切面的情况下比Spring AOP快很多
6、Spring AOP 属于动态织入,而 AspectJ 是静态织入
模板方法
- 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中
- 优点:类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式
- Spring使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性
观察者模式
- 它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应
事件驱动模型
-
事件角色
- ApplicationEvent
- 抽象类
- 实现
ContextStartedEvent
:ApplicationContext
启动后触发的事件;ContextStoppedEvent
:ApplicationContext
停止后触发的事件;ContextRefreshedEvent
:ApplicationContext
初始化或刷新完成后触发的事件;ContextClosedEvent
:ApplicationContext
关闭后触发的事件。
- ApplicationEvent
-
事件监听者角色
- ApplicationListener
- 接口
- 该接口中的事件需要实现了
ApplicationEvent
- ApplicationListener
-
事件发布者角色
- ApplicationEventPublisher
- 接口
- 通过
ApplicationEventMulticaster
来广播出去的
- ApplicationEventPublisher
Spring 的事件流程总结
- 定义一个事件: 实现一个继承自
ApplicationEvent
,并且写相应的构造函数; - 定义一个事件监听者:实现
ApplicationListener
接口,重写onApplicationEvent()
方法; - 使用事件发布者发布消息: 可以通过
ApplicationEventPublisher
的publishEvent()
方法发布消息。
适配器模式
-
Spring AOP 的增强或通知(Advice)使用到了适配器模式
-
相关接口:AdvisorAdapter
-
Advice
-
常用类型
BeforeAdvice
(目标方法调用前,前置通知)- 拦截器:
MethodBeforeAdviceInterceptor
- 拦截器:
AfterAdvice
(目标方法调用后,后置通知)- 拦截器:
AfterReturningAdviceAdapter
- 拦截器:
AfterReturningAdvice
(目标方法执行结束后,return之前)- 拦截器:AfterReturningAdviceInterceptor
-
Spring预定义的通知要通过对应的适配器,适配成
MethodInterceptor
接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor
负责适配MethodBeforeAdvice
-
spring MVC中的适配器模式
在Spring MVC中,
DispatcherServlet
根据请求信息调用HandlerMapping
,解析请求对应的Handler
。解析到对应的Handler
(也就是我们平常说的Controller
控制器)后,开始由HandlerAdapter
适配器处理。HandlerAdapter
作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller
作为需要适配的类。
-
装修者模式
-
动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活
-
我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
Spring事物
事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的innodb
引擎。但是,如果把数据库引擎变为 myisam
,那么程序也就不再支持事务了
- 事物的四大特性
- 原子性
- 一致性
- 隔离性
- 持久性
Spring支持的两种事物管理方式
-
编程式事务管理
TransactionTemplate
或者TransactionManager
手动管理事务(try-catch)- 使用较少
-
声明式事务管理
-
使用AOP实现
-
代码入侵性小
-
基于
@Transactional
的全注解方式使用最多 -
推荐使用
-
相关接口介绍
PlatformTransactionManager
:- (平台)事务管理器,Spring 事务策略的核心。
- 三个方法:获得事物、提交事物、回滚事物
TransactionDefinition
- 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
TransactionStatus
:- 事务运行状态。
- 供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚、是否有恢复点、是否已完成
事物的属性
事务隔离级别
ISOLATION_DEFAULT
:使用后端数据库默认的隔离级别,MySQL 默认采用的REPEATABLE_READ
隔离级别 Oracle 默认采用的READ_COMMITTED
隔离级别.ISOLATION_READ_UNCOMMITTED
:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读ISOLATION_READ_COMMITTED
: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ
: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE
: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
传播行为
-
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
1.
TransactionDefinition.PROPAGATION_REQUIRED
- 使用的最多
@Transactional
注解默认- 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
- 创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,
Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰 - 如果
bMethod()
抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()
同样也会回滚
3.
TransactionDefinition.PROPAGATION_NESTED
**:- 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于
TransactionDefinition.PROPAGATION_REQUIRED
- 外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务
4.
TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。
TransactionDefinition.PROPAGATION_SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED
: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER
: 以非事务方式运行,如果当前存在事务,则抛出异常。
超时
- 指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务
只读
- 只读事务不涉及数据的修改
- 给方法加上了
Transactional
注解的话,这个方法执行的所有sql
会被放在一个事务中- 一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态
- 如果不加
Transactional
,每条sql
会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值- 场景:一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性
回滚规则
- 规则定义了哪些异常会导致事务回滚而哪些不会
- 默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)、Error时才会回滚
- 检查型(Checked)异常时不会回滚
- 若想回滚@Transactional(rollbackFor= MyException.class)
@Transactional 注解使用详解
- @Transactional` 的作用范围
- 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
- 类 :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
- 接口 :不推荐在接口上使用。
-
属性名 说明 propagation 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过 isolation 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过 timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 readOnly 指定事务是否为只读事务,默认值为 false。 rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 -
通过AOP动态代理实现的
-
createAopProxy()
方法 决定了是使用 JDK 还是 Cglib 来做动态代理 -
若同一类中的其他没有
@Transactional
注解的方法内部调用有@Transactional
注解的方法,有@Transactional
注解的方法的事务会失效-
原因:AOP代理造成的,只有在类以外被调用的时候才能生效
-
解决办法:
1、不这样做
2、使用 AspectJ 取代 Spring AOP 代理
-
String Bean
在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean
-
singleton单例模式
- 创建容器时就自动创建了一个Bean的对象
- 若设置
lazy-init=”true”
则即第一次请求该bean时才初始化 - singleton 作用域是Spring中的缺省作用域
- 可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁
-
prototype——每次请求都会创建一个新的 bean 实例
- 它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象
- 对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域
- 如果 bean 的 scope 设为prototype时,当容器关闭时,
destroy
方法不会被调用 - Spring不能对一个 prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了
-
request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效
- request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束
-
session——每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效
- session只适用于Web程序
- 当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉
-
globalSession
- 仅仅在基于 portlet 的 web 应用中才有意义
- Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的 portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内
bean的生命周期
1.实现InitializingBean和DisposableBean接口
- 不推荐
- 通过实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作,实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。
2.在bean的配置文件中指定init-method和destroy-method方法
- 在 Bean 初始化时和销毁之前执行一些操作
3.使用@PostConstruct和@PreDestroy注解
为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为*Aware的接口
-
提供一个将由 Bean 实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用
-
针对某个实现这些接口的Bean定制初始化的过程
-
具体接口
- ApplicationContextAware: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。
- BeanFactoryAware:获得BeanFactory对象,可以用来检测Bean的作用域。
- BeanNameAware:获得Bean在配置文件中定义的名字。
- ResourceLoaderAware:获得ResourceLoader对象,可以获得classpath中某个文件。
- ServletContextAware:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。
- ServletConfigAware: 在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。
BeanPostProcessor
-
针对容器中的所有Bean,或者某些Bean定制初始化过程
-
postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行, postProcessAfterInitialization方法在容器中的Bean初始化之后执行
我们一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个Bean不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。