工厂模式
Spring IOC就是通过工厂模式来实现的,将对象的创建和管理过程交由Spring去完成,主要通过beanFactory来实现,根据传入bean的名字来获取对象。
- 当spring启动时会先通过注解或xml配置获取bean的beanDefinition信息,然后将其放在springFactory的beanDefinitionMap中,key为bean的名字;
- 在这个地方Spring为为我们提供了一个切入点可以用来修改beanFactory,可以通过实现接口BeanFactoryPostProcessor的postProcessBeanFactory方法完成,例如修改beanfactory内beanDefinition之间的依赖关系等操作;也可通过BeanDefinitionRegisrryPostProcessor接口修改、删除、添加bean定义,例如mybatis就实现了该接口,添加mapper接口的bean定义
- 在bean的实例化过程中,执行构造方法,填充内部属性,完成初始化方法等操作,将完整bean信息放入三级缓存中的singleObejcts中存储
- 在实例化的过程中Spring也提供了一些扩展点,比如最常见的通过BeanPostProcessor接口的PostProcessBeforeInitialization和postProcessAfterInitialization在bean初始化动作前后做一些操作;例如Spring AOP的核心类AbstractAutoProxyCreator类,实现了SmartInstantiationAwareBeanPostProcessor接口,通过PostProcessBeforeInstantiation方法提供创建代理对象的机会,不过绝大部分代理对象是在执行完构造方法后,在放入第三级缓存前通过getEarlyBeanReference方法获取代理对象
这样做的好处是:
- 改变原来硬编码的依赖关系,通过Spring的beanFactory来实现bean的注入,实现了松耦合
- bean的额外处理,在bean的整个生命周期内提供了很多接口,支持我们做一些额外的处理。
单例模式
在使用Spring框架,服务启动过程中,通过Spring beanFactory创建、管理的bean都是单例的,每一个被beanFactory管理的类在Spring容器中仅有一个实例。下面给出常见的懒汉和饿汉方式创建单例bean
//懒汉
public class MySingleInstance {
private volatile static MySingleInstance mySingleInstance;
private MySingleInstance(){};
public static MySingleInstance getInstance() {
if (mySingleInstance == null) {
synchronized (MySingleInstance.class) {
if (mySingleInstance == null) {
mySingleInstance = new MySingleInstance();
}
}
}
return mySingleInstance;
}
}
大家可以想一下,为什么要使用volatile来修饰单例对象?主要与mySingleInstance = new MySingleInstance();这一句有关。创建MySingleInstance的过程并不是一个原子操作,它分为
- 在堆中为MySingleInstance对象分配空间
- 调用MySingleInstance()构造函数创建对象,填充内部属性,执行初始化方法
- 将mySingleInstance引用指向堆中的分配的内存地址
在jvm指令重排序的情况下,并不能保证按照1-2-3的顺序执行,有可能是1-3-2,如果在并发环境中,则有概率会报错。当执行完3之后,mySingleInstance就不指向null,此时如果B线程也想获取单例对象,就可以正常返回,但是由于对象的创建并没有完成,当B使用它获取到的单例对象进行一系列操作时就会报错。所以在懒汉模式中mySingleInstance之前加上了volatile关键字,禁止指令重排序,当执行到3时需要保证1和2均已完成。
//饿汉
private static MySingleInstance mySingleInstance = new MySingleInstance();
private MySingleInstance(){};
public static MySingleInstance getInstance() {
return mySingleInstance;
}
代理模式
AOP(面向切面编程)的底层就是使用代理模式实现的,将一些公共操作,如权限校验、日志打印、提交事务等操作封装成功公共模块,作为切面;将其放在切点前后,用于对切点做一系列的增强操作。
- 当类实现接口时,想要对其实现代理用的是JDK的方式,具体用Proxy类的new ProxyInstance方法获取代理对象,重写InvocationHandler接口的invoke方法实现具体的代理操作
- 当类没有实现接口时,利用CGLIB的方式完成代理,通过Enhancer创建代理对象,重写MethodInterceptor接口的intercept方法实现代理操作
可以看到其中很重要的一点就是获取代理对象,在Spring中AOP的核心类是AbstractAutoProxyCreator,它实现了InstantiationAwareBeanPostProcessor接口,重写postProcessBeforeInstantiation和getEarlyBeanReference、postProcessAfterInitialization方法来
创建具体的代理对象
观察者模式
观察者模式中包括三个部分 事件、事件源、监听器
- ApplicationEvent 事件,通过继承该类,可以自定义自己的事件
- ApplicationEventMulticaster,通过multicastEvent方法将事件广播给所有的事件监听器
- ApplicationListener事件监听器,继承该类重写其中的onApplicationEvent方法(或者直接使用@EventListener注解),完成对监听事件的处理
适配器模式
适配器模式可以通过新增一个适配器让两个原本不兼容的接口一起工作。比较常见的例如MethodBeforeAdviceInterceptor,它实现了MethodInterceptor接口,并且其中有一个MethodBeforeAdvice类型实例,
在inoke方法中调用advice的before方法,将这两个接口组合在一起工作,在方法执行之前完成一些操作,
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
private MethodBeforeAdvice advice;
/**
* Create a new MethodBeforeAdviceInterceptor for the given advice.
* @param advice the MethodBeforeAdvice to wrap
*/
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}
}
装饰器模式
通过提供一个装饰器,为现有接口提供功能扩展,但又不需要改动现有代码,支持层级关系。比如有一家饮品店,一开始仅仅卖普通咖啡,后来又可以向咖啡中加料,例如加牛奶、加摩卡等,支持客人多次添加,利用装饰器模式就可以很好的实现
public interface Drink {
//加料
String addMaterial();
}
//最初只有咖啡
public class Coffee implements Drink {
@Override
public String addMaterial() {
return "我是普通咖啡";
}
}
//实现一个装饰器
public class DrinkDecorator implements Drink {
private Drink drink;
public DrinkDecorator(Drink drink) {
this.drink = drink;
}
@Override
public String addMaterial() {
return drink.addMaterial();
}
}
//加摩卡
public class 摩卡 extends DrinkDecorator {
public 摩卡(Drink drink) {
super(drink);
}
@Override
public String addMaterial() {
return super.addMaterial() + ",加摩卡";
}
}
//加牛奶
public class 牛奶 extends DrinkDecorator {
public 牛奶(Drink drink) {
super(drink);
}
@Override
public String addMaterial() {
return super.addMaterial() + ",加牛奶";
}
}
//测试一下 客户点单
public static void main(String[] args) {
Coffee coffee = new Coffee();
//加点牛奶
牛奶 milk1 = new 牛奶(coffee);
//再加牛奶
牛奶 milk2 = new 牛奶(milk1);
//加摩卡
摩卡 moka = new 摩卡(milk2);
System.out.println(moka.addMaterial());
}
//结果 我是普通咖啡,加牛奶,加牛奶,加摩卡
- 在JDK中InputStream中就用到了装饰器模式,例如FilterInputStream就是一个装饰器,子类包括BufferedInputStream等,它们都在不改变InputStream代码的基础上完成了对InputStream功能的扩展
- 在Spring中装饰器也有较多的使用场景,例如TransactionAwareCacheDecorator,主要用于处理事务缓存,是Cache的一个装饰者类
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
/**
* Create a new TransactionAwareCache for the given target Cache.
* @param targetCache the target Cache to decorate
*/
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
.....
}
还有就是mybatis的 org.apache.ibatis.cache.Cache类也有很多装饰者模式场景,均在Cache同路径下的decorators包下,如FifoCache先进先出、LruCache 最近最少使用缓存等
模版方法模式
父类定义整体框架,包括调用哪些方法,方法执行顺序,在某些特定点提供抽象方法由子类根据自己的需要去实现。这样做的好处是代码复用,减少重复代码,除了子类要实现的特殊方法,其余方法的实现及调用顺序都是在父类写好的。
Spring中事务管理器 AbstractPlatformTransactionManager就包含有多个模版方法,比如提交事务、获取当前事务,在模版方法中会按照一定的顺序调用一些抽象方法,等待子类去实现,比较常见的子类有DataSourceTransactionManager等
//提交事务
public final void commit()
//获取TransactionStatus
public final TransactionStatus getTransaction()
策略模式
策略模式针对不同的场景封装不同的算法,用于应对不同的业务场景。Spring中获取资源时会用到Resource接口,它用很多的实现类,像UrlResource访问网络资源,ClassPathResource方法类加载本地资源,InputStreamResource访问输入流中的资源等。有一个方法叫做DefaultResourceLoader,其中包含一个getResource方法,可以根据资源路径选取不同的Resource
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}