Spring Ioc基于java反射而远远高于反射---Spring框架三大核心接口

关于最基础的Ioc的概念及其思想,我们在

Spring核心功能–Ioc一节中都有过详细的讨论,这里就不多言了!

1、java反射

    那么,接下来,我们从反射入手,开始Spring征程。看例子:
package com.smart.reflect;

public class Car {
    private String brand;
    private String color;
    private int maxSpeed;

    public Car() {
    }

    public Car(String brand, String color, int maxSpeed) {
        this.brand = brand;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }

    public void introduce() {
        System.out.println("brand:"+brand+";color:"+color+";maxSpeed:"+maxSpeed);
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
}
一般情况下,我们使用如下代码示例Car:
Car car = new Car();
car.setBrand("法拉利");

或者

Car car =  new Car("法拉利", "红色", 250);
以上两种方法都是采用传统的方式,直接调用目标类的方法。下面我们通过反射机制一一种间接的方式操控目标类:
package com.smart.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectTest {
    public static Car initByDefaultConst()
            throws ClassNotFoundException,
                    NoSuchMethodException,
                    IllegalAccessException,
                    InvocationTargetException,
                    InstantiationException {
        //①通过类加载器获取Car对象
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class clazz = classLoader.loadClass("com.smart.reflect.Car");
        /*
        * 也可以通过Class.forName();来获取Car对象
        */
        //②获取类的默认构造器对象并通过他实例化Car
        Constructor cons = clazz.getDeclaredConstructor((Class[]) null);
        Car car = (Car) cons.newInstance();

        //③通过反射方法设置属性
        Method setBrand = clazz.getMethod("setBrand", String.class);
        setBrand.invoke(car, "法拉利");
        Method setColor = clazz.getMethod("setColor", String.class);
        setColor.invoke(car, "红色");
        Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
        setMaxSpeed.invoke(car, 250);
        return car;
    }

    public static void main(String[] args)
            throws ClassNotFoundException,
            NoSuchMethodException,
            InvocationTargetException,
            InstantiationException,
            IllegalAccessException {
        Car car = initByDefaultConst();
        car.introduce();
    }
}

运行上述程序:
brand:法拉利;color:红色;maxSpeed:250
这些都是基础的反射的知识,我们就不详细给大家说明了。
关于类加载器,类加载机制,详见
java类加载
这里我们补充一些ClassLoad的知识。在java中ClassLoad是一个抽象类,位于java.lang包中,下面对该类的一些方法进行分析:

  • Class loadClass(String name):name参数指定类装载器需要装载的类的名字,必须使用全限定名,如 com.smart.reflect.Car。该方法有一个重载方法loadClass(String name, boolean resolve),resolve参数告诉类装载器是否需要解析该类。在初始化类之前,应考虑进行类解析工作,但并不是所有的类都需要解析。如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析。
  • Class defineClass(String name, byte[] b, int off, int len):将类文件的字节数组转换成JVM内部的java.lang.Class对象。字节数组可以从本地文件系统、远程网络获取。参数name为字节数组的全限定类名。
  • Class findSystemClass(String name):调用该方法来查看ClassLoader是否已装入某个类。如果已经装入,那么返回java.lang.Class对象;否则,抛出ClassNotFoundException异常。该方法是JVM默认使用的装载机制。
  • ClassLoader getParent():获取类装载器的父装载器。除根装载器外,所有的类装载器都有且只有一个父类装载器。ExtClassLoader的父装载器是根装载器,因为根装载器是非java语言写的,所以无法获得, 返回null;

    除了JVM默认的三个类加载器外,用户可以自己编写自己的第三方类加载器,以实现一些特殊的要求。类文件被装载并解析后,在JVM内拥有一个对应的java.lang.Class类描述对象,该类的实例都拥有指向这个类描述对象的引用,而类描述对象用拥有指向关联ClassLoader的引用
    

