抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

思维导图

文章已收录Github精选,欢迎Starhttps://github.com/yehongzhi/learningSummary

概述

一个优秀的框架肯定离不开各种设计模式的运用,Spring框架也不例外。因为网上很多文章比较散乱,所以想总结一下在Spring中用到的设计模式,希望大家看完之后能对spring有更深层次的理解。

工厂模式

工厂模式我们都知道是把创建对象交给工厂,以此来降低类与类之间的耦合。工厂模式在Spring中的应用非常广泛,这里举的例子是ApplicationContext和BeanFactory,这也是Spring的IOC容器的基础。

首先看BeanFactory,这是最底层的接口。

1
2
3
4
5
6
7
8
9
10
11
12
public interface BeanFactory {
Object getBean(String name) throws BeansException;

<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

Object getBean(String name, Object... args) throws BeansException;

<T> T getBean(Class<T> requiredType) throws BeansException;

<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//省略...
}

ApplicationContext则是扩展类,也是一个接口,他的作用是当容器启动时,一次性创建所有的bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();

String getApplicationName();

String getDisplayName();

long getStartupDate();

@Nullable
ApplicationContext getParent();

AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

ApplicationContext有三个实现类,分别是:

  • ClassPathXmlApplication:从类路径ClassPath中寻找指定的XML配置文件,找到并装载ApplicationContext的实例化工作。
  • FileSystemXMLApplicationContext:从指定的文件系统路径中寻找指定的XML配置文件,找到并装载ApplicationContext的实例化工作。
  • XMLWebApplicationContext:从Web系统中的XML文件载入Bean定义的信息,Web应用寻找指定的XML配置文件,找到并装载完成ApplicationContext的实例化工作。

因此这几个类的关系我们清楚了,类图就是这样:

在哪里初始化呢,这讲起来有些复杂,就不展开细讲,提一下。主要看AbstractApplicationContext类的refresh()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//省略...
try {
//省略...
//初始化所有的单实例 Bean(没有配置赖加载的)
finishBeanFactoryInitialization(beanFactory);
}catch (BeansException ex) {
//省略...
}finally {
//省略...
}
}
}

单例模式

在系统中,有很多对象我们都只需要一个,比如线程池、Spring的上下文对象,日志对象等等。单例模式的好处在于对一些重量级的对象,省略了创建对象花费的时间,减少了系统的开销,第二点是使用单例可以减少new操作的次数,减少了GC线程回收内存的压力

实际上,在Spring中的Bean默认的作用域就是singleton(单例)的。如何实现的呢?

主要看DefaultSingletonBeanRegistry的getSingleton()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** 保存单例Objects的缓存集合ConcurrentHashMap,key:beanName --> value:bean实例 */
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 {
//通过singletonFactory获取单例
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
//省略...
if (newSingleton) {
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);
}
}
}

从源码中可以看出,是通过ConcurrentHashMap的方式,如果在Map中存在则直接返回,如果不存在则创建,并且put进Map集合中,并且整段逻辑是使用同步代码块包住的,所以是线程安全的。

策略模式

策略模式,简单来说就是封装好一组策略算法,外部客户端根据不同的条件选择不同的策略算法解决问题。这在很多框架,还有日常开发都会用到的一种设计模式。在Spring中,我这里举的例子是Resource类,这是所有资源访问类所实现的接口。

针对不同的访问资源的方式,Spring定义了不同的Resource类的实现类。我们看一张类图:

简单介绍一下Resource的实现类:

  • UrlResource:访问网络资源的实现类。
  • ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类。
  • ByteArrayResource:访问字节数组资源的实现类。
  • PathResource:访问文件路径资源的实现类。
  • ClassPathResource:访问类加载路径里资源的实现类。

