一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 17 天,点击查看活动详情。
Design Patterns(设计模式) 是面向对象软件开发中最好的计算机编程实践。 Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看 Spring 到底有哪些设计模式?
工厂模式
Spring 使用工厂模式通过 BeanFactory
或 ApplicationContext
创建 bean 对象。
首先,我们聊下 IOC,IoC(Inversion of Control, 控制翻转) 是 Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于 “第三方”(即 Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦 (通过 IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。IOC 是一个设计原则,而不是一个模式。
Spring IOC 容器就是一个大的工厂,把所有的 bean 实例都给放在了 spring 容器里,如果你要使用 bean,就找 spring 容器就可以了,自己不用创建对象了,具体流程如下图所示:
BeanFactory
与 ApplicationContext
的区别
BeanFactory
:延迟注入 (使用到某个 bean 的时候才会注入),相比于ApplicationContext
来说,会占用更少的内存,程序启动速度更快。ApplicationContext
:容器启动的时候,不管你用没用到,一次性创建所有的 bean 。
BeanFactory
仅提供了最基本的依赖注入支持,ApplicationContext
扩展了 BeanFactory
,除了有BeanFactory
的功能之外还有额外更多功能,所以一般开发人员使用ApplicationContext
会更多。
ApplicationContext 的三个实现类:
ClassPathXmlApplication
:把上下文文件当成类路径资源。FileSystemXmlApplication
:从文件系统中的 XML 文件载入上下文定义信息。XmlWebApplicationContext
:从 Web 系统中的 XML 文件载入上下文定义信息。
单例设计模式
Spring 中 bean 的默认作用域就是 singleton (单例)。
Spring 通过ConcurrentHashMap
实现单例注册表的方式来实现单例模式。
// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 此处省略了很多代码
try {
singletonObject = singletonFactory.getObject();
}
// 此处省略了很多代码
// 如果实例对象在不存在,我们注册到单例注册表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
//将对象添加到单例注册表
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
}
Spring 实现单例的两种方式:
- xml:
<bean scope="singleton"/>
- 注解:
@Scope(value = "singleton")
实际上,在我们的系统中,有一些对象其实只需要一个示例,比如说:线程池、缓存、注册表、日志对象、数据库连接池、显卡等设备驱动程序的对象。这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
另外,Spring 中 Bean,除了singleton
作用域还有下面几种作用域:
prototype
: 每次请求都会创建一个新的 bean 实例。request
: 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。session
: 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。global-session
: 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。
代理模式
Spring AOP 功能就是基于动态代理的实现的。
在 Spring 中,如果要代理的对象实现了某个接口,那么 Spring AOP 会使用 JDK Proxy ,去创建代理对象,而对于没有实现接口的对象,Spring AOP 会使用 Cglib,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
AOP(Aspect-Oriented Programming:面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
模板方法模式
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
在 Spring 中, jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板方法模式。
一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback(回调) 模式与模板方法模式配合,模板模式用于对一些不太变化的流程进行模板化,与回调模式结合,可以将变化的部分出离出来,使用回调模式实现。然后根据不同的情况,向 template 中注入不同的 callback,那些模板代码就没有必要重复写了。这样既达到了代码复用的效果,同时又增加了灵活性。
例如:Hibernate Template 提供了常规的 CRUD 操作,但是 Hibernate Template 的封装也使程序失去了 hibernate 中直接使用 Session 进行操作的灵活性,所以 Hibernate Template 提供了 execute(CallBack action)
等系列方法,允许程序员实现自己的HibernateCallBack
,实现具体的逻辑。
@FunctionalInterface
public interface HibernateCallback<T> {
/**
* Gets called by {@code HibernateTemplate.execute} with an active
* Hibernate {@code Session}. Does not need to care about activating
* or closing the {@code Session}, or handling transactions.
* <p>Allows for returning a result object created within the callback,
* i.e. a domain object or a collection of domain objects.
* A thrown custom RuntimeException is treated as an application exception:
* It gets propagated to the caller of the template.
*
* @param session active Hibernate session
* @return a result object, or {@code null} if none
* @throws HibernateException if thrown by the Hibernate API
* @see HibernateTemplate#execute
*/
@Nullable
T doInHibernate(Session session) throws HibernateException;
}
public interface HibernateOperations {
...
@Nullable
<T> T execute(HibernateCallback<T> action) throws DataAccessException;
...
}
public class HibernateTemplate implements HibernateOperations, InitializingBean {
@Override
@Nullable
public <T> T execute(HibernateCallback<T> action) throws DataAccessException {
return doExecute(action, false);
}
@Nullable
protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Session session = null;
boolean isNew = false;
...
try {
enableFilters(session);
Session sessionToExpose = (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
return action.doInHibernate(sessionToExpose);
}
...
finally {
if (isNew) {
SessionFactoryUtils.closeSession(session);
}
else {
disableFilters(session);
}
}
}
...
}
装饰者模式
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。
通常,Spring 中用到的装饰器模式,在类名上大都含有 Wrapper
或者 Decorator
,这些类基本都是动态地给对象添加额外的职责。
例如,DataSource 接口继承了 Wrapper 接口:
# 抽象角色
public interface DataSource extends CommonDataSource, Wrapper {
// 尝试与此数据源对象表示的数据源建立连接。
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password) throws SQLException;
}
# 装饰类
public class DelegatingDataSource implements DataSource, InitializingBean {
@Nullable
private DataSource targetDataSource;
public DelegatingDataSource() {
}
public DelegatingDataSource(DataSource targetDataSource) {
this.setTargetDataSource(targetDataSource);
}
public void setTargetDataSource(@Nullable DataSource targetDataSource) {
this.targetDataSource = targetDataSource;
}
@Nullable
public DataSource getTargetDataSource() {
return this.targetDataSource;
}
protected DataSource obtainTargetDataSource() {
DataSource dataSource = this.getTargetDataSource();
Assert.state(dataSource != null, "No 'targetDataSource' set");
return dataSource;
}
public void afterPropertiesSet() {
if (this.getTargetDataSource() == null) {
throw new IllegalArgumentException("Property 'targetDataSource' is required");
}
}
public Connection getConnection() throws SQLException {
return this.obtainTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.obtainTargetDataSource().getConnection(username, password);
}
public PrintWriter getLogWriter() throws SQLException {
return this.obtainTargetDataSource().getLogWriter();
}
public void setLogWriter(PrintWriter out) throws SQLException {
this.obtainTargetDataSource().setLogWriter(out);
}
public int getLoginTimeout() throws SQLException {
return this.obtainTargetDataSource().getLoginTimeout();
}
public void setLoginTimeout(int seconds) throws SQLException {
this.obtainTargetDataSource().setLoginTimeout(seconds);
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return iface.isInstance(this) ? this : this.obtainTargetDataSource().unwrap(iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this) || this.obtainTargetDataSource().isWrapperFor(iface);
}
public Logger getParentLogger() {
return Logger.getLogger("global");
}
}
还有,ServerHttpResponse
具有ServerHttpResponseDecorator
实现类。
// 装饰类
public class ServerHttpResponseDecorator implements ServerHttpResponse {
private final ServerHttpResponse delegate;
public ServerHttpResponseDecorator(ServerHttpResponse delegate) {
Assert.notNull(delegate, "Delegate is required");
this.delegate = delegate;
}
public ServerHttpResponse getDelegate() {
return this.delegate;
}
// ServerHttpResponse delegation methods...
@Override
public boolean setStatusCode(@Nullable HttpStatus status) {
return getDelegate().setStatusCode(status);
}
@Override
public HttpStatus getStatusCode() {
return getDelegate().getStatusCode();
}
@Override
public HttpHeaders getHeaders() {
return getDelegate().getHeaders();
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return getDelegate().getCookies();
}
@Override
public void addCookie(ResponseCookie cookie) {
getDelegate().addCookie(cookie);
}
@Override
public DataBufferFactory bufferFactory() {
return getDelegate().bufferFactory();
}
...
}
观察者模式
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。
Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如,我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
Spring 事件驱动模型中的三种角色
事件角色
ApplicationEvent 充当事件的角色, 这是一个抽象类,它继承了java.util.EventObject
。
Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent
的实现 (它继承自ApplicationEvent
):
ContextStartedEvent
:ApplicationContext
启动后触发的事件;ContextStoppedEvent
:ApplicationContext
停止后触发的事件;ContextRefreshedEvent
:ApplicationContext
初始化或刷新完成后触发的事件;ContextClosedEvent
:ApplicationContext
关闭后触发的事件。
事件监听者角色
ApplicationListener
充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()
方法来处理ApplicationEvent
。
ApplicationListener
接口类源码如下。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
在 Spring 中我们只要实现 ApplicationListener
接口的 onApplicationEvent()
方法即可完成监听事件。
事件发布者角色
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("你好")
,控制台就会打印出:接收到的信息是:你好
。
适配器模式
适配器模式 (Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。适配器分类适配器方式(继承方式)和对象适配器方式(使用了组合的方式,跟被适配对象整合)两种。
Spring AOP 中的适配器模式
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知 (Advice) 使用到了适配器模式,与之相关的接口是AdvisorAdapter
。
Advice 常用的类型有:
BeforeAdvice
(目标方法调用前, 前置通知)AfterAdvice
(目标方法调用后, 后置通知)
每个类型 Advice(通知)都有对应的拦截器: MethodBeforeAdviceInterceptor
、AfterReturningAdviceInterceptor
。
目标方法调用前,前置通知:
# 对象适配器
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
目标方法调用后,后置通知:
# 对象适配器
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
private final AfterReturningAdvice advice;
public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
public Object invoke(MethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
}
Spring 预定义的通知要通过对应的适配器,适配成 MethodInterceptor
接口 (方法拦截器) 类型的对象,如:MethodBeforeAdviceInterceptor
负责适配 MethodBeforeAdvice
。
Spring MVC 中的适配器模式
在 Spring MVC 中,DispatcherServlet
根据请求信息调用 HandlerMapping
,解析请求对应的 Handler
。解析到对应的 Handler
(也就是我们平常说的 Controller
控制器)后,开始由HandlerAdapter
适配器处理。HandlerAdapter
作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller
作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式?
Spring MVC 中的 Controller
种类众多,不同类型的 Controller
通过不同的方法来对请求进行处理。
如果不利用适配器模式的话,DispatcherServlet
直接获取对应类型的 Controller
,需要的自行来判断。
策略模式
Spring 框架的资源访问 Resource 接口就是基于策略设计模式实现的。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。
Spring 为 Resource 接口提供了如下实现类:
- UrlResource: 访问网络资源的实现类。
- ClassPathResource: 访问类加载路径里资源的实现类。
- FileSystemResource: 访问文件系统里资源的实现类。
- ServletContextResource: 访问相对于 ServletContext 路径里的资源的实现类.
- InputStreamResource: 访问输入流资源的实现类。
- ByteArrayResource: 访问字节数组资源的实现类。
这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。
责任链模式
在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。
发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链模式实现了请求者和处理者的解耦。
责任链中的两个角色
- Handle(抽象处理者):定义一个处理请求的接口。
- ConcreteHandler(具体处理者):处理请求的具体类,或者传给 “下家”。
Spring MVC 中很多地方使用了责任链模式,比如:过滤器(FilterChain
)。
public interface FilterChain {
/**
* Causes the next filter in the chain to be invoked, or if the calling
* filter is the last filter in the chain, causes the resource at the end of
* the chain to be invoked.
*
* @param request
* the request to pass along the chain.
* @param response
* the response to pass along the chain.
*/
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
/**
* Implementation of <code>javax.servlet.FilterChain</code> used to manage
* the execution of a set of filters for a particular request. When the
* set of defined filters has all been executed, the next call to
* <code>doFilter()</code> will execute the servlet's <code>service()</code>
* method itself.
*/
public final class ApplicationFilterChain implements FilterChain {
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Void>() {
@Override
public Void run()
throws ServletException, IOException {
internalDoFilter(req,res);
return null;
}
}
);
} catch( PrivilegedActionException pe) {
...
}
} else {
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
return;
}
...
}
/**
* Add a filter to the set of filters that will be executed in this chain.
*
* @param filterConfig The FilterConfig for the servlet to be executed
*/
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
for(ApplicationFilterConfig filter:filters)
if(filter==filterConfig)
return;
if (n == filters.length) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
}
public interface Filter {
/**
*
* @param filterConfig The configuration information associated with the
* filter instance being initialised
*
* @throws ServletException if the initialisation fails
*/
public default void init(FilterConfig filterConfig) throws ServletException {}
/**
*
* @param request The request to process
* @param response The response associated with the request
* @param chain Provides access to the next filter in the chain for this
* filter to pass the request and response to for further
* processing
*
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
总结
本文讲述了 Spring / SpringMVC 框架中使用的不同类型的设计模式,具体如下:
- 工厂模式 : Spring 使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 - 代理模式 : Spring AOP 功能的实现。
- 单例模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中
jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 - 装饰者模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式 : Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 : Spring AOP 的增强或通知(
Advice
)使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配Controller
。 - 策略模式 : Spring 中的资源访问根据不同策略访问任何底层对应的资源。
- 责任链模式 : Spring mvc 中过滤器就使用了责任链模式,对请求进行拦截处理。