Spring-设计模式
// todo 结合设计模式案例分析源码
Spring-设计模式
学习核心
- 掌握Spring中设计模式的场景应用
学习资料
Spring中的设计模式
设计模式 | 说明 |
---|---|
工厂设计模式 | 使用工厂模式通过BeanFactory或ApplicationContext创建bean对象 |
单例设计模式 | 通过 ConcurrentHashMap 实现单例注册表的特殊方式,常见用于bean对象的创建(Spring默认scope是singleton) |
代理设计模式 | SpringAOP利用AspectJ AOP进行构建,其底层是基于代理模式实现的 |
模板方法设计模式 | Spring通过使用 Callback 模式与模板方法模式配合使用,达到代码复用的效果同时又增加了灵活性 例如Spring中的数据库相关JdbcTemplate、事务处理TransactionTemplate(PlatformTransactionManager提供接口,由不同的平台接入实现) |
策略模式 | SpringAOP中的代理实现:JDK动态代理、CGLIB代理 加载资源文件的方式:Resource(ClassPathResource、FileSystemResource、ServletContextResource) |
观察者模式 | Spring事件驱动模型的场景应用(事件ApplicationEvent、事件监听者ApplicationListener、事件发布者ApplicationEventPublisher) |
适配器模式 | SpringAOP中的AdvisorAdapter、SpringMVC中的DispatcherServlet |
装饰器模式 | 例如Spring配置Datasource的时候动态切换数据源(Wrapper、Decorator,用于动态地给对象添加一些额外地属性或者行为) |
Spring设计模式详解
1.工厂设计模式
BeanFactory VS ApplicationContext
BeanFactory:
- 延迟注入(只有在使用到某个bean的时候才会注入),对比ApplicationContext来说会占用更少的内容,程序启动速度更快
- BeanFactory提供了最基本的依赖注入支持
ApplicationContext:
- 容器启动的时候一次性创建所有的bean(不管是否使用到)
- ApplicationContext扩展了BeanFactory,在其基础上扩展了更多的额外功能
ApplicationContext 的实现类
ClassPathXmlApplication
:把上下文文件当成类路径资源
FileSystemXmlApplication
:从文件系统中的 XML 文件载入上下文定义信息
XmlWebApplicationContext
:从 Web 系统中的 XML 文件载入上下文定义信息
静态模式创建实例
构建思路
- 创建一个静态工厂类,提供静态方法返回要创建的对象
- 在application Context.xml 配置,将实例注册到IOC容器中
- 测试:创建IOC容器并通过其创建对象并使用
public class Staff {
public void say(){
System.out.println("say sth...");
}
}
// 1.创建一个静态工厂类,提供静态方法类返回要创建的对象
public class MyBeanFactory {
// 创建静态工厂方法实例化Bean对象
public static Staff createBean(){
return new Staff();
}
}
// 2.注册
<!-- factory-method:告诉Spring容器调用工厂类中的指定方法获取Bean的实例 -->
<bean id="myBeanFactory" class="com.noob.framework.di.staticFactory.MyBeanFactory" factory-method="createBean"></bean>
// 3.测试
public class SpringDIStaticFactoryTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-staticFactory.xml");
// 通过执行静态工厂方法获取到Bean对象
Staff staff = (Staff) applicationContext.getBean("myBeanFactory");
staff.say();
}
}
实例工厂创建实例
构建思路
- 创建一个实例工厂类,提供实例方法返回要创建的对象
- 在application Context.xml 配置,将实例注册到IOC容器中
- 测试:创建IOC容器并通过其创建对象并使用
// 1.创建一个实例工厂类,提供实例方法返回要创建的对象
public class InstanceBeanFactory {
// 构造函数初始化
public InstanceBeanFactory(){
System.out.println("instance factory init");
}
// 创建普通方法实例化Bean对象
public Boss createBean(){
return new Boss();
}
}
// 2.注册
<!-- 步骤1:配置实例工厂 -->
<bean id="instanceBeanFactory" class="com.noob.framework.di.instanceFactory.InstanceBeanFactory"></bean>
<!-- factory-bean:指定一个实例工厂; factory-method 指定实例工厂的指定方法 -->
<bean id="boss" factory-bean="instanceBeanFactory" factory-method="createBean"></bean>
// 3.测试
public class SpringDIInstanceFactoryTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-instanceFactory.xml");
// 通过执行静态工厂方法获取到Bean对象
Boss boss = (Boss) applicationContext.getBean("boss");
boss.say();
}
}
2.单例设计模式
Spring 通过 ConcurrentHashMap
实现单例注册表的特殊方式实现单例模式
在一些系统涉及场景中,有一些对象其实只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
使用单例模式的好处
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间
Singleton VS Prototype
测试不同配置下的对象生成
// 1.创建Staff实体
public class Staff {
public void say(){
System.out.println("say sth...");
}
}
// 2.配置xml
<bean id="staff" name="staff" class="com.noob.framework.di.staticFactory.Staff"></bean>
// 3.测试
public class SingletonTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-singleton.xml");
Staff staff1 = (Staff) context.getBean("staff");
System.out.println("staff1" + staff1);
Staff staff2 = (Staff) context.getBean("staff");
System.out.println("staff2" + staff2);
}
}
// 默认是singleton,输入如下结果:输出的两个内容都是指向同一个对象
staff1com.noob.framework.di.staticFactory.Staff@5ea434c8
staff2com.noob.framework.di.staticFactory.Staff@5ea434c8
Spring默认的scope是singleton,则输出的都是指向同一个对象;如果指定的scope是prototype,则构建的是多例对象
<bean id="staff" name="staff" class="com.noob.framework.di.staticFactory.Staff" scope="prototype"></bean>
单例Bean的线程安全问题
单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的
常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的
Spring实现单例的核心代码
源码跟踪:通过ApplicationContext调用getBean方法创建对象
=》BeanFactory#getBean
=》AbstractApplicationContext#getBean
=》doGetBean
(调用getSingleton
获取单例对象)
=》DefaultSingletonBeanRegistry#getSingleton
(多个重载方法getSingleton
的参数不同,定位到单例工厂)
核心内容说明
方法 | 说明 |
---|---|
单例注册表 | singletonObjects |
获取单例对象 | public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) |
添加对象 | protected void addSingleton(String beanName, Object singletonObject) |
// 单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
// 获取单例对象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
this.beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = this.suppressedExceptions == null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException var16) {
IllegalStateException ex = var16;
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
} catch (BeanCreationException var17) {
BeanCreationException ex = var17;
if (recordSuppressedExceptions) {
Iterator var8 = this.suppressedExceptions.iterator();
while(var8.hasNext()) {
Exception suppressedException = (Exception)var8.next();
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
this.afterSingletonCreation(beanName);
}
if (newSingleton) {
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
// 添加对象
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
3.代理设计模式
AOP(Aspect-Oriented Programming,面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理
可以使用AspectJ框架(Java生态系统中较完善的AOP框架),将一些通用功能抽象出来,在需要使用的地方直接使用,大大简化代码开发,提供系统扩展性,例如一些的常见场景(日志管理、事务管理等)
4.模板方法设计模式
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式
结合实际业务场景,可以理解为模板方法设计模式是基于一组操作流程的规范定义(某些特定的步骤),子类通过继承模板进而扩展方法实现,但不会打破原有的执行结构。其最大的有点是实现代码复用、减少代码重复量
Spring 中 JdbcTemplate
、HibernateTemplate
等以 Template 结尾的对数据库操作的类,就使用到了模板模式。一般情况下,都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性
模板方法构建思路
- 定义一个Template抽象类,定义相应的执行步骤
// 抽象模板方法
public abstract class Template {
// 定义模板方法(可以理解为一组操作流程的规则定义)
public final void templateMethod(){
// 按照一定规则执行操作
primitiveOperation1();
primitiveOperation2();
primitiveOperation3();
}
// 当前类实现
protected void primitiveOperation1(){
System.out.println("Template默认实现:基本操作1");
}
// 子类的扩展实现
protected abstract void primitiveOperation2();
protected abstract void primitiveOperation3();
}
- 定义不同的子类Template实现通过继承Template,在扩展方法实现的同时而不打破原有的执行结构
// 默认模板实现类定义
public class DefaultTemplate extends Template{
@Override
protected void primitiveOperation2() {
System.out.println("默认模板实现类-基本操作方法2执行");
}
@Override
protected void primitiveOperation3() {
System.out.println("默认模板实现类-基本操作方法3执行");
}
}
- 测试
public class TemplateTest {
public static void main(String[] args) {
DefaultTemplate defaultTemplate = new DefaultTemplate();
defaultTemplate.templateMethod();
}
}
基于模板方法,当需要扩展某个节点的方法时则可通过继承Template并重写方法实现。
public class NewTemplate extends Template{
@Override
protected void primitiveOperation2() {
}
@Override
protected void primitiveOperation3() {
}
}
5.策略模式
策略模式的应用场景
- 一个系统中有很多类,其主要区别在于行为不同
- 在一个系统中需要动态地在集中算法中选择一种
SpringAOP中的代理实现:JDK动态代理、CGLIB代理
加载资源文件的方式:Resource
Spring框架的资源访问Resource接口,该接口提供了更强的资源访问能力,对应有多个实现类ClassPathResource、FileSystemResource、ServletContextResource等
Resource实现类 | 说明 |
---|---|
UrlResource | 访问网络资源的实现类 |
ClassPathResource | 访问类加载路径里资源的实现类 |
FileSystemResource | 访问文件系统里资源的实现类 |
ServletContextResource | 访问相对于 ServletContext 路径里的资源的实现类 |
InputStreamResource | 访问输入流资源的实现类 |
ByteArrayResource | 访问字节数组资源的实现类 |
6.观察者模式
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,依赖这个对象的所有对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题.
Spring事件驱动模型则是通过观察者模式来完成状态和通知方的松耦合
Spring事件驱动模型中的三种角色
事件角色
ApplicationEvent
(org.springframework.context
包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject
并实现了 java.io.Serializable
接口。
Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent
的实现(继承自ApplicationContextEvent
):
ContextStartedEvent
:ApplicationContext
启动后触发的事件;ContextStoppedEvent
:ApplicationContext
停止后触发的事件;ContextRefreshedEvent
:ApplicationContext
初始化或刷新完成后触发的事件;ContextClosedEvent
:ApplicationContext
关闭后触发的事件
事件监听者角色
ApplicationListener
充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()
方法来处理ApplicationEvent
。ApplicationListener
接口类源码如下,在 Spring 中只要实现 ApplicationListener
接口的 onApplicationEvent()
方法即可完成监听事件
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
事件发布者角色
ApplicationEventPublisher
充当了事件的发布者,它也是一个接口
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
ApplicationEventPublisher
接口的publishEvent()
这个方法在AbstractApplicationContext
类中被实现,深入阅读这个方法的实现,实际上事件真正是通过ApplicationEventMulticaster
来广播出去的
Spring事件流程
事件流程构建思路
- 定义一个事件: 实现一个继承自
ApplicationEvent
,并且写相应的构造函数; - 定义一个事件监听者:实现
ApplicationListener
接口,重写onApplicationEvent()
方法; - 使用事件发布者发布消息: 可以通过
ApplicationEventPublisher
的publishEvent()
方法发布消息
参考案例
// 定义事件,继承ApplicationEvent并写相应的构造方法
public class DemoEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
// 使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
// 发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
当调用 DemoPublisher
的 publish()
方法的时候,比如 demoPublisher.publish("你好")
,控制台就会打印出:接收到的信息是:你好
// 例如在springboot项目中构建测试
@SpringBootTest
class DemoPublisherTest {
@Autowired
private DemoPublisher demoPublisher;
@Test
void publish() {
demoPublisher.publish("hello world");
}
}
7.适配器模式
适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作
SpringAOP中的适配器模式
Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter
Advice 常用的类型有:BeforeAdvice
(前置通知:目标方法调用前)、AfterAdvice
(后置通知:目标方法调用后)、AfterReturningAdvice
(目标方法执行结束后,return 之前)等。每个类型 Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor
、AfterReturningAdviceInterceptor
、ThrowsAdviceInterceptor
。
Spring 预定义的通知要通过对应的适配器,适配成 MethodInterceptor
接口(方法拦截器)类型的对象(如:MethodBeforeAdviceAdapter
通过调用 getInterceptor
方法,将 MethodBeforeAdvice
适配成 MethodBeforeAdviceInterceptor
)
SpringMVC中的适配器模式
在 Spring MVC 中,DispatcherServlet
根据请求信息调用 HandlerMapping
,解析请求对应的 Handler
。解析到对应的 Handler
(即 Controller
控制器)后,开始由HandlerAdapter
适配器处理。HandlerAdapter
作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller
作为需要适配的类。
如果不采用适配器模式,则DispatcherServlet
直接获取对应类型的 Controller
,需要自行判断,代码实现思路参考如下:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
当需要再增加一个Controller类型,则需要在上面的代码中加入判断语句,以此类推。在SpringMVC中使用适配器模式则是为了更好地进行业务扩展,而不影响原有的实现逻辑
8.装饰者模式
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当需要修改原有的功能,但又不愿直接去修改原有的代码时,设计一个 Decorator 套在原有代码外面。
在 JDK 中就有很多地方用到了装饰者模式,IO处理相关: InputStream
家族,InputStream
类下有 FileInputStream
(读取文件)、BufferedInputStream
(增加缓存、使读取文件速度大大提升)等子类都在不修改InputStream
代码的情况下扩展了它的功能
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。Spring 中用到的包装器模式在类名上含有 Wrapper
或者 Decorator
。这些类基本上都是动态地给一个对象添加一些额外的职责