在当今软件开发的广阔领域中,Spring 框架无疑占据着重要的一席之地。它以其强大的功能和灵活性,成为众多开发者的得力助手。而在 Spring 框架的背后,蕴含着一系列精妙绝伦的设计模式,这些设计模式犹如隐藏的智慧密码,赋予了框架无尽的活力与魅力。
当我们深入探究 Spring 框架时,就仿佛开启了一扇通往软件设计艺术殿堂的大门。其中的设计模式不仅是代码组织和架构的精巧构建,更是对软件开发理念的深刻诠释。它们巧妙地解决了各种复杂问题,实现了代码的高效复用、模块的松散耦合以及系统的良好扩展性。通过对 Spring 框架涉及的设计模式的剖析,我们将领略到设计智慧的熠熠光辉,感受它们如何在软件世界中演绎出一场场精彩绝伦的代码之舞,为构建高质量、可维护的软件系统奠定坚实基础。让我们一同踏上这场探索 Spring 框架设计模式的奇妙之旅,去揭示其中的奥秘与精彩。
一、关于设计模式的一些只是铺垫
1.软件设计有哪些原则?
整体来说有七大原则:
- 开闭原则:对扩展开放,对修改关闭。
- 里氏转换:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
- 依赖倒置: 高层类应该依赖于底层类的抽象而不是具体。
- 合成复用:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的。
- 单一职责:不要多于一个导致类变更的原因。
- 接口原则:用多个接口确定类的行为,而不是一个总接口定义所有行为。
- 迪米特法则:最少知道原则,一个类应该尽可能减少对其他类的了解,避免类之间过度耦合。(其他类应该封装一个方法提供的该类使用)
2.设计模式分为哪几类?有哪些设计模式
分类:
- 创建型模式:创建型模式主要用于创建对象。
- 结构型模式:主要用于处理类或对象的组合。
- 行为型模式:主要用于描述对类或对象怎样交互和怎样分配职责。
创建型模式:
- 单例模式:确保类有且只有一个对象被创建。
- 抽象工厂模式:允许客户创建对象的宗族,而无需指定他们的具体类。
- 建造者模式:将一个复杂对象的构建与它的表示分离,让同样的构建过程而已创建不同的表示。
- 工厂方法模式:由类决定要创建的具体类是哪一个。
- 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
结构型模式:
- 适配器模式:封装对象,并提供不同的接口。
- 桥接模式:将抽象部分和实现部分分离,让他们独立的变化。
- 装饰模式:包装一个对象,提供新的行为。
- 组合模式:客户用一致的方式处理对象集合和单个对象。
- 外观模式:简化一群类的接口。
- 享元模式:运用共享技术有效的支持大量细粒度的对象。
- 代理模式:包装对象,以控制对此对象的访问。
行为型模式:
- 模板方法模式:定义一个操作算法的总体架构,将一些步骤的实现放在子类中。
- 命令模式:封装请求成为对象。
- 迭代器模式:在对象的集合之中游走,而不是暴露集合的实现。
- 观察者模式:让对象能够在状态改变时被通知。
- 中介者模式:用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显示的相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。
- 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 以后就可以将该对象恢复到保存状态。
- 解释器模式:介绍给定一个语言,定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
- 状态模式:封装了基于状态的行为,并使用委托在行为之间的切换。
- 策略模式:封装可以互换的行为,并使用委托来决定要使用哪一个。
- 责任链模式:为了解除请求的发送者和接收者之间的耦合,使多个对象都有机会处理这个请求。将这些处理对象连成一个链,并沿着这个链传递该请求,直到一个对象处理它。
- 访问者模式:一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
二、详解Spring核心中的设计模式
1.为什么说Spring框架中的IOC是解耦的
从代码层面来说,Spring通过控制反转将对象之类依赖关系交由容器管理,如下所示我们的AService 依赖BService ,BService 可能还会依赖于CService 层层递进,如果是传统编码,我们可能需要通过硬编码的方式完成AService 的构建。 而Spring通过IOC思想,在初始化阶段将所有对象都交由三级缓存管理,将所有java bean初始化责任的实现细节转移给Spring,使用时也只需指明接口类型,接口实现也无需关心,只需在配置层面中指定,而非通过硬编码完成依赖管理:
所以我们使用时只需几个简单的配置和注解即可完成各种复杂的bean的依赖管理,这也就是开发层面对象依赖关系管理的解耦:
@Service("aService")
@Slf4j
public class AService {
@Autowired
private BService bService;
//......
}
从使用层面来说,Spring中的IOC也用到类似于门面模式的思想,将工具类的使用和创建剥离,整个工具类的创建过程对使用者来说是透明解耦的。
例如我们需要使用日志框架,在spring中我们只需给出简单的配置,框架即在运行时基于给定配置完成对应的日志工具的注入(可以是log4j可以是slf4j或者其他日志框架),程序启动后即可直接使用功能,无需关心创建和实现:
2.Spring源码中涉及的简单工厂模式
简单工厂模式的思想就是对外屏蔽创建对象的细节,将对象的获取统一内聚到一个工厂类中,这一点在Spring中的ApplicationContext 发挥的淋漓尽致。 我们都知道Spring将所有java bean统一交由三级缓存进行管理,使用时我们可以通过上下文或者需要的java bean,用户只需按需要传递给工厂对应的bean名称即可得到自己需要的对象即可:
对应的我们也给出使用示例:
@Autowired
private ApplicationContext applicationContext;
public Object getBean(String beanName) {
Object bean = applicationContext.getBean(beanName);
return bean;
}
3.Spring中的工厂方法模式
工厂方法模式适用于想让工厂专注创建一个对象的场景,相较于简单工厂模式,工厂方法模式思想是提供一个工厂的接口,开发者根据这个规范创建不同的工厂,然后按需使用不同的工厂创建不同的类即可。这种做法确保了工厂类也遵循开闭原则。
Spring中的FactoryBean就是工厂方法模式的典型实现,如果我们希望容器中能够提供一个可以创造指定类的工厂,那么我们就可以通过FactoryBean实现。 例如我们希望有一个工厂可以创建经理,另一个工厂可以创建主管。那么我们就可以通过FactoryBean实现。 实现步骤如下,由于经理和主管都是雇员类,所以我们创建一个雇员类:
//雇员类
public class EmployeeDTO {
private Integer id;
private String firstName;
private String lastName;
private String designation;
//Setters and Getters are hidden behind this comment.
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
@Override
public String toString() {
return "Employee [id=" + id + ", firstName=" + firstName
+ ", lastName=" + lastName + ", type=" + designation + "]";
}
}
然后我们继承FactoryBean接口实现一个工厂方法类,如下所示,可以看到如果我们可以根据传入的designation决定创建的雇员类型。
public class EmployeeFactoryBean extends AbstractFactoryBean<Object> {
// 根据这个值决定创建主管还是经理
private String designation;
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
//This method will be called by container to create new instances
@Override
protected Object createInstance() throws Exception {
EmployeeDTO employee = new EmployeeDTO();
employee.setId(-1);
employee.setFirstName("dummy");
employee.setLastName("dummy");
//Set designation here
employee.setDesignation(designation);
return employee;
}
//This method is required for autowiring to work correctly
@Override
public Class<EmployeeDTO> getObjectType() {
return EmployeeDTO.class;
}
}
两种雇员的配置如下所示:
<!--factoryBean使用示例-->
<!--经理工厂-->
<bean id="manager" class="com.study.service.EmployeeFactoryBean">
<property name="designation" value="Manager" />
</bean>
<!--主管工厂-->
<bean id="director" class="com.study.service.EmployeeFactoryBean">
<property name="designation" value="Director" />
</bean>
如果我们想创建director(主管)的工厂,那么我们的代码就可以这样使用,注意我们获取bean时必须使用&,否则获得的就不是EmployeeFactoryBean,则是EmployeeDTO
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Object factory = context.getBean("&director");
System.out.println(factory);
//工厂方法模式,通过单一职责的工厂获取专门的类
System.out.println(((EmployeeFactoryBean) factory).getObject());
当然,如果想直接获取高管或者经理,获取bean时不加&即可代码如下所示即可:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
EmployeeDTO manager = (EmployeeDTO) context.getBean("manager");
System.out.println(manager);
Object director = context.getBean("director");
System.out.println(director);
4.工厂方法模式相较于简单工厂模式的优缺点
工厂方法模式的优点:
- 符合开闭原则,相较于上面说到的简单工厂模式来说,我们无需因为增加一个类型而去修改工厂代码,我们完全可以通过实现一个新的工厂实现。
- 更符合单一职责的原则,对于单个类型创建的工厂逻辑更加易于维护。
缺点:
- 针对特定类型都需要创建特定工厂,创建的类会增加,导致项目变得臃肿。
- 因为工厂方法的模式结构,维护的成本相对于简单工厂模式会更高一些。
5.单例模式在Java中的使用优势
节省没必要的创建对象的时间,由于是单例的对象,所以创建一次后就可以一直使用了,所以我们无需为了一个重量级对象的创建而耗费大量的资源。
由于重量级对象的创建次数少了,所以我们就避免了没必要的GC。从而降低GC压力,避免没必要的STW(Stop the World)导致的GC停顿。
6.Spring中单例模式的实现
Spring中获取对象实例的方法即DefaultSingletonBeanRegistry中的getSingleton就是典型的Double-Checked Locking单例模式代码:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
//一级缓存没有需要的bean,进入该逻辑
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//二级对象也没有,上锁进入创建逻辑
synchronized (this.singletonObjects) {
// 再次检查一级缓存也没有,避免重复创建问题
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//......
//创建对象存入二级缓存中
}
}
}
}
}
return singletonObject;
}
7.Spring中的代理模式
代理模式解耦了调用者和被调用者的关系,同时通过对原生类型的代理进行增强易于拓展和维护,Spring AOP就是通过代理模式实现增强切入,我们就以JDK代理为例查看Spring中的实现:
public Object getProxy(@Nullable ClassLoader classLoader) {
// 忽略代码
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//通过被代理的类的接口以及增强逻辑创建一个增强的用户所需要的类
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
查看newProxyInstance的实现即可看到jdk代理的传统创建细节即拿到被代理的类类型和需要增强后的方法实现InvocationHandler 通过反射完成代理类创建:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//......
//获取接口类类型
Class<?> cl = getProxyClass0(loader, intfs);
//......
//从缓存中获取代理类的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//......
//基于InvocationHandler 和构造方法完成代理类创建
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
//......
} catch (InvocationTargetException e) {
//......
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
8.Spring中的模板方法模式
模板方法模式即固定一个算法骨架,抽象出某些可变的方法交给子类实现,Spring的AbstractApplicationContext的refresh方法就是典型模板方法模式,
@Override
public void refresh() throws BeansException, IllegalStateException {
// 给容器refresh加锁,避免容器处在refresh阶段时,容器进行了初始化或者销毁的操作
synchronized (this.startupShutdownMonitor) {
// .........
try {
// .........
//定义了相关接口给用户实现,该方法会通过回调的方式调用这些方法,已经实现好的细节
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建过程的BeanPostProcessor,已经实现好的细节
registerBeanPostProcessors(beanFactory);
//模板方法的体现,用户可自定义重写该方法
onRefresh();
//.......
}
// .......
}
}
9.模板方法模式的优劣势
优势很明显:
- 算法骨架由父类定义,封装不变,扩展可变。
- 子类只需按需实现抽象类即可,易于扩展维护。
- 提取了公共代码,避免编写重复代码。
缺点:
- 可读性下降
- 可能会导致子类泛滥问题。
10.Spring中的观察者模式
观察者模式是一种行为型模式。 它表示的是一种主题与订阅者之间具有依赖关系,当订阅者订阅的主题状态发生改变,会发送通知给响应订阅者,触发订阅者的响应操作。
Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次发布一个通知就需要某个用户做出收到的响应,这个时候就可以利用观察者模式来解决这个问题。
首先我们需要定义一个事件类:
public class DemoEvent extends ApplicationEvent {
private String msg;
public DemoEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
然后创建一个监听器集成Spring框架的ApplicationListener,对DemoEvent进行监听:
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
private static Logger logger = LoggerFactory.getLogger(DemoListener.class);
@Override
public void onApplicationEvent(DemoEvent event) {
logger.info("收到消息,消息内容:{}", event.getMsg());
}
}
这样我们发布事件后,监听器就接受并处理返回结果了:
@Autowired
private ApplicationContext applicationContext;
@Test
public void sendMsg() {
DemoEvent event = new DemoEvent(this, "你好");
applicationContext.publishEvent(event);
}
对应输出结果如下所示:
2022-11-22 13:03:15.814 INFO 15600 --- [ main] com.example.demo.DemoListener : 收到消息,消息内容:你好
11.Spring中用到的适配器模式
适配器模式有用在在DisposableBeanAdapter适配统一处理bean销毁上,因为我们bean销毁方法可以通过xml配置也可以通过继承DisposableBean接口实现,所以这两种不同的方法在销毁时处理方式可能不一样,所以我们可以通过适配器模式将这两个处理逻辑封装成统一适配器进行处理
@Override
public void destroy() {
//......
//如果是接口实现则执行该类实现的destroy方法
if (this.invokeDisposableBean) {
//......
try {
if (System.getSecurityManager() != null) {
//......
}
else {
//
((DisposableBean) this.bean).destroy();
}
}
catch (Throwable ex) {
//......
}
}
//如果有注解或者xml配置的方法,则走invokeCustomDestroyMethod进行调用
if (this.destroyMethod != null) {
invokeCustomDestroyMethod(this.destroyMethod);
}
else if (this.destroyMethodName != null) {
Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
if (methodToInvoke != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
}
}
}
12.Spring中的装饰者模式
装饰者模式即通过组合的方式对原有类的行为进行一些扩展操作即在开闭原则下的一种结构型设计模式,就以Spring中的TransactionAwareCacheDecorator为例,它就是实现缓存支持事务的功能,继承缓存接口,并将目标缓存类组合进来,保证原有类不被修改的情况下实现功能的扩展:
//继承Cache 类
public class TransactionAwareCacheDecorator implements Cache {
//行为需要扩展的目标类
private final Cache targetCache;
// 从接口那里获得的put方法,通过对targetCache的put进行进一步封装实现功能的包装
@Override
public void put(final Object key, @Nullable final Object value) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
}
else {
this.targetCache.put(key, value);
}
}
}
三、更多关于设计模式
1.什么是策略模式
策略模式是一种行为模式,它的作用主要是用于封装那些动作一致,但是具体实现不一致的场景。例如我们现在有一个审核的动作,不同的人审核的方式不同。而且审核的行为会随着时间推移不断增加,单纯使用if else去维护会使得代码变得非常凌乱。 这时候使用策略模式定义一下相同的行为,让子类去实现不同的策略,这样的方式是最利于拓展的。
2.策略模式在Spring中如何使用
使用策略模式前,我们先需要使用接口固定一下策略的定义,例如我们现在要创建一个生成对象的策略createObj:
public interface CreateObjStrategy {
Object createObj();
}
对应我们分别写一个创建字符串对象和json对象的策略代码:
@Component("createObj_str")
public class CreateStrObjStrategy implements CreateObjStrategy {
@Override
public Object createObj() {
Map<String, Object> map = new HashMap<>();
map.put("name", "xiaoming");
return JSONUtil.toJsonStr(map);
}
}
@Component("createObj_json")
public class CreateJsonObjStrategy implements CreateObjStrategy {
@Override
public Object createObj() {
JSONObject obj = new JSONObject();
obj.put("name", "zhangsan");
return obj;
}
}
到此为止,我们已经完成了所有的策略的封装,按照原生的策略模式的做法它会通过一个上下文来配置当前策略,就像下面这张设计图:
对应我们给出封装的代码示例:
public class CreateJsonObjContext {
private CreateObjStrategy createObjStrategy;
public CreateJsonObjContext(CreateObjStrategy strategy) {
this.createObjStrategy = strategy;
}
public Object createObj() {
return createObjStrategy.createObj();
}
}
实际上有了Spring我们无需进行显示声明创建了,我们可以通过配置、注解等方式指明本地程序需要注入的CreateObjStrategy 实现,加载上下文的时候通过ApplicationContextAware这样的扩展点把bean取出来设置到CreateJsonObjContext 中:
@Component
public class CreateJsonObjContext implements ApplicationContextAware {
private CreateObjStrategy createObjStrategy;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//从容器中拿到由我们配置后容器中所设置的CreateObjStrategy设置到Context中
CreateObjStrategy bean = applicationContext.getBean(CreateObjStrategy.class);
createObjStrategy = bean;
}
public Object createObj() {
return createObjStrategy.createObj();
}
}
3.Spring中策略模式的运用
最典型的就时InstantiationStrategy,Spring为其实现了两种不同的策略,分别是CglibSubclassingInstantiationStrategy和SimpleInstantiationStrategy,在Spring通过实例化java bean的时候其内部就会通过getInstantiationStrategy从上下文中获取初始化策略instantiationStrategy,然后调用instantiationStrategy的instantiate完成bean的创建:
对应的我们也给出AbstractAutowireCapableBeanFactory中instantiate实例化bean的实现印证这一说法:
private Object instantiate(String beanName, RootBeanDefinition mbd,
@Nullable Object factoryBean, Method factoryMethod, Object[] args) {
try {
else {
//通过getInstantiationStrategy获取上下文中对应的创建策略完成bean的创建
return this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);
}
}
catch (Throwable ex) {
//......
}
}