学号:
姓名:
设计模式(Design Pattern)是软件开发人员在软件开发过程中面临的一般问题的解决方案,是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结,通俗来讲就是程序员面对软件工程设计问题总结出来的有用的经验。使用合适的设计模式可以提高代码可读性、重用性和可靠性,可以降低软件的复杂度,提高系统的通用性和扩展性,学习设计模式可以帮助经验不足的开发人员通过简单快捷的方式学习软件设计。
根据设计模式参考书《设计模式:可复用面向对象软件的基础》,常见的设计模式有23种,分为三大类:创建型模式、结构型模式和行为型模式。
Spring作为最受欢迎的企业级 Java 应用程序开发框架,大量使用了设计模式来进行功能扩展,从这款优秀的开源框架中学习设计模式是很好的选择。
本文将分析Spring框架对设计模式的使用,帮助软件开发人员加深对设计模式落地应用的理解,写出让人惊叹的代码。对于每种设计模式,本文首先给出相关模式的思想,随后附以其在Spring中的应用解释。
工厂模式(Factory Pattern)将类的实例化延迟到子类,定义一个接口来创建对象,让子类决定实例化哪个类。
其类图如下:
Product
是一个接口,所有产品必须实现此接口,其他类使用产品时通过引用此接口来使用产品。ConcreteProduct
是具体产品产品类。Creator
类实现了所有操纵产品的方法,但不实现工厂方法。ConcreteCreator
类实现factoryMethod()
方法,来制造出实际产品ConcreteProduct
。工厂模式适用于多种场景:
工厂方法模式是比较常见的设计模式,Spring底层大量使用这种设计模式进行封装。
Spring使用工厂模式,可以通过BeanFactory
或 ApplicationContext
创建 bean 对象。
**在此以FactoryBean
为例,从它开始阅读Spring源码,探索其工厂模式的使用。**代码阅读顺序以继承、实现关系为依据:
FactoryBean
org.springframework.beans.factory.FactoryBean
FactoryBean
既是Bean,也能生产Bean,只提供了3个方法。
getObject()
方法返回真正的Bean
实例。getObjectType()
返回该Factory生产的Bean class。isSingleton()
判断该Factory生成的Bean是否为单例。补充:后面单例模式分析时涉及
BeanFactory
,在此说明二者区别。BeanFactory
是IOC容器或对象工厂,在Spring中所有的Bean
由BeanFactory
管理。FactoryBean
是个Bean
,但也能够生产或修饰工厂Bean。(4条消息) spring中BeanFactory和FactoryBean的区别_常胜的博客-CSDN博客_factorybean和beanfactory的区别
AbstractFactoryBean
org.springframework.beans.factory.config.AbstractFactoryBean
AbstractFactoryBean
是一个实现了FactoryBean
接口的抽象类,可以基于此类定义自己的FactoryBean
。
AbstractFactoryBean
中的getObject()
方法如下:
public final T getObject() throws Exception {
if (this.isSingleton()) { //如果是单例模式,单例已经初始化则返回单例,否则返回earlySingletonInstance
return this.initialized ? this.singletonInstance : this.getEarlySingletonInstance();
} else {
return this.createInstance();//不是单例模式,则创建对象
}
}
其中不是单例模式时,调用的createInstance()
方法是AbstractFactoryBean
中的一个抽象方法,表示由其子类自定义创建对象的逻辑。
protected abstract T createInstance() throws Exception;
查看继承了AbstractFactoryBean
的工厂类如下,选取AbstractServiceLoaderBasedFactoryBean
类继续阅读
AbstractServiceLoaderBasedFactoryBean
org.springframework.beans.factory.serviceloader.AbstractServiceLoaderBasedFactoryBean
此类对父类AbstractFactoryBean
的抽象方法createInstance
加以实现,创建实例时调用的getObjectToExpose()
仍为抽象方法,继续查看该类的子类ServiceLoaderFactoryBean
ServiceLoaderFactoryBean
org.springframework.beans.factory.serviceloader.ServiceLoaderFactoryBean
该类中实现的getObjectToExpose()
方法返回了serviceLoader
,因此得出结论:该FactoryBean工厂生产了ServiceLoader实例。
FactoryBean
为真正的抽象工厂类,getObject()
为其工厂方法,AbstractServiceLoaderBasedFactoryBean
是一种具体工厂,但此具体工厂又可能生产多种产品。单例模式(Singleton Pattern)保证一个类只有一个实例,并提供一个访问该实例的全局访问点。
其类图如下:
uniqueInstance
为该类持有的唯一单件实例。getInstance
()为静态方法,可以在代码的任何地方使用Singleton.getInstance()
访问它,可以延迟实例化。使用场景
Spring中bean的默认作用域单例的,实现单例的方式有2种
xml形式:
<bean id="user" class="com.buaa.mmy.User" scope="singleton"/>
注解形式@Scope(value = "singleton")
Spring 中加载单例的过程在BeanFactory
接口中的getBean()
方法中定义的,实现默认在AbstractBeanFactory
类中,该类的getBean()
实现方法调用doGetBean()
方法,该方法的大致流程为
①尝试从缓存获取Bean
②判断循环依赖
③递归到父容器获取Bean实例
④从当前容器获取BeanDefinition实例
⑤递归实例化显示依赖的Bean
⑥根据不同的Scope采取不同策略创建Bean实例
⑦对Bean进行类型检查
Object sharedInstance = getSingleton(beanName);
尝试从缓存集合中获取Bean实例。若先前创建过Bean实例且调用的getBean方法传入的参数为空,则会执行doGetBean()
方法里if中的逻辑,否则执行else中的代码。getSingleton()
方法调用如如下方法:
isSingletonCurrentlyInCreation()
结果为false因此getSingleton()
方法会返回null,doGetBean()
方法继续运行。此时sharedInstance=null
,缓存中不存在Bean,执行else部分,核心代码如下图,会再次调用getSingleton()
重载方法。
核心代码如下,通过 ConcurrentHashMap
实现单例注册表的特殊方式实现单例模式。当从缓存中加载单例对象时,只要创建一个单例对象就会把它放入singletonObjects
中,在getBean()
时只能获取一个。创建了单例Bean的缓存之后,下次直接从缓存中可以获取到Bean。
// 通过线程安全的ConcurrentHashMap实现单例注册表
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) {
//...
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
//...
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
this.afterSingletonCreation(beanName);
}
if (newSingleton) {
//如果实例对象不存在,在singletonObject中注册单例
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
以上为Spring创建单例的过程,由于处理时需要解决其他问题,因而显得十分复杂,但本质逻辑较为清楚。
(4条消息) doGetBean方法详解_kerry_x的博客-CSDN博客
代理模式(Proxy Pattern)为另一个对象提供一个替身或占位符,从而控制对这个对象的访问。
其类图如下
Subject
为Proxy
和RealSubject
提供了接口,通过实现同一个接口,Proxy
可以在RealSubject
出现的地方取代它。RealSubject
通常是真正做事的对象。Proxy
持有Subject
的引用,必要时可以将请求转发给Subject
;Proxy
通常负责创建RealSubject
,并控制对RealSubject
的访问。使用场景如下
根据代理类生成的时间不同可以把代理分为静态代理和动态代理。
Spring中AOP的实现方式是动态代理。若要代理的对象实现了某个接口,Spring AOP采用JDK Proxy创建代理对象;若要代理的对象没有实现该接口,则采用cglib生成一个被代理对象的子类来作为代理。也可以强制使用cglib方式,在xml配置
或采用@EnableAspectJAutoProxy(proxyTargetClass = true)
即可。
在Spring中,创建代理的过程如下:
从Spring 容器处理器固定方法AbstractAutoProxyCreator
定义的 postProcessAfterInitialization()
方法开始,当每个bean初始化完,都会执行该方法:
在 postProcessAfterInitialization
中,获取完用于缓存的key后,进入到 wrapIfNecessary()
方法进行具体包装。
targetSourcedBeans
中含有beanName
,说明beanName
的代理对象已经生成;adviseBeans
中存储是否需要为某个Bean生成代理,如果里面存在cacheKey
,说明之前已经初始化过Bean的代理类,此时不需要再次初始化;getAdvicesAndAdvisorsForBean()
获取所有过滤器,接下来通过createProxy()
创建代理过程。createProxy()
方法中为创建代理的过程。
originalTargetClass
属性,创建代理工厂。specificInterceptors
转换为advisor,添加增强。getProxy()
方法获取代理实例。创建代理的过程被Spring委托给ProxyFactory
处理,在对ProxyFactory
进行初始化的过程中,有一部分创建AOP代理工厂的过程。
查看DefaultAopProxyFactory
的createAopProxy
方法,在此判断了使用何种代理。代理配置会影响Spring使用代理的方式,此外,如果目标对象实现了接口,默认采用JDK动态代理,但也可以强制使用cglib实现AOP,若没有实现接口则必须采用cglib实现。
使用AOP可以通过代理的方式为程序添加统一功能,在权限、缓存、内容传递、错误处理、日志、追踪、事务、同步、持久化等场景中均可使用。
在本学期J2EE大作业的Web开发中,为了方便调试,需要查看接口调用的基本信息,可以采用AOP实现。打印内容包括接口、请求方式、输入参数、返回值等。
首先在pom.xml中导入所需依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.8.5version>
dependency>
切面类配置如下
package com.example.common;
import com.google.gson.Gson;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect //标识切面类
@Component
public class WebLogAspect {
private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
//以 controller 包下定义的所有请求为切入点
@Pointcut("execution(public * com.example.controller..*.*(..))")
public void webLog() {
}
@Before("webLog()") //在切入点之前织入
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 打印请求相关参数,依次为接口方法,接口URL,请求参数,调用者ip
logger.info("{} {} {} {}", request.getMethod(), request.getRequestURL().toString(), new Gson().toJson(joinPoint.getArgs()), request.getRemoteAddr());
}
@After("webLog()")//在切点之后织入
public void doAfter() throws Throwable {
}
@Around("webLog()") //环绕方式
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = proceedingJoinPoint.proceed();
// 打印出参
logger.info("Response Args : {}", new Gson().toJson(result));
return result;
}
}
最终效果
观察者模式(Observer Pattern)定义对象之间的一种多对多依赖关系,当一个对象的状态法发生改变时,所有依赖于它的对象都得到通知并被自动更新。
类图如下
Subject
是一个主题接口,对象使用这个接口注册为观察者,或者把自己从观察者中删除。ConcreteSubject
是一个具体的主题,它总是实现主题接口,除了注册、删除方法之外,具体实现了notifyObserver()
方法,用于在观察者状态改变时更新当前所有观察者。此外,具体主题类也可能有设置和获取状态的方法。Observer
是一个观察者接口,所有潜在的观察者必须实现此接口,该接口只有一个update()
方法,当主题状态改变时被调用。ConcreteObserver
是具体的观察者,必须注册具体主题,以便于接收更新消息。应用场景
Spring 事件驱动模型是观察者模式很经典的一个应用。该模型可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,可以利用观察者模式来解决这个问题。
Spring的观察者模式中包含几个角色:事件、事件监听、事件发布。
事件角色由ApplicationEvent
(org.springframework.context
包下)充当,作为一个抽象类它继承java.util.EventObject
并实现了 java.io.Serializable
接口。所有的事件都需要继承 ApplicationEvent
, 并且通过source得到事件源。
Spring中默认存在以下事件,它们是对抽象类ApplicationContextEvent
的具体实现。
ContextRefreshedEvent
: ApplicationContext
初始化或刷新时触发的事件。“初始化”意味着所有 bean 加载,post-processor bean 被检测到并且激活,单例预先实例化,ApplicationContext
对象可以使用了。只要上下文没有关闭,可以触发多次刷新, ApplicationContext
提供了一种可选择的支持这种“热”刷新。ContextStartedEvent
: ApplicationContext
开始使用ConfigurableApplicationContext
接口 start()
方法时触发的事件。“开始”意味着所有生命周期 bean 接收到一个明确的起始信号。这个信号通常用于明确停止后重新启动,也可以用于启动组件没有被配置为自动运行。ContextStoppedEvent
:ApplicationContext
停止时使用 ConfigurableApplicationContext
接口上的 stop()
方法时触发的事件。“停止”意味着所有生命周期bean接收一个显式的停止信号。ContextClosedEvent
: ApplicationContext
关闭时触发的事件。“关闭”意味着所有单例 bean 被摧毁,不能刷新或重启。事件监听者角色由ApplicationListener
充当。它是一个只定义了 onApplicationEvent()
方法的接口,用于处理ApplicationEvent
。在 Spring中我们只要实现 ApplicationListener
接口的 onApplicationEvent()
方法即可完成监听事件。
事件发布者角色由ApplicationEventPublisher
充当。ApplicationEventPublisher
接口的publishEvent()
方法在AbstractApplicationContext
类中被实现。
ApplicationEvent
,写相应的构造函数;ApplicationListener
接口,重写 onApplicationEvent()
方法;ApplicationEventPublisher
的 publishEvent()
方法发布消息。@Component
public class MyEvent extends ApplicationEvent {
public MyEvent(ApplicationContext source) {
super(source);
System.out.println("MyEvent 构造器执行");
}
public void echo() {
System.out.println("模拟业务逻辑执行");
}
}
@Component
public class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
System.out.println("监听到了...");
myEvent.echo();
}
}
@Component
public class MyPublisher implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
//发布事件 监听该事件的监听者都可以获取消息
public void publisherEvent(MyEvent myEvent) {
System.out.println("---开始发布 myEvent 事件---");
applicationContext.publishEvent(myEvent);
}
}
// 单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {
@Autowired
private MyPublisher myPublisher;
@Autowired
private MyEvent myEvent;
@Test
public void contextLoads() {
myPublisher.publisherEvent(myEvent);
}
}
单元测试结果✔
模板模式(Template Pattern)定义一个操作中算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
类图如下
AbstractClass
抽象类中包含模板方法,该模板方法实现的过程中用到两个原语操作,primitiveOperaton1()
和primitiveOperaton2()
,模板方法本身和这两个操作的具体实现之间被解耦。ConcreteClass
为具体类,可能有多个,每个都实现了模板方法所需的全部操作,当模板方法需要时会调用。使用场景为
Spring底层对模板模式的使用有多处,比如 jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,事务管理器中的类等等。
以AbstractPlatformTransactionManager
为例,此类中有多个抽象方法。
以doGetTransaction()
方法为例,在模板方法getTransaction()
中调用了此抽象方法,而该抽象方法的具体实现由子类完成。
其中AbstractPlatformTransactionManager
类的子类DataSourceTransactionManager
对抽象方法doGetTransaction()
的实现如下。
由于AbstractPlatformTransactionManager
类有多个子类,不同子类对象的getTransaction()
使用方法略有差别,通过模板设计模式,保留公共部分,抽象出有差异的地方施以不同实现,大大提高了代码复用性✨。
结束之前再回顾一下本文提到的设计模式的思想
工厂模式将类的实例化延迟到子类,定义一个接口来创建对象,让子类决定实例化哪个类。
单例模式保证一个类只有一个实例,并提供一个访问该实例的全局访问点。
代理模式为另一个对象提供一个替身或占位符,从而控制对这个对象的访问。
观察者模式定义对象之间的一种多对多依赖关系,当一个对象的状态法发生改变时,所有依赖于它的对象都得到通知并被自动更新。
模板模式定义一个操作中算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
在Spring框架中,还用到一些本文未提及的设计模式,例如原型模式、适配器模式、装饰器模式、责任链模式、策略模式等,有时间会继续探索