Spring的生态演进变化
Spring是一款伟大的框架产品,在发展过程中一直都是靠一家叫做Pivotal的技术公司在背后支撑。Spring真正流行的时间是在2007年11月份,发布了2.5版本的时候。
Spring Source 在3.0升级为了后续的发展所以拆分为了Spring Framework4.0 发布于2013年,随后Spring Boot发布于2014年,和传统的Spring Framework有所不同,SpringBoot是一款完全独立的产品路线,很多设计都是在为了简化对于Spring的使用而发明的。
2014年发布了一个Spring Cloud ,这是业界第一个完整的微服务解决方案。早期时候以Spring Cloud Netflix为代。这款框架在2015年3月开源,2018年12.12日后进入了维护模式。
Netflix公司对外开源其实目的是:
- 想对外宣传,吸收外界的技术点,学习和完善现有的技术框架。
- 试探市场中对于这套技术解决方案的接受性。
- 挣钱。
2017年09月 Spring Framework发布了5.0
2019年08月01日 发布了Spring Cloud Alibaba 1.0
阿里巴巴和pivotal合作创建的一个框架标准
Spring用了哪些设计模式
首先来看下边这段代码案例:
package org.idea.spring.framework.http.util; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.StreamUtils; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; /** * @Author linhao * @Date created in 9:09 下午 2021/5/22 */ public class HttpRequestFactoryDemo { public static void main(String[] args) throws URISyntaxException, IOException { ClientHttpRequestFactory chrf = new SimpleClientHttpRequestFactory(); ClientHttpRequest clientHttpRequest = chrf.createRequest(new URI("http://www.baidu.com"), HttpMethod.GET); ClientHttpResponse clientHttpResponse = clientHttpRequest.execute(); InputStream inputStream = clientHttpResponse.getBody(); String response = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8")); inputStream.close(); System.out.println(response); } } 复制代码
一个简单的http发送,这里面主要使用的是Spring框架中的 ClientHttpRequestFactory 对象,这个对象也是RestTemplate中涉及到的一个重要组件,该对象在设计的时候,对http请求封装了一个工厂。例如我们的SimpleClientHttpRequestFactory 就是其中一种实现。从这个角度来看,这里采用了工厂相关的设计模式。
关于工厂模式
工厂模式包含了三种类型:
- 简单工厂模式
- 工厂方法模式 (这种用得不多,这里我直接略过)
- 抽象工厂模式
简单工厂模式
这种设计比较好理解,我写了个简单的案例如下:
public class SessionFactory { public Session getSession(){ /** 省略 **/ return new Session(); } } 复制代码
没有任何的接口定义,就是一个简单的Factory,专门负责生产指定的session。但是这种设计很明显存在扩展性的问题,假设后期需要融合更多种类的Session,就会出现以下情况:
public class SessionFactory { public Session getSession(){ return new Session(); } public RedisSession getRedisSession(){ return new RedisSession(); } public ZookeeperSession getZookeeperSession(){ return new ZookeeperSession(); } public WebSocketSession getWebSocketSession(){ return new WebSocketSession(); } } 复制代码
每次新增一个Session的类型都需要对SessionFactory这个父类做修改,这样的设计导致了SessionFactory这个类包含了多种业务职责,代码只会越堆越多,职责变得混乱。
抽象工厂模式
将原先的sessionFactory抽象成为一个接口,然后各种类的session都统一继承一个父类。大体如下:
public class Session { String name; public Session(String name) { this.name = name; } @Override public String toString() { return "Session{" + "name='" + name + '\'' + '}'; } } 复制代码
public class ZookeeperSession extends Session { public ZookeeperSession(String name){ super(name); } } public class WebSocketSession extends Session { public WebSocketSession(String name) { super(name); } } public class RedisSession extends Session { public RedisSession(String name) { super(name); } } 复制代码
SessionFactory模块改成:
public interface ISessionFactory { Session getSession(); } 复制代码
然后各自的子类进行基础
这样能够保证代码结构的设计遵守了开放封闭原则和依赖倒置原则。
好处:
从原先的单一工厂拆分为了多个工厂,不同工厂的含义不一样,满足了设计原则的单一职责。
将session的生成规则进行了封装,调用分不必关心session的生产过程。
不足点:
每次新增一个工厂都需要写一堆的代码。可以结合一些反射来进行优化。
Spring中的观察者模式
Spring内部的监听器使用场景:
package org.idea.spring.framework.event; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * 事件发布器 * * @Author linhao * @Date created in 4:47 下午 2021/5/23 */ public class ApplicationEventPublisherDemo implements ApplicationEventPublisherAware { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(ApplicationEventPublisherDemo.class); annotationConfigApplicationContext.addApplicationListener(new ApplicationListener<MyEvent>() { @Override public void onApplicationEvent(MyEvent event) { System.out.println("application listener 接收到消息:" + event); } }); annotationConfigApplicationContext.refresh(); annotationConfigApplicationContext.close(); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { System.out.println("===="); applicationEventPublisher.publishEvent(new MyEvent("hello world") { }); applicationEventPublisher.publishEvent("pay load event"); } } 复制代码
自定义一套属于自己的事件
package org.idea.spring.framework.event; import org.springframework.context.ApplicationEvent; /** * @Author linhao * @Date created in 6:21 下午 2021/5/23 */ public class MyEvent extends ApplicationEvent { /** * Create a new {@code ApplicationEvent}. * * @param source the object on which the event initially occurred or with * which the event is associated (never {@code null}) */ public MyEvent(Object source) { super(source); } } 复制代码
启动之后你会发现有如下消息:
其实代码里面发送了两条消息,但是监听器由于只是监听了MyEvent事件,所以这里没有将PayLoad事件打印出来。
这里所参考到的设计模式就是经典的观察者模式。
本质上观察者模式就是声明一个事件,然后观察者们都定于到这个事件中,并且使用一个类似list都数据结构将这些观察者们存放起来,最后通过遍历去触发它们对应的回调函数。类似的这种设计在JDK8中已经有提供标准的api,代码案例如下:
package org.idea.spring.framework.event; import java.util.EventListener; import java.util.EventObject; import java.util.Observable; import java.util.Observer; /** * @Author linhao * @Date created in 3:52 下午 2021/5/23 */ public class EventDemo { public static void main(String[] arg) { EventObservable observable = new EventObservable(); observable.addObserver(new EventObserver()); observable.notifyObservers("send message"); } static class EventObservable extends Observable { @Override protected synchronized void setChanged() { super.setChanged(); } @Override public void notifyObservers(Object data){ this.setChanged(); super.notifyObservers(new EventObject(data)); clearChanged(); } } static class EventObserver implements Observer, EventListener { @Override public void update(Observable o, Object msg) { EventObject eventObject = (EventObject) msg; System.out.println("接收数据:" + eventObject); } } } 复制代码
关于事件部分的整体设计如下图所示:
Spring中的模版模式
在Spring内部源代码中,模版模式也是使用非常多的一种设计,例如我们的事务管理部分:
org.springframework.transaction.PlatformTransactionManager
org.springframework.transaction.support.AbstractPlatformTransactionManager
这里我以我们常用的DataSourceTransactionManager作为讲解案例分析:
DataSourceTransactionManager通常会在我们的数据库访问中处理一些和事务有关的会话信息,例如提交,回滚。那么这些统一的提交回滚方法其实都是由其父类所制定的。
这三段源代码主要目的就是查询对应url匹配到适配器,从而处理后台的响应逻辑部分代码。
Spring内部的策略模式
在之前我有一篇文章中介绍了关于Spring容器内部加载资源属性的一些技巧使用,其中有提到过Resource这个接口,其实Resource接口就是一层封装,对于不同的资源处理有不同的子类实现。
Spring中获取资源的方式一共有以下四种:
- 通过Resource接口获取资源
- 通过ResourceLoader接口获取资源
- 通过ApplicationContext获取资源
- 将resource注入到bean中的方式获取资源
实现类 | 描述 |
ClassPathResource | 通过类路径获取资源文件 |
FileSystemResource | 通过文件系统获取资源 |
UrlResource | 通过URL地址获取资源 |
ByteArrayResource | 获取字节数组封装的资源 |
ServletContextResource | 获取ServletContext环境下的资源 |
InputStreamResource | 获取输入流封装的资源 |
这部分我们可以通过一个代码案例来实践理解下:
package org.idea.spring.resource; import org.springframework.core.io.*; /** * @Author linhao * @Date created in 7:38 下午 2021/5/23 */ public class ResourceLoaderDemo { public static void main(String[] args) { ResourceLoader loader = new DefaultResourceLoader(); Resource resource = loader.getResource("http://www.baidu.com"); System.out.println(resource instanceof UrlResource); Resource textResource = loader.getResource("classpath:test.txt"); System.out.println(textResource instanceof ClassPathResource); } } 复制代码
从这段代码的执行结果可以看出ResourceLoader会根据我们输入到字符串规则来匹配不同的资源加载规则。
而实际上呢,当我们进入源代码去debug查阅到时候也会发现确实逻辑是这么实现的。
在实际应用中,我们也可以从ApplicationContext中去获取Resource对象,代码如下:
public class MyResource implements ApplicationContextAware { private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public void resource() throws IOException { Resource resource = applicationContext.getResource("file:D:\\test.txt"); System.out.println(resource.getFilename()); System.out.println(resource.contentLength()); } } 复制代码
Spring内部的代理模式
代理模式这块比较代表性的就是aop这部分了,解决代码复用,公共函数抽取,简化开发,业务之间的解耦;例如日志功能,因为日志功能往往横跨系统中的每个业务模块,使用 AOP 可以很好的将日志功能抽离出来。
关于Spring内部的AOP的代理实现主要有cglib和JDK两种方式,源代码核心部分如下:
这部分代码可能有不少读者会有疑惑:
为什么代码中那么推崇JDK代理,而不是CGLIB代理呢,不是说CGLIB代理要比JDK代理效率更高吗?
误区解释:我在初期学习CGLIB和JDK代理的时候在网上看了不少的资料都说CGLIB代理的效率要比JDK代理高很多,但是通过实战之后发现,高版本的JDK(如JDK8)已经对其自身的代理机制做了优化,性能要比CGLIB更佳。
另外在Spring的官网上也看到了这么一段话:
关于代理部分的总结
1.默认使用 JDK 动态代理,这样便可以代理所有的接口类型(interface)
2.Spring AOP也支持CGLIB的代理方式。如果我们被代理对象没有实现任何接口,则默认是CGLIB
3.我们可以强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)
其实关于Spring内部的设计模式使用场景还有很多,大部分都是多种设计模式的混合使用,例如BeanFactory模块的设计。希望今天的这篇文章能够对你有所帮助。