    这里写图片描述

反射机制

class反射对象描述类语义结构,可以从Class对象中获取构造函数,成员变量,方法类等元素的反射对象,并以编程的方式通过这些反射对象对目标类进行操作。
除了我们经常使用的Constructor类的构造函数反射类、Method类的方法反射类、Filed类的成员变量反射类之外,java还为包提供了Package反射类,在java5.0中还为注解提供了AnnotatedElement反射类。总之,java的反射体系保证了可以通过程序化的方式访问目标类中的所有元素,对于private和protected成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用。具体就不演示了。

2、资源访问利器

负责任的说,这块是重点内容。

2.1、资源抽象接口
JDK所提供的访问资源的类(java.net.URL、File等)并不能很好的满足各种底层资源的访问需求,比如缺少从类路径(classpath:com.smart)或者Web容器的上下文(ServletContexst)中获取资源的类。鉴于此,Spring设计了一个Resource接口,他为应用提供了更强的底层资源访问能力。该接口拥有对应不容资源的实现类。先来了解一下Resource接口的主要方法。

  • boolena exists():资源是否存在。
  • boolean isOpen():资源是否已经打开。
  • URL getURL() throws IoException:如果底层资源可以表示成URL,则方法返回对应的URL对象。
  • File getFile() throws IoException:如果底层资源对应一个文件,则返回该方法的对应的File对象。
  • InputStream getInputStream() throws IoException:放回资源对应的输入流。
    Resource在Spring框架中起着不可获取的作用,Spring框架使用Resource装载各种资源,包括配置文件资源,国际化属性文件资源等。
    假如有一个文件位于Web应用的类路径下,用户可以通过以下方式对这个文件资源进行访问:

  • 通过FileSystemResource以文件系统绝对路径的方式进行访问。

