• Spring中的ApplicationContext所体现出来的工厂模式


    Spring初体验

    通常我们写代码时,先定义一个Class,然后再别的地方实例化,再进行调用,比如下边的Hello World的例子,类Hello有一个say()方法,用于打印出“Hello World”的字符串,另一个类App,承载程序的入口方法main(),我们在main()方法中实例化Hello类,并调用Hello.say()方法,代码如下:

    Hello.java 

    1. package demo;
    2. public class Hello {
    3. public Hello() {
    4. }
    5. public void say() {
    6. System.out.println("Hello world!");
    7. }
    8. }

     App.java

    1. package demo;
    2. public class App {
    3. public static void main(String[] args) {
    4. //手动硬编码创建一个Hello对象
    5. Hello hello=new Hello();
    6. hello.say();
    7. }
    8. }

    上边是入门的编程方式,在程序规模较小的情况是没啥问题的,但程序的规模成指数级增长后,可能成千上万的对象需要创建,可能很多对象的方法还是事务性,你如何在这成千上万个new出来的对象间进行事务处理?当然此处先点到为止,暂时还没进入这么高深的步骤。总之,Sun的工程师们为此发明了一个叫EJB的东西,将某些对象的创建进行统一管理,后来的Spring也继承了这一衣钵,EJB和Spring的差别就是,EBJ很臃肿并依赖程序运行的容器,而Spring则轻巧灵活,直接内嵌在程序中。总之就是不要自己去创建对象,统一由容器创建。

    Spring定义了一个叫做ApplicationContext的接口,由其实现类完成对象的创建。按照Spring的思路,将上边的代码稍作转换后写法变成了这样:

    1. package demo;
    2. import org.springframework.context.support.StaticApplicationContext;
    3. public class App {
    4. public static void main(String[] args) {
    5. //StaticApplicationContext是ApplicationContext的一个具体实现,用在独立运行的程序中
    6. StaticApplicationContext context = new StaticApplicationContext();
    7. context.registerBean(Hello.class);
    8. Hello hello = context.getBean(Hello.class);
    9. hello.say();
    10. }
    11. }

    这里我们看到多了一个StaticApplicationContext类的引用,实例化之后,对这Hello作了一些操作,然后通过这个context就可以获取Hello的实例,这里没有了new Hello()的代码,取而代之的是context.getBean(Class),后边的内容就跟前边一样了。这个例子暂时还没看到Spring有什么好处,但我们可以看到Spring的作用,就是用ApplicationContext接管了对象的实例化。当然,一般情况下我们也不会这样去使用Spring,并不会再实际项目中使用上下文获取对象,而是全权托管给Spring。

    Spring用了反射机制创建对象,实际效果跟new一个对象是一样的。所以ApplicationContext在创建实例的时候也是调用构造方法的。为了演示Spring的托管效果,我们可以把say()方法在构造方法中调用,这样创建对象时就会调用say()方法,我们改一下Hello及App的代码:

    Hello.java

    1. package demo;
    2. public class Hello {
    3. public Hello() {
    4. say();
    5. }
    6. public void say() {
    7. System.out.println("Hello world!");
    8. }
    9. }

    然后将main方法再改造一下,便只剩下Spring的内容,而无需编写自己的程序的调用代码,修改之后变成这样: 

    App.java

    1. package demo;
    2. import org.springframework.context.support.StaticApplicationContext;
    3. public class App {
    4. public static void main(String[] args) {
    5. StaticApplicationContext context = new StaticApplicationContext();
    6. context.registerBean(Hello.class);
    7. //因为没有显式getBean()的调用,所以不会主动创建实例,我们用下边的语句进行预实例化。
    8. context.getBeanFactory().preInstantiateSingletons();
    9. }
    10. }

    运行App控制台打出“Hello World!”。

    StaticApplicationContext就是所谓的上下文,我们将对象的生命周期交给这个上下文来管理。前边我只体验到了使用上下文来生成对象,实际上上下文还有更多的用法,可以提供除构造方法为的初始化以及销毁方法。由于StaticApplicationContext这个实现只用于测试,所以有些功能并未提供,为了体验更多的功能,接下来我们使用外部xml配置文件来指定要托管的类型。

    ApplicationContext本身也有多种具体实现,使用不同的方式来进行自的初始化。ClassPathXmlApplicationContext就是其中之一,其目的是在classpath中搜索xml配置文件来进行上下的初始化。具体xml的配置文件怎么写可以参考下边的链接:Core Technologiesicon-default.png?t=M85Bhttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition

    我们创建一个app.xml:

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. https://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <bean class="demo.Hello" init-method="say" destroy-method="bye">
    7. bean>
    8. beans>

    Hello.java改成最初的样子:

    1. package demo;
    2. public class Hello {
    3. public Hello() {
    4. }
    5. public void say() {
    6. System.out.println("Hello world!");
    7. }
    8. public void bye() {
    9. System.out.println("Bye bye!");
    10. }
    11. }

    App.java改成如下:

    1. package demo;
    2. import org.springframework.context.support.ClassPathXmlApplicationContext;
    3. public class App {
    4. public static void main(String[] args) {
    5. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
    6. //关闭上下文时会销毁上下中创建的所有对象。
    7. context.close();
    8. }
    9. }

    运行App控制台打出“Hello World!”及“Bye bye!”。

    工厂模式的应用

    前文所体现出来的是设计模式中标准的工厂模式。每个上下文中都有一个beanFactory的实例,然后通过beanFactory产生出对象实例。意思就是说,就像工厂一样,你给定设计模型,工厂就照样子给你生产。

    App.java中我们只实例化了一个上下文,但程序正常运行了。当然此处我们依然没有体验到Spring带来了什么好处,但可以看出,Spring的上下文就像一个容器,里边装载了我们需要的对象,本来我们需要在main()方法要写的代码,全部由Spring接管。同时不是说我们不需要处理对象的初始化,而是将对象的初始代码与逻辑代码进行分离,并且使用基于配置的方式实现,并不需要进行硬编码。

    接下来,我们研究一下,在自己的程序之上,再套一层Spring究竟有什么好处,为什么要将对象初始化进行单独处理。

    我们创建一个跟Hello类差不多的HelloTom类:

    1. package demo;
    2. public class HelloTom {
    3. public void say() {
    4. System.out.println("Hello Tome!");
    5. }
    6. }

    因为跟Hello长得一样,所以无论用不用Spring,调用方式都是一样的,我们先不用Spring来写个示例。为了使程序有一定扩展性,我们可以抽象出一个接口来,我们建一个SayHello的接口:

    1. package demo;
    2. public interface SayHello {
    3. void say();
    4. }

    然后Hello与HelloTom都实现这个接口:

    画个UML类图看起来更直观一点

     

    Hello.java

    1. package demo;
    2. public class Hello implements SayHello {
    3. public void say() {
    4. System.out.println("Hello world!");
    5. }
    6. }

    HelloTom.java

    1. package demo;
    2. public class HelloTom implements SayHello{
    3. public void say() {
    4. System.out.println("Hello Tom!");
    5. }
    6. }

    然后修改App.java第一次调用Hello.say():

    1. package demo;
    2. public class App {
    3. public static void main(String[] args) {
    4. SayHello hello=new Hello();
    5. hello.say();
    6. }
    7. }

    修改App.java再次调用HelloTom.say():

    1. package demo;
    2. public class App {
    3. public static void main(String[] args) {
    4. SayHello hello=new HelloTom();
    5. hello.say();
    6. }
    7. }

    从这两个示例中,可以看出两个不同的类型,却可以使用同一个接口类型来表示。不过,这个示例依然需要改两次代码才能实现不同的调用,能不能不改App.java就可以实现不同的调用呢?答案是可以的。我们可以使用工厂模式,通过运行参数告诉程序来选择哪个类型,但我们不必自己去写这样的逻辑,Spring已经自带了这个功能,可以直接拿用。这次我们还是用xml配置加载的方式,这样写的好处是不用在程序中硬编码,只要修改外部的xml文件后,重新运行程序就可以了。

    先将App.java改成如下:

    1. package demo;
    2. import org.springframework.context.ApplicationContext;
    3. import org.springframework.context.support.ClassPathXmlApplicationContext;
    4. public class App {
    5. public static void main(String[] args) {
    6. ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
    7. SayHello hello = context.getBean(SayHello.class);
    8. hello.say();
    9. }
    10. }

    然后修改app.xml后运行App.java:

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. https://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <bean class="demo.Hello" >
    7. bean>
    8. beans>

    然后再修改app.xml

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. https://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <bean class="demo.HelloTom" >
    7. bean>
    8. beans>

     再次运行,发现两次结果是不同的,这个示例中我们除了刚开始设计需要改了下App.java,后边我们就不用再改了,而是修改明文的app.xml文件。这样做的好处就是,对于一个明确的功能,为了对环境做出一定的适应性,我们只要在外部调整一下配置文件,而不必要对项目的主体进行修改。实现了程序代码的轻耦合,也就是减少无关代码的之间的联系。这个示例中,任意一个实现SayHello接口的子类,都可以这样注册到Spring的上下文中,以达到不一样的效果。

    当然,ApplicationContext并不仅仅是个工厂实现,它还给了我们统一的编程框架。无论是CS程序,还是WEB程序,我们写Spring都是类似乐高一样的组装方式。区别就在于使用什么样的上下文。

  • 相关阅读:
    10分钟搞定 Spring 批处理组件 —— spring-batch
    uniapp+vue3+ts+vite+echarts开发图表类小程序,将echarts导入项目使用的详细步骤,耗时一天终于弄好了
    【uniapp】HBuilderx中uniapp项目运行到微信小程序报错Error: Fail to open IDE
    Zookeeper的功能简介
    第152篇 Solidity 中的 Call
    intel cpu core/“酷睿”系列发展史,供组装机的朋友们参考
    OpenResty使用漏桶算法实现限流
    ubuntu安装electerm
    加快访问github的速度解决方案
    C++枪战小游戏停电救援(最终版,已完结)
  • 原文地址:https://blog.csdn.net/icoolno1/article/details/111412415