Spring中的设计模式
# 一.控制反转与依赖注入
首先IOC是一个原则,而不是一种设计模式。它是一种解耦的思想,没有IOC之前,创建对象的职责是开发人员主动来创建对象。但是当代码量越来越多时,对象与对象之间的关系越来越复杂,如果这时继续由开发人员来new一个对象,很容易被其中复杂的关系而误导。IOC就是来解决这个问题,将创建对象的职责转交给IOC容器,从而降低代码之间的耦合度。
IOC容器就像一个工厂一样,我们只需要配置好配置文件或注解即可,不需要关心对象是如何创建嗯的,容器会帮助我们创建,我们只需要进行使用即可。
那依赖注入又有什么作用呢?
DI 是为了实现控制反转的一种设计模式,通俗点讲就是将一个实例注入到指定的对象当中。
# 二.工厂模式
# 1. 简单工厂模式
首先,通过对Spring的使用,我们知道spring中使用工厂模式的主要有两个:BeanFactory
和ApplicationContext
。
首先看看BeanFactory
BeanFactory是一个接口,其中定义了一些方法,都是通过简单工厂模式
去获得我们想要的对象:
另外一个是ApplicationContext,它扩展了BeanFactory,提供了更多的功能。当然其获得Bean的方式也是基于简单工厂模式。
这两种都使用了工厂模式,但是他们有什么区别呢?
BeanFactory
: 延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext
来说会占用更少的内存,程序启动速度更快。
ApplicationContext
:容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory
仅提供了最基本的依赖注入支持,ApplicationContext
扩展了 BeanFactory
,除了有BeanFactory
的功能还有额外更多功能,比如事件监听发布
、Bean前后置处理器
所以一般开发人员使用ApplicationContext
会更多。
另外需要我们了解一下BeanFactory继承体系的几个重要的类:
ClassPathXmlApplication
:把上下文类当作类加载的资源。FileSystemXmlApplication
:指定配置文件路径完成信息加载。XmlWebApplicationContext
:从web
系统中载入上下文信息。
# 2. 工厂方法模式
首先,工厂方法是具体工厂重写抽象工厂方法,并实现具体的创建对象逻辑,客户端引用抽象工厂调用创建对象的方法即可。
答: 工厂方法模式适用于想让工厂专注创建一个对象的场景,相较于简单工厂模式,工厂方法模式思想是提供一个工厂的接口,开发者根据这个规范创建不同的工厂,然后按需使用不同的工厂创建不同的类即可。这种做法确保了工厂类也遵循开闭原则。
Spring
中的FactoryBean
就是工厂方法模式的典型实现,如果我们希望容器中能够提供一个可以创造指定类的工厂,那么我们就可以通过FactoryBean
实现。 例如我们希望有一个工厂可以创建经理,另一个工厂可以创建主管。那么我们就可以通过FactoryBean
实现。 实现步骤如下,由于经理和主管都是雇员类,所以我们创建一个雇员类
//雇员类
public class EmployeeDTO {
private Integer id;
private String firstName;
private String lastName;
private String designation;
}
2
3
4
5
6
7
8
然后我们继承FactoryBean
接口实现一个工厂方法类,如下所示,可以看到如果我们可以根据传入的designation
决定创建的雇员类型。
public class EmployeeFactoryBean extends AbstractFactoryBean<Object> {
// 根据这个值决定创建主管还是经理
private String designation;
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
//This method will be called by container to create new instances
@Override
protected Object createInstance() throws Exception {
EmployeeDTO employee = new EmployeeDTO();
employee.setId(-1);
employee.setFirstName("dummy");
employee.setLastName("dummy");
//Set designation here
employee.setDesignation(designation);
return employee;
}
//This method is required for autowiring to work correctly
@Override
public Class<EmployeeDTO> getObjectType() {
return EmployeeDTO.class;
}
}
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
雇员的配置
<!--factoryBean使用示例-->
<!--经理工厂-->
<bean id="manager" class="com.study.service.EmployeeFactoryBean">
<property name="designation" value="Manager" />
</bean>
<!--主管工厂-->
<bean id="director" class="com.study.service.EmployeeFactoryBean">
<property name="designation" value="Director" />
</bean>
2
3
4
5
6
7
8
9
10
如果我们想创建director(主管)
的工厂,那么我们的代码就可以这样使用,注意我们获取bean
时必须使用&
,否则获得的就不是EmployeeFactoryBean
,则是EmployeeDTO
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Object factory = context.getBean("&director");
System.out.println(factory);
//工厂方法模式,通过单一职责的工厂获取专门的类
System.out.println(((EmployeeFactoryBean) factory).getObject());
2
3
4
5
6
当然,如果想直接获取高管
或者经理
,获取bean
时不加&
即可代码如下所示即可:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
EmployeeDTO manager = (EmployeeDTO) context.getBean("manager");
System.out.println(manager);
Object director = context.getBean("director");
System.out.println(director);
2
3
4
5
6
7
上面介绍了与工厂相关的两种模式,简单工厂和工厂方法,那么两种有什么区别呢?
# 3. 工厂方法与简单工厂的区别
能不能说说工厂方法模式相较于简单工厂模式的优缺点呢?
答: 优点嘛,符合开闭原则,相较于上面说到的简单工厂模式来说,我们无需因为增加一个类型而去修改工厂代码,我们完全可以通过实现一个新的工厂实现。而且对于单个类型创建的工厂逻辑更加易于维护。 缺点也很明显,创建的类明显多了。
# 三.单例模式
# 1.在spring中bean默认就是单例的
来看看spring中是怎么创建单例bean的
// 通过 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) {
// 从完全体缓存中查看是否存在到单例bean
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));
}
}
}
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
其实就是看单例池中是否注册了该bean,如果没有注册,就通过工厂创建一个新的bean并注册到单例池中。
# 2.使用单例bean有什么有优势呢?
- 节省没必要的创建对象的时间,由于是单例的对象,所以创建一次后就可以一直使用了,所以我们无需为了一个重量级对象的创建而耗费大量的资源。
- 由于重量级对象的创建少了,所以我们就避免了没必要的GC。从而降低GC压力,避免没必要的
STW(Stop the World)
导致的GC停顿。
# 3.单例Bean是线程安全的吗?
单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
解决方法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。 - 当然有些变量没必要在全局创建,我们也可以通过栈封闭技术确保线程安全。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
什么是栈封闭技术?
栈封闭简单理解就是通过局部变量来实现线程封闭,多个线程访问对象的同一个方法,方法内部的局部变量会拷贝到每个线程的线程栈当中,只有当前线程才能访问到,互不干扰。 所以局部变量是不被多个线程所共享的。
线程封闭是保证线程安全的一种方法。线程封闭具体实现有Ad-hoc线程封闭、栈封闭、ThreadLocal,具体1可以查询以下资料:线程封闭之ThreadLocal和栈封闭 (opens new window)
# 4.除了单例,还有哪些状态?
singleton
:单例bean
。prototype
:每次获取都是一个全新的bean
,也就是说两次getBean
用到的是不同的bean
。request(仅web可用)
:每一次HTTP
请求就会获得一个新的bean
,这个bean
仅对当前的请求有效。session(仅web可用)
:每一个新的session
对应一个bean
,该bean
仅对本次session
有效。application/global-session (仅 Web 应用可用)
:每一次容器启动产生bean
,该bean
仅在本此启动工作期间有效。websocket (仅 Web 应用可用)
:每一次WebSocket
会话产生一个新的 bean。
# 四.代理模式
代理模式主要体现在AOP面向切面增强上
AOP(Aspect-Oriented Programming,面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
当然,你也可以使用 AspectJ ,Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
# Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
# 五.模版方法
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
Spring 中 JdbcTemplate
、HibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
Template定义了问题的边界,子类定义了具体的实现
@Override
public void refresh() throws BeansException, IllegalStateException {
// 给容器refresh加锁,避免容器处在refresh阶段时,容器进行了初始化或者销毁的操作
synchronized (this.startupShutdownMonitor) {
// .........
try {
//定义了相关接口给用户实现,该方法会通过回调的方式调用这些方法,有点模板方法的味道
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建过程的BeanPostProcessor
registerBeanPostProcessors(beanFactory);
//模板方法的体现,用户可自定义重写该方法
onRefresh();
//.......
}
// .......
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当然Spring
的模板方法也不是标准的模板方法,还有一点用到类似于回调机制(Callback)
例如initializeBean
实际上是把bean
初始化的方法逻辑抽象成类交给用户实现类实现,并非抽象一个方法供用户修改
exposedObject = initializeBean(beanName, exposedObject, mbd);
步入代码可以看到,他将某些固定的逻辑抽成接口,提供用户实现,然后容器通过回调的方式processor.postProcessBeforeInitialization(result, beanName)
实现调用
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
//从容器中获取各种BPP处理器
for (BeanPostProcessor processor : getBeanPostProcessors()) {
//调用bpp处理器对bean进行后置处理
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 六.观察者模式
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
# Spring 事件驱动模型中的三种角色
# 事件角色
ApplicationEvent
(org.springframework.context
包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject
并实现了 java.io.Serializable
接口。
Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent
的实现(继承自ApplicationContextEvent
):
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);
}
2
3
4
5
6
# 事件发布者角色
ApplicationEventPublisher
充当了事件的发布者,它也是一个接口。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
2
3
4
5
6
7
8
ApplicationEventPublisher
接口的publishEvent()
这个方法在AbstractApplicationContext
类中被实现,阅读这个方法的实现,你会发现实际上事件真正是通过ApplicationEventMulticaster
来广播出去的。具体内容过多,就不在这里分析了,后面可能会单独写一篇文章提到。
# Spring 的事件流程总结
- 定义一个事件: 实现一个继承自
ApplicationEvent
,并且写相应的构造函数; - 定义一个事件监听者:实现
ApplicationListener
接口,重写onApplicationEvent()
方法; - 使用事件发布者发布消息: 可以通过
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接收消息
@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));
}
}
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
当调用 DemoPublisher
的 publish()
方法的时候,比如 demoPublisher.publish("你好")
,控制台就会打印出:接收到的信息是:你好
。
# 案例
# coding~~~~~~
1、定义事件
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
System.out.println("my Event");
}
}
复制代码
2
3
4
5
6
7
2、实现事件监听器
@Component
class MyListenerA implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent AyEvent) {
System.out.println("ListenerA received");
}
}
@Component
class MyListenerB implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent AyEvent) {
System.out.println("ListenerB received");
}
}
复制代码
2
3
4
5
6
7
8
9
10
11
12
13
14
3、事件发布者
@Component
public class MyPublisher implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public void publishEvent(ApplicationEvent event){
System.out.println("publish event");
applicationContext.publishEvent(event);
}
}
复制代码
2
3
4
5
6
7
8
9
10
11
12
13
14
4、测试,先用注解方式将 MyPublisher 注入 Spring
@Configuration
@ComponentScan
public class AppConfig {
@Bean(name = "myPublisher")
public MyPublisher myPublisher(){
return new MyPublisher();
}
}
复制代码
public class Client {
@Test
public void main() {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyPublisher myPublisher = (MyPublisher) context.getBean("myPublisher");
myPublisher.publishEvent(new MyEvent(this));
}
}
复制代码
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5、输出
my Event
publish event
ListenerA received
ListenerB received
2
3
4