乐趣区

关于java:Spring-设计模式介绍

JDK 中用到了那些设计模式?Spring 中用到了那些设计模式? 这两个问题,在面试中比拟常见。我在网上搜寻了一下对于 Spring 中设计模式的解说简直都是千篇一律,而且大部分都年代久远。所以,花了几天工夫本人总结了一下,因为我的集体能力无限,文中如有任何谬误各位都能够指出。另外,文章篇幅无限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的次要目标是回顾一下 Spring 中的设计模式。

Design Patterns(设计模式) 示意面向对象软件开发中最好的计算机编程实际。Spring 框架中宽泛应用了不同类型的设计模式,上面咱们来看看到底有哪些设计模式?

管制反转 (IoC) 和依赖注入(DI)

IoC(Inversion of Control, 管制反转) 是 Spring 中一个十分十分重要的概念,它不是什么技术,而是一种解耦的设计思维。它的次要目标是借助于“第三方”(Spring 中的 IOC 容器) 实现具备依赖关系的对象之间的解耦(IOC 容器治理对象,你只管应用即可),从而升高代码之间的耦合度。IOC 是一个准则,而不是一个模式,以下模式(但不限于)实现了 IoC 准则。

Spring IOC 容器就像是一个工厂一样,当咱们须要创立一个对象的时候,只须要配置好配置文件 / 注解即可,齐全不必思考对象是如何被创立进去的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创立中解决这些对象的整个生命周期,直到它们被齐全销毁。

在理论我的项目中一个 Service 类如果有几百甚至上千个类作为它的底层,咱们须要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只须要配置好,而后在须要的中央援用就行了,这大大增加了我的项目的可维护性且升高了开发难度。对于 Spring IOC 的了解,举荐看这一下知乎的一个答复:https://www.zhihu.com/question/23277575/answer/169698662,十分不错。

管制反转怎么了解呢? 举个例子:” 对象 a 依赖了对象 b,当对象 a 须要应用 对象 b 的时候必须本人去创立。然而当零碎引入了 IOC 容器后,对象 a 和对象 b 之前就失去了间接的分割。这个时候,当对象 a 须要应用 对象 b 的时候,咱们能够指定 IOC 容器去创立一个对象 b 注入到对象 a 中 ”。对象 a 取得依赖对象 b 的过程, 由被动行为变为了被动行为,控制权反转,这就是管制反转名字的由来。

DI(Dependecy Inject, 依赖注入)是实现管制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。

工厂设计模式

Spring 应用工厂模式能够通过 BeanFactory 或 ApplicationContext 创立 bean 对象。

两者比照:

  • BeanFactory:提早注入(应用到某个 bean 的时候才会注入), 相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
  • ApplicationContext:容器启动的时候,不论你用没用到,一次性创立所有 bean。BeanFactory 仅提供了最根本的依赖注入反对, ApplicationContext 扩大了 BeanFactory , 除了有 BeanFactory 的性能还有额定更多功能,所以个别开发人员应用  ApplicationContext 会更多。

ApplicationContext 的三个实现类:

  1. ClassPathXmlApplication:把上下文文件当成类门路资源。
  2. FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
  3. XmlWebApplicationContext:从 Web 零碎中的 XML 文件载入上下文定义信息。

Example:

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(单例)的。 除了 singleton 作用域,Spring 中 bean 还有上面几种作用域:

  • prototype : 每次申请都会创立一个新的 bean 实例。
  • request : 每一次 HTTP 申请都会产生一个新的 bean,该 bean 仅在以后 HTTP request 内无效。
  • session : 每一次 HTTP 申请都会产生一个新的 bean,该 bean 仅在以后 HTTP session 内无效。
  • global-session:全局 session 作用域,仅仅在基于 portlet 的 web 利用中才有意义,Spring5 曾经没有了。Portlet 是可能生成语义代码 (例如:HTML) 片段的小型 Java Web 插件。它们基于 portlet 容器,能够像 servlet 一样解决 HTTP 申请。然而,与 servlet 不同,每个 portlet 都有不同的会话

Spring 实现单例的形式:

  • xml : <bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
  • 注解:@Scope(value = "singleton")

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));

            }
        }
}

代理设计模式

代理模式在 AOP 中的利用

AOP(Aspect-Oriented Programming: 面向切面编程)可能将那些与业务无关,却为业务模块所独特调用的逻辑或责任(例如事务处理、日志治理、权限管制等)封装起来 ,便于 缩小零碎的反复代码 升高模块间的耦合度 ,并 有利于将来的可拓展性和可维护性

Spring AOP 就是基于动静代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会应用JDK Proxy,去创立代理对象,而对于没有实现接口的对象,就无奈应用 JDK Proxy 去进行代理了,这时候 Spring AOP 会应用Cglib,这时候 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 快很多。

模板办法