  • 通过ClassPathResource以类路径的方式进行访问。
  • 通过ServletContextResource以相对于web应用根目录的方式进行访问。

相比于通过JDK的File类访问文件资源的方式,Spring的Resource实现类无疑提供了更加灵活便捷的访问方式,用户可以根据实际情况选择适合的Resource实现类访问资源。下面通过FileSystemResource 和ClassPathResource访问同一个资源。

package com.smart.resource;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileSourceExample {
    public static void main(String[] args) {
        try {
            String filepath = "E:/IdeaProjects/chapter4/src/main/resources/conf/file1.txt";

            //①使用系统文件路径方式加载文件
            WritableResource res1 = new PathResource(filepath);

            //②使用类路径加载文件
            Resource res2 = new ClassPathResource("conf/file1.txt");

            //③使用WritableResource接口写资源文件
            OutputStream outputStream = res1.getOutputStream();
            outputStream.write("欢迎光临\nSpring学习基地".getBytes());
            outputStream.close();

            //④使用Resource接口读资源文件
            InputStream inputStream1 = res1.getInputStream();
            InputStream inputStream2 = res2.getInputStream();

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int i;
            while((i = inputStream1.read()) != -1) {
                byteArrayOutputStream.write(i);
            }
            System.out.println(byteArrayOutputStream.toString());
            System.out.println("res1:" + res1.getFilename());
            System.out.println("res2:" + res2.getFilename());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

2.2、资源加载
为了访问不同类型的资源,必须使用相应的Resource实现类,这是比较麻烦的。是否可以在不显式使用Resource实现类的情况下,仅通过资源地址的特殊标识就可以访问相应的资源呢?Spring提供了一个强大的加载资源机制,不但能够通过“classpath”,“file”等资源地址前缀识别不同的资源类型,还支持Ant风格带通配符的资源地址。
首先了解一下Spring支持哪些资源类型的地址前缀。

  • “classpath:”:从类路径中加载资源,classpath和classpath:/是等价的,都是相对于类的根路径。资源文件可以在标准的文件系统中,也可以在JAR或ZIP的类包中。
  • “file:”:使用UrlResource从文件系统目录中装载资源,可以采用绝对路径或相对路径。
  • “http://”:使用UrlResource从Web服务器中装载资源。
  • ftp://”:使用UrlResource从FTP服务器中装载资源。
  • 没有前缀:根据ApplicationContext的具体实现类采用对应类型的Resource。
    至于通配符就不详细说明了,大家可以自己查。
    Spring定义了一套资源加载的接口,并提供了实现类。
    这里写图片描述
    ResourceLoader接口仅有一个getResource(String localtion)方法,可以根据一个资源地址加载人间资源。不过,资源地址仅支持带资源类型前缀的表达式,不支持Ant风格的资源路径表达式。ResourcePatternResolver扩展ResourceLoader接口,定义了一个新的路径表达式。PathMatchingResourcePatternResolver是Spring提供的标准实现类。看一个例子:
package com.smart.resource;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

import static org.junit.Assert.assertNotNull;

public class PatternResolverTest {
    @Test
    public void getResources() {
        ResourcePatternResolver resolcer = new PathMatchingResourcePatternResolver();

        //①加载所有类包com.smart(及子孙包)一下的.xml为后缀的资源
        try {
            Resource[] resources = resolcer.getResources("classpath*:com/smart/**/*.xml");
            assertNotNull(resources);

            for(Resource resource : resources) {
                System.out.println(resource.getDescription());
            }
        } catch (IOException e) {
            e.printStackTrac`
();
        }
    }
}

file [E:\IdeaProjects\chapter4\target\classes\com\smart\beanfactory\beans.xml]
file [E:\IdeaProjects\chapter4\target\classes\com\smart\beans1.xml]
file [E:\IdeaProjects\chapter4\target\classes\com\smart\beans2.xml]
file [E:\IdeaProjects\chapter4\target\classes\com\smart\context\beans.xml]

由于资源路径是”classpath*:”,所以PathMatchingResourcePatternResolver将扫描所有类路径下以及JAR包中对应的com.smart类包下的路径,读取所有以.xml为后缀的文件资源。
提示

用Resource造作文件时,如果资源配置文件在项目发布时会被打包到JAR中,那么不能使用Resource#getFile()方法,否则会抛出FileNotFoundException。但可以使用Resource#getInputStream()方法。
这样的问题在实际项目开发中很容易被疏忽!
2.3 BeanFactory和ApplicationContext

Spring通过一个配置文件描述Bean及Bean之间的依赖关系。利用java语言的放射功能实例化Bean并建立Bean之间的依赖关系,Spring的Ioc容器在完成这些底层工作的基础上,还提供了Bean实例缓存,生命周期管理,Bean实例代理,事件发布,资源转载等高级服务。
Bean工厂(com.springframework.beans.BeanFactory)是Spring框架的最核心的接口,他提供了高级Ioc的配置机制,BeanFactory使管理不同类型的java对象成为可能,应用上下文(com.springframework.beans.ApplicationContext)建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。我们一般称BeanFactory为Ioc容器,而称ApplicationContext为应用上下文,但有时为了行文方便,我们也将ApplicationContext成为Spring容器。
对于二者的用途,我们可以进行简单的划分,BeanFactory是Spring框架的基础设施,面向Spring本身,ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory。
2.3.1、BeanFactory介绍
诚如其名,BeanFactory是一个类工厂,但和传统的类工厂不同,传统的类工厂仅负责构造一个或几个类的实例,而BeanFactory是类的通用工厂,他可以创建并管理各种类的对象。这些可被创建和管理的对象本身没有什么特别之处,仅仅是一个POJO,Spring称这些被创建和管理的java对象为Bean。
2.3.1.1BeanFactory的类体系结构
Spring为BeanFactory提供了多种实现,最常用的是XmlBeanFactory,但是在Spring3.2中已经被废弃,建议使用XmlBeanDefinitionReader、DefaultListableFactory替代。BeanFactory的类继承体系设计优雅,堪称经典。通过继承体系,我们可以很容易的了解BeanFactory具有哪些功能。
这里写图片描述
BeanFactory接口位于类结构树的顶端,他最主要的方法就是getBean(String beanName),该方法从容器中放回特定名称的Bean。BeanFactory的功能通过其他接口得到不断扩展。
2.3.1.2初始化BeanFactory

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
   <bean id="car" class="com.smart.Car"
         p:brand="红旗CA72"
         p:maxSpeed="200"/>
</beans>
    下面通过XmlBeanDefinitionReader、DefaultListableBeanFactory实现启动Spring Ioc容器。
package com.smart.beanFactory;

import com.smart.Car;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

public class BeanFactoryTest {
    @Test
    public void getBean() throws IOException {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource res = resolver.getResource("classpath:com/smart/beanFactory/beans.xml");

        System.out.println(res.getURL());
        //资源定位
        //file:/E:/IdeaProjects/chapter4/target/classes/com/smart/beanFactory/beans.xml

        //①初始化Ioc 容器
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        //②装载Spring配置信息并启动Ioc容器
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions(res);

        System.out.println("init BeanFactory");

        //默认使用类名首字母小写为键值
        Car car = factory.getBean("car", Car.class);
        System.out.println("car bean is ready for use");
        car.introduce();
    }
}

通过BeanFactory启动Ioc容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时。对于单实例(singleton)的Bean来说,BeanFactory会缓存Bean实例,所以在第二次使用getBean()获取Bean时,直接从Ioc容器的缓存中获取Bean实例。
Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存Bean实例的缓存器。他是一个用HashMap实现的缓存器,单实例的Bean以beanName为键值保存在这个HashMap中。
2.3.2、ApplicationContext介绍
如果说BeanFactory是Spring的“心脏”,那么ApplicationContext就是整的“身躯”了。ApplicationContext有BeanFactory派生而来,体用了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置的方式实现。
2.3.2.1 ApplicationContext类体系结构
ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统中装载配置文件,下面了解一下ApplicationContext的类继承体系。
这里写图片描述
从图中可以看出,ApplicationContext继承了HierarchicalBeanFactory和listableBeanFactory接口。在此基础上,还通过多个其他的接口扩展了BeanFactory的功能。
ConfigurableApplicationContext扩展于ApplicationC,它新增了两个主要的方法,refresh()和close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文已经启动的状态下,调用close()即可关闭应用上下文。
和BeanFactory初始化相似,ApplicationContext的初始化也很简单。如果配置文件放置在类路径下,则可以优先考虑使用ClassPathXmlApplicationContext实现类。

/*
此时的com/smart/context/beans.xml等同于classpath:com/smart/context/beans.xml
*/
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/context/beans.xml");
如果配置文件放置在文件系统的路径下,则可以优先考虑使用FileSystemXmlApplicationContext实现类。
/*
此时的com/smart/context/beans.xml等同于file:com/smart/context/beans.xml
*/
Application ctx = new FileSystemXmlApplicationContext("com/smart/context/beans.xml");

当然,FileSystemXmlApplicationContext和ClassPathXmlApplication都可以显示的使用带资源类型前缀的路径,他们的区别在于如果不是显示指定资源类型前缀,则分别将路径解析为文件系统路径和类路径。
在获取ApplicationContext实例之后,就可以像BeanFactory一样调用getBean()方法获取Bean。但是两者有一个巨大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次调用getBean()才实例化,而ApplicationContext在实例化应用上下文时,就会实例化所有单例Bean。因此,ApplicationContext在初始化时间上会比BeanFactory稍长一些,但是在调用时则没有“一次惩罚”的问题。
Spring支持基于注解的配置,主要功能来自于Spring的一个名为JavaConfig的子项目。JavaConfig现在已经升级为Spring核心框架的一部分。一个标注@Configuration注解的POJO即可提供Sprin所需的Bean配置信息。

package com.smart.context;

import com.smart.Car;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//表示是一个配置信息的提供类
@Configuration
public class Beans {
    //定义一个Bean
    @Bean(name="car")
    public Car builder() {
        Car car = new Car();
        car.setBrand("法拉利");
        car.setMaxSpeed(200);
        return car;
    }
}

和基于XML配置的方式相比,类注解的配置方式可以很容易的让开发者控制Bean的初始化过成功,比基于XML文件的配置方式更加灵活。
Spring为基于注解类的配置提供了专门的ApplicationContext实现类:AnnotationConfigApplicationContext。来看一个例子:

package com.smart.context;

import com.smart.Car;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.junit.Assert.assertNotNull;

public class AnnotationApplicationContextText {
    @Test
    public void getBean() {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(Beans.class);

        Car car = ctx.getBean("car", Car.class);
        assertNotNull(car);
    }
}

Spring4.0支持使用Groovy DSL来进行Bean定义配置。其与基于XML文件的配置类似,只不过基于Groovy脚本语言,可以实现复杂、灵活的Bean配置逻辑,来看一个例子。
文件后缀名.groovy

package com.smart.context

import com.smart.Car

beans {
    car(Car) {//名字(类型)
        brand = "法拉利";  //注入属性
        maxSpeed = "270";
        color = "红色";
    }
}

基于Groovy的配置方式可以很容易的让开发者配置复杂的Bean的初始化过程,比基于XML文件,注解得配置方式更加灵活。
Spring为基于Groovy的配置提供了专门的ApplicationContext实现类,GenericGroovyApplicationContext。来看一个例子:

package com.smart.context;

import com.smart.Car;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericGroovyApplicationContext;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class GroovyApplicationContextText {
    @Test
    public void getBean() {
        ApplicationContext ctx = new GenericGroovyApplicationContext("classpath:com/smart/context/beans.groovy");
        Car car = ctx.getBean("car", Car.class);
        assertNotNull(car);
        assertEquals(car.getColor(), "red");

    }
}

2.3.2.2 WebApplicationContext类体系结构
WebApplicationContext是专门为WEB应用准别的,他允许从相对根目录的路径中装载配置文件完成初始化工作。从 WebApplicationContext中可以获得ServletContext的引用,整个web应用上下文对象将作为属性放置到ServletContext中,以便web应用环境可以访问Spring的上下文。Spring专门为此提供了一个工具类WebApplicationContextUtils,通过该类的getWebApplication(ServletContext sc)方法,可以从ServletContext中获取WebApplicationContext实例。
在非Web应用环境下,Beans只有singleton和prototype两种作用域,WebApplicationContext为Bean添加了三个新的作用域:request、session、global session。
由于web应用比一般的应用拥有更多的特性,因此 WebApplicationContext扩展了ApplicationContext。 WebApplicationContext定义了一个常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在上下文启动时, WebApplicationContext实例即以此为键值存放在ServletContext的属性列表中,可以通过以下语句从容器中获取 WebApplicationContext:

 WebApplicationContext wac = ( WebApplicationContext)servletContext.getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

这正是前面提到的 WebApplicationContextUtils工具类的getWebApplicationContext(ServletContext sc)方法的内部实现方式。这样,Spring的web应用上下文和web容器的上下文应用就可以互相访问。
这里写图片描述

ConfigurableWebApplicationContext扩展了WebApplicationContext,他允许通过配置的方式实例化WebApplicationContext,同时定义了两个方法:

  • setServletContext(ServletContext servletContext):为Spring设置web应用上下文,以便二者整合。
  • setConfigLocations(String[] configLocations):设置Spring配置文件地址,一般情况下,配置文件地址是相对于WEB根目录的地址,如/WEB-INF/smart-dao.xml。但是用户可以使用带资源类型前缀的地址,如classpath:com/smart/beans.xml
    2.3.2.3 WebApplicationContext初始化

WebApplicationContext的初始化方式和BeanFactory、ApplicationContext的初始化方式有所区别。因为WebApplicationContext需要实例化ServletContext,也就是说,他必须在拥有WEB容器的基础上才能完成启动工作。有过web开发经历的同学都知道,可以在web.xml中配置自启动的Servlet或者定义web容器监听器(ServletContextListener),借助二者中的任何一个,就可以完成启动Spring WEB应用上下文的工作。
Spring分别提供了用于启动WebApplicationContext的Servlet和web容器监听器:

  • org.springframework.web.context.ContextLoaderServlet。
  • org.springframework.web.context.ContextLoaderListener。

        二者的内部 都实现了启动WebApplicationContext实例的逻辑,只要根据web容器的具体情况选择二者之一,并在web.xml中完成配置即可。
    
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <!--指定配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>  
                  /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml
        </param-value>
    </context-param>

    <!-- 声明web容器监听器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
</web-app>

ContextLoaderListener通过web容器上下文参数contextConfigLocation获取Spring配置文件的位置。用户可以指定多个配置文件,用逗号隔开、空格或冒号隔开。对于未带资源类型前缀的配置文件路径,WebApplicationContext默认这些路径相对于Web的部署根路径。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <!--指定配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>  
                  /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml
        </param-value>
    </context-param>

    <!-- 声明自启动的Servlet-->
    <servlet>
        <servlet-class>
            org.springframework.web.context.ContextLoaderServlet
        </servlet-class>
        <!-- 启动顺序 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

</web-app>

由于WebApplicationContext需要使用日志功能,所以用户可以将Log4J的配置文件放置在类路径WEN-INF/classes下,这是Log4J引擎即可顺利启动。如果Log4J配置文件放置在其他位置,那么用户必须在web.xml中指定Log4J配置文件的位置。Spring为启动Log4J引擎提供了两个类似于启动WebApplicationContext的实现类
Log4jConfigServlet和Log4jConfigListener,不管采用那种方式,都必须保证能够在装载配置文件前装载Log4J.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <!--指定配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>  
                  /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml
        </param-value>
    </context-param>
    <!-- 指定Log4j配置文件的位置 -->
    <context-param>
       <param-name>Log4jConfigLocation</param-name>
        <param-value>  
                  /WEB-INF/log4j.properties            
         </param-value> 
    </context-param>

    <!-- 装载Log4J配置文件的自启动Servlet -->
    <servlet>
        <servlet-class>
            org.springframework.web.util.Log4jConfigServlet 
        </servlet-class>
        <load-on-startup>1</load-on-startup>
      </setvlet>

    <!-- 声明自启动的Servlet-->
    <servlet>
        <servlet-class>
            org.springframework.web.context.ContextLoaderServlet
        </servlet-class>
         <!-- 启动顺序 -->
         <load-on-startup>2</load-on-startup>
    </servlet>
    </web-app>

采用上述配置方法,Spring将自动使用XmlWebApplicationContext启动Spring容器,即通过xml配置为Spring容器提供Bean配置信息。
如果使用标注@Configuration的java类提供配置信息,则web.xml需要按以下方式配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <!--通过指定context参数,让Spring使用GroovyWebApplicationContext而非
    XmlWebApplicationContext或AnnotationConfigWebApplicationContext启动容器 -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- 指定标注了@Configuration注解的配置类-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            com.smart.AppConfig1
        </param-value>
    </context-param>

    <!-- ContextLoaderListener监听器将根据上面配置使用
         AnnotationConfigWebApplicationContext根据contextConfigLocation
         指定的配置类启动Spring容器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

</web-app>

ContextLoaderListener如果发现了配置了contextClass上下文参数,就会使用参数所指定的WebApplicationContext实现类(AnnotationConfigWebApplicationContext)初始化容器,该实现类会根据contextConfigLocation上下文参数指定的标注@Configuration的配置类所提供的Spring配置信息初始化容器。
如果使用Groovy DSL配置Bean信息,则web.xml需要按照以下方式配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <!--通过指定context参数,让Spring使用GroovyWebApplicationContext而非
    XmlWebApplicationContext或AnnotationConfigWebApplicationContext启动容器 -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.GroovyWebApplicationContext
        </param-value>
    </context-param>

    <!-- 指定标注了Groovy的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:conf/spring-mvc.groovy
        </param-value>
    </context-param>

    <!-- ContextLoaderListener监听器将根据上面配置使用
         AnnotationConfigWebApplicationContext根据contextConfigLocation
         指定的配置类启动Spring容器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

</web-app>

GroovyWebApplicationContext实现类会根据ContextConfigLocation上下文参数指定的conf/spring_mvc.groovy所提供的Spring配置信息初始化容器。
2.3父子容器
通过HierarchicalBeanFactory接口,Spring的Ioc容器可以建立父子层级关联容器体系,子容器可以访问父容器的Bean,但父容器不能访问子容器的Bean。在容器内,Bean的id必须是唯一的,但是子容器可以拥有和父容器id相同的Bean。父子容器层级体系增强了Spring容器架构的扩展性和灵活性,因为第三方可以通过编程的方式为一个已经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外功能。
Spring使用父子容器实现了很多功能,比如在Spring MVC中,展现层Bean位于一个子容器中,而业务层和持久层Bean位于父容器层,这样,展现层可以应用业务层和持久层Bean,反之则不能!

3、小结

    为了解开Spring依赖注入的神秘面纱,透视Spring原理,我们对java语言的反射机制进行了快速与学习。掌握这些知识,我们不但可以深刻理解Spring的内部机制,还可以自己动手编写一个Ioc容器。
    BeanFactory、ApplicationContext、WebApplicationContext是Spring框架的3个核心接口,框架中其他大部分的类都围绕他们展开,为他们提供服务和支持,在这些支持类中,Resource是一个不可忽视的重要接口,框架通过Resource实现了和具体资源的解耦,不论他们位于何种存储介质中,都可以返回相同的实例。与Resource配合的另一个接口是ResourceLoader,ResourceLoader采用了策略模式,可以通过传入资源地址的信息,自动选择合适的底层实现类,为上层对资源的引用提供了极大地便利。

猜你喜欢

转载自blog.csdn.net/L970524/article/details/78546896
今日推荐