Spring中的设计模式
目录
控制反转(Ioc)和依赖注入(DI)
工厂设计模式
代理模式设计
代理模式在AOP的应用
模版方法
观察者模式
Spring事件驱动模型中的三种角色
Spring的事件流程总结
适配器模式
Spring AOP中的适配器模式
Spring MVC中的适配器模式
Spring中用到了那些设计模式?这个问题很常见。另外本篇文章的主要目的是回顾一下Spring中的设计模式。
控制反转(Ioc)和依赖注入(DI)
Ioc控制反转是Spring中一个非常重要的概念,他不是什么技术,而是一种解耦思想,Ioc的主要目的是借助于第三方(Spring中的Ioc容器)实现具有依赖关系的对象之间的解耦(Ioc容器管理对象,你只管使用即可),从而降低了代码之间的耦合度。
Ioc是一个原则,而不是一个模式,以下模式(但不限于)实现了Ioc原则。
Spring Ioc容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解接口,完全不用考虑对象是如何被创建出来的。Ioc容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到他们被完全销毁。
在实际项目中一个Service类如果有甚至上千个类作为它的底层,我们需要实例化这个Service,你可能要每次都要搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯,如果利用Ioc的话,你只需要配置好,然后在需要引用的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
控制反转怎么理解呢?举一个例子,对象a依赖了对象b,当对象a去使用对象b的时候需要自己手动创建一个对象b。但是当系统引入了Ioc容器的时候,对象a和对象b之间就失去了直接联系,这个时候,当对象a需要使用对象b的时候,我们可以指定IOC容器去创建一个对象b到对象a中去。对象a获得对象b的过程由主动行为变成了被动行为,控制权反转,这个就是控制反转名字的由来。
DI(Dependency Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。
工厂设计模式
Spring使用工厂模式可以通过BeanFactory或ApplicationContext创建对象。
两者对比:
BeanFactory:延迟注入(使用到某个Bean的时候才会注入),相比于ApplicationContext来说会占用更少的内存,程序启动速度更快。
ApplicationContext:容器启动的时候,不管你有没有用到,一次性创建所有bean,BeanFactory仅提供了最基本的依赖注入支持,ApplicationContext扩展了BeanFactory,除了有BeanFactory的功能还有额外的功能,所以一般都是使用ApplicationContext会更多。
ApplicationContext的三个实现类:
1.ClassPathXmlApplication:把上下文文件当成类路径资源。
2.FileSystemXmlApplication:从文件系统中的XML文件载入上下文定义的信息。
3.XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;public class App {public static void main(String[] args) {ApplicationContext context = new FileSystemXmlApplicationContext("C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");obj.getMsg();}
}
单例设计模式
在我们系统中,有一些对象其实我们只需要一个,比如说:线程池,缓存,对话框,注册表,日志对象,充当打印机,显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能导致出现一些问题,比如:程序的行为异常,资源使用过量,或者不一致的结果。
使用单例模式的好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔开销。
- 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这减轻GC压力,缩短GC停顿时间。
Spring中bean的默认作用就是单例的,除了singleton作用域,Spring中bean还有下面几种作用域:
prototype:每次获取都会创建一个新的对象,连续getBean()两次,得到的是不同的Bean实例。
request(仅 Web 应用可用):每一次HTTP请求都会产生一个新的bean(bean请求),该bean仅在当前HTTP request内有效。
session(仅 Web 应用可用):每一次来自新的session的HTTP请求都会产生一个新的bean(回话bean),该bean仅在当前HTTP session内有效。
application/global-session(仅Web应用可用):每个Web应用在启动的时候创建一个bean(应用bean),该bean仅在当前应用启动时间内有效。
websocket(仅Web应用可用):每一次Websocket会话产生一个新的bean。
Spring通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式。
Spring实现单例的核心代码如下:
// 通过 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));}}
}
单例Bean存在线程安全问题吗?
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例Bean存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
1.在Bean中尽量避免定义可变的成员变量。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
不过大部分Bean都是无状态(没有实例变量)的(比如Dao,Service),这种情况下,Bean是线程安全的。
代理模式设计
代理模式在AOP的应用
AOP(面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理,日志管理,权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring Aop就是基于动态代理的,如果有代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy去创建代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy去进行代理了,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理,如下图所示:
模版方法
模版方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
public abstract class Template {//这是我们的模板方法public final void TemplateMethod(){PrimitiveOperation1();PrimitiveOperation2();PrimitiveOperation3();}protected void PrimitiveOperation1(){//当前类实现}//被子类实现的方法protected abstract void PrimitiveOperation2();protected abstract void PrimitiveOperation3();}
public class TemplateImpl extends Template {@Overridepublic void PrimitiveOperation2() {//当前类实现}@Overridepublic void PrimitiveOperation3() {//当前类实现}
}
Spring中的JdbcTemplate,HibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模版模式。一般情况下,我们都是使用继承方式来实现模版模式,但是Spring并没有使用这种方式,而是使用CallBack模式与模版方法模式配合,既达到了代码复用的效果,同时也增加了灵活性。
观察者模式
观察者模式是一种对象行为模式,它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,依赖这个对象的所有对象也会做出反应。Spring时间驱动模型就是观察者模式很经典的一个应用。Spring事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加 商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
Spring事件驱动模型中的三种角色
事件角色
ApplicationEvent充当事件的角色,这就是一个抽象类,它继承了java.util.EventObject并实现了java.io.Serializable接口。
Spring中默认存在以下事件,他们都是对ApplicationContextEvent的实现(继承自ApplicationContext):
ContextStartedEvent:ApplicationContext启动后触发的事件。
ContextStoppedEvent:ApplicationContext停止后触发的事件。
ContextRefreshedEvent:ApplicationContext初始化或刷新完成后触发的事件。
ContextClosedEvent:ApplicationContext关闭后触发的事件。
事件监听者角色
ApplicationListener充当了事件监听者角色,他是一个接口,里面只定义了一个onApplicationEvent()方法来处理ApplicationEvent。ApplicationListener接口类源码下,可以看出接口定义看出接口中的事件只要实现了ApplicationEvent就可以了。所以,在Spring中我们只要实现ApplicationListener接口的onApplicationEvent()方法即可完成监听事件。
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E var1);
}
事件发布者角色
ApplicationEventPublisher充当了事件的发布者,他也是一个接口。
@FunctionalInterface
public interface ApplicationEventPublisher {default void publishEvent(ApplicationEvent event) {this.publishEvent((Object)event);}void publishEvent(Object var1);
}
ApplicationEventPublisher接口的publisherEvent这个方法在AbstractApplicationContext类中被实现,阅读这个方法的实现,你就会发现实际上事件真正是通过ApplicationEventMulticaster来广播出去的。
Spring的事件流程总结
1.定义一个事件:实现一个继承自ApplicationEvent,并且写相应的构造函数。
2.定义一个事件监听者:实现ApplicationListener接口,重写onApplicationEvent方法。
3.使用事件发布者发布消息,可以通过ApplicationEventPublisher的publishEvent()方法发布消息。
Example:
// 定义一个事件,继承自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接收消息@Overridepublic void onApplicationEvent(DemoEvent event) {String msg = event.getMessage();System.out.println("接收到的信息是:"+msg);}}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {@AutowiredApplicationContext applicationContext;public void publish(String message){//发布事件applicationContext.publishEvent(new DemoEvent(this, message));}
}
当调用DemoPublisher的publish()方法的时候,比如demoPublisher.publish("你好"),控制台就会打印出:接收到的消息是:你好。
适配器模式
适配器模式将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。
Spring AOP中的适配器模式
我们知道Spring AOP的实现是基于代理模式,但是Spring AOP的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter。
Advice常用的类型有:BeforedAdvice(目标方法调用前,前置通知),AfterAdvice(目标方法调用后,后置通知),AfteReturningAdvice(目标方法执行结束后,return之前)等等。每个类型Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor,AfterReturningAdviceInterceptor,ThrowsAdviceInterceptor等等。
Spring MVC中的适配器模式
在Spring MVC中,DispatchServlet根据我们请求信息调用HandlerMapping,解析请求对应的Handler。解析到对应的Handler(也就是我们常说的Controller控制器)后,开始由HandlerAdapter适配器处理。HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller作为需要适配的类。
为什么要在Spring MVC中使用适配器模式?
Spring MVC中的Controller种类众多,不同类型的Controller通过不同的办法来请求进行处理。如果不利于适配器模式的会,DispatchServlet直接获取对应类型的Controller,需要自行判断。
if(mappedHandler.getHandler() instanceof MultiActionController){((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){...
}else if(...){...
}