模板办法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤提早到子类中。模板办法使得子类能够不扭转一个算法的构造即可重定义该算法的某些特定步骤的实现形式。

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 {

    @Override
    public void PrimitiveOperation2() {// 以后类实现}

    @Override
    public void PrimitiveOperation3() {// 以后类实现}
}

Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就应用到了模板模式。个别状况下,咱们都是应用继承的形式来实现模板模式,然而 Spring 并没有应用这种形式,而是应用 Callback 模式与模板办法模式配合,既达到了代码复用的成果,同时减少了灵活性。

观察者模式

观察者模式是一种对象行为型模式。它示意的是一种对象与对象之间具备依赖关系,当一个对象产生扭转的时候,这个对象所依赖的对象也会做出反馈。Spring 事件驱动模型就是观察者模式很经典的一个利用。Spring 事件驱动模型十分有用,在很多场景都能够解耦咱们的代码。比方咱们每次增加商品的时候都须要从新更新商品索引,这个时候就能够利用观察者模式来解决这个问题。

Spring 事件驱动模型中的三种角色

事件角色

ApplicationEvent (org.springframework.context包下)充当事件的角色, 这是一个抽象类,它继承了 java.util.EventObject 并实现了 java.io.Serializable接口。

Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent 的实现(继承自ApplicationContextEvent):

  • ContextStartedEventApplicationContext 启动后触发的事件;
  • ContextStoppedEventApplicationContext 进行后触发的事件;
  • ContextRefreshedEventApplicationContext 初始化或刷新实现后触发的事件;
  • ContextClosedEventApplicationContext 敞开后触发的事件。

事件监听者角色

ApplicationListener 充当了事件监听者角色,它是一个接口,外面只定义了一个 onApplicationEvent()办法来解决 ApplicationEventApplicationListener 接口类源码如下,能够看出接口定义看出接口中的事件只有实现了 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 接口的 publishEvent() 这个办法在 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 接管音讯
    @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));
    }
}

当调用 DemoPublisher 的 publish() 办法的时候,比方 demoPublisher.publish("你好"),控制台就会打印出:接管到的信息是:你好

适配器模式

适配器模式(Adapter Pattern) 将一个接口转换成客户心愿的另一个接口,适配器模式使接口不兼容的那些类能够一起工作,其别名为包装器(Wrapper)。

spring AOP 中的适配器模式

咱们晓得 Spring AOP 的实现是基于代理模式,然而 Spring AOP 的加强或告诉 (Advice) 应用到了适配器模式,与之相干的接口是 AdvisorAdapter 。Advice 罕用的类型有:BeforeAdvice(指标办法调用前, 前置告诉)、AfterAdvice(指标办法调用后, 后置告诉)、AfterReturningAdvice(指标办法执行完结后,return 之前) 等等。每个类型 Advice(告诉)都有对应的拦截器:MethodBeforeAdviceInterceptorAfterReturningAdviceAdapterAfterReturningAdviceInterceptor。Spring 预约义的告诉要通过对应的适配器,适配成 MethodInterceptor接口 (办法拦截器) 类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)。

spring MVC 中的适配器模式

在 Spring MVC 中,DispatcherServlet 依据申请信息调用 HandlerMapping,解析申请对应的 Handler。解析到对应的 Handler(也就是咱们平时说的 Controller 控制器)后,开始由HandlerAdapter 适配器解决。HandlerAdapter 作为冀望接口,具体的适配器实现类用于对指标类进行适配,Controller 作为须要适配的类。

为什么要在 Spring MVC 中应用适配器模式? Spring MVC 中的 Controller 品种泛滥,不同类型的 Controller 通过不同的办法来对申请进行解决。如果不利用适配器模式的话,DispatcherServlet 间接获取对应类型的 Controller,须要的自行来判断,像上面这段代码一样:

if(mappedHandler.getHandler() instanceof MultiActionController){((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){...}else if(...){...}  

如果咱们再减少一个 Controller类型就要在下面代码中再退出一行 判断语句,这种模式就使得程序难以保护,也违反了设计模式中的开闭准则 – 对扩大凋谢,对批改敞开。

装璜者模式

装璜者模式能够动静地给对象增加一些额定的属性或行为。相比于应用继承,装璜者模式更加灵便。简略点儿说就是当咱们须要批改原有的性能,但咱们又不愿间接去批改原有的代码时,设计一个 Decorator 套在原有代码里面。其实在 JDK 中就有很多中央用到了装璜者模式,比方 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (减少缓存, 使读取文件速度大大晋升)等子类都在不批改InputStream 代码的状况下扩大了它的性能。

Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。咱们是否依据客户的需要在少批改原有类的代码下动静切换不同的数据源?这个时候就要用到装璜者模式 (这一点我本人还没太了解具体原理)。Spring 中用到的包装器模式在类名上含有 Wrapper 或者 Decorator。这些类基本上都是动静地给一个对象增加一些额定的职责

总结

Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring 应用工厂模式通过 BeanFactoryApplicationContext 创立 bean 对象。
  • 代理设计模式 : Spring AOP 性能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板办法模式 : Spring 中 jdbcTemplatehibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就应用到了模板模式。
  • 包装器设计模式 : 咱们的我的项目须要连贯多个数据库,而且不同的客户在每次拜访中依据须要会去拜访不同的数据库。这种模式让咱们能够依据客户的需要可能动静切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个利用。
  • 适配器模式  :Spring AOP 的加强或告诉(Advice) 应用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller
  • ……

参考

  • 《Spring 技术底细》
  • https://blog.eduonix.com/java-programming-2/learn-design-patterns-used-spring-framework/
  • http://blog.yeamin.top/2018/03/27/ 单例模式 -Spring 单例实现原理剖析 /
  • https://www.tutorialsteacher.com/ioc/inversion-of-control
  • https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html
  • https://juejin.im/post/5a8eb261f265da4e9e307230
  • https://juejin.im/post/5ba28986f265da0abc2b6084
  • https://zhuanlan.zhihu.com/p/273398448
  • https://shimo.im/docs/yvJqyQy3QgRdcCPx/
退出移动版