写一段伪代码来示范一下Resource类的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RequestMapping(value = "/resource", method = RequestMethod.GET)
public String resource(@RequestParam(name = "type") String type,
@RequestParam(name = "arg") String arg) throws Exception {
Resource resource;
//这里可以优化为通过工厂模式,根据type创建Resource的实现类
if ("classpath".equals(type)) {
//classpath下的资源
resource = new ClassPathResource(arg);
} else if ("file".equals(type)) {
//本地文件系统的资源
resource = new PathResource(arg);
} else if ("url".equals(type)) {
//网络资源
resource = new UrlResource(arg);
} else {
return "fail";
}
InputStream is = resource.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
int i;
while ((i = is.read()) != -1) {
os.write(i);
}
String result = new String(os.toByteArray(), StandardCharsets.UTF_8);
is.close();
os.close();
return "type:" + type + ",arg:" + arg + "\r\n" + result;
}

这就是策略模式的思想,通过外部条件使用不同的算法解决问题。其实很简单,因为每个实现类的getInputStream()方法都不一样,我们看ClassPathResource的源码,是通过类加载器加载资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ClassPathResource extends AbstractFileResolvingResource {

private final String path;

@Nullable
private ClassLoader classLoader;

@Nullable
private Class<?> clazz;

@Override
public InputStream getInputStream() throws IOException {
InputStream is;
//通过类加载器加载类路径下的资源
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
//如果输入流is为null,则报错
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
//返回InputStream
return is;
}
}

再看UrlResource的源码,获取InputStream的实现又是另一种策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class UrlResource extends AbstractFileResolvingResource {
@Nullable
private final URI uri;

private final URL url;

private final URL cleanedUrl;

@Override
public InputStream getInputStream() throws IOException {
//获取连接
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try {
//获取输入流,并返回
return con.getInputStream();
}
catch (IOException ex) {
// Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection) {
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
}

代理模式

Spring除了IOC(控制反转)之外的另一个核心就是AOP(面向切面编程)。AOP能够将与业务无关的,却被业务模块所共同调用的逻辑(比如日志,权限控制等等)封装起来,减少系统重复代码,降低系统之间的耦合,有利于系统的维护和扩展。

Spring AOP主要是基于动态代理实现的,如果要代理的类,实现了某个接口,则使用JDK动态代理,如果没有实现接口则使用Cglib动态代理。

我们看DefaultAopProxyFactory的createAopProxy()方法,Spring通过此方法创建动态代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
}

JDK动态代理和Cglib动态代理的区别:

  • JDK动态代理只能对实现了接口的类生成代理,没有实现接口的类不能使用。
  • Cglib动态代理即使被代理的类没有实现接口,也可以使用,因为Cglib动态代理是使用继承被代理类的方式进行扩展。
  • Cglib动态代理是通过继承的方式,覆盖被代理类的方法来进行代理,所以如果方法是被final修饰的话,就不能进行代理。

从源码中可以看出,Spring会先判断是否实现了接口,如果实现了接口就使用JDK动态代理,如果没有实现接口则使用Cglib动态代理,也可以通过配置,强制使用Cglib动态代理,配置如下:

1
<aop:aspectj-autoproxy proxy-target-class="true"/>

模板模式

模板模式在Spring中用得太多了,它定义一个算法的骨架,而将一些步骤延迟到子类中。 一般定义一个抽象类为骨架,子类重写抽象类中的模板方法实现算法骨架中特定的步骤。模板模式可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

Spring中的事务管理器就运用模板模式的设计,首先看PlatformTransactionManager类。这是最底层的接口,定义提交和回滚的方法。

1
2
3
4
5
6
7
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

void commit(TransactionStatus status) throws TransactionException;

void rollback(TransactionStatus status) throws TransactionException;
}

毫无意外,使用了抽象类作为骨架,接着看AbstractPlatformTransactionManager类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Override
public final void commit(TransactionStatus status) throws TransactionException {
//省略...
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
//省略...
//调用processRollback()
processRollback(defStatus, false);
return;
}

if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
//省略...
//调用processRollback()
processRollback(defStatus, true);
retur