Spring4学习:IoC容器

一、概念

IoC(Inverse of Control)控制反转,及某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,即由Spring容器借由Bean配置来进行控制。后来软件界的Martin Fowler提出了DI(Dependency Injection)依赖注入的概念用来代替IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。

1、IoC的类型

(1)构造函数注入:通过调用类的构造函数,将接口实现类通过构造函数变量传入。

(2)属性注入:可以有选择地通过Setter方法完成调用类所需依赖的注入。

(3)接口注入:将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。

注:由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,因此不提倡采用接口注入的方式。

2、通过容器完成依赖关系的注入

Spring通过配置文件或注解描述类和类之间的依赖关系, 自动完成类的初始化和依赖注入工作。

二、Java反射机制

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义,下面是三个主要的反射类:

1、Constructor:通过Class#getConstructors()方法可以获取类的所有构造函数反射对象数组,在Java5.0中通过getConstructor(Class...parameterTypes)获取拥有特定入参的构造函数反射对象。Constructor的方法newInstance(Object[] initargs)可以创建一个对象类的实例,相当于new关键字。

2、Method:类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[],在Java5.0中可以通过getDeclaredMethod(String name,Class... parameterTypes)获取特定签名的方法,其中name为方法名;Class...为方法入参类型列表。Method的方法invoke(Object obj,Object[] args),可以调用该对象的方法,其中obj表示操作的目标对象;args为方法的参数。Method中的getReturnType()方法可以获取方法的返回值类型,getParameterTypes()获取方法的参数类型数组,getExceptionTypes()获取方法的异常类型数组,getParameterAnnotations()获取方法的注解信息。

3、Field:类的成员变量的反射类,通过Class#getDelaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)可以获取某个特定名称的成员变量反射对象。Field类的set(Object obj,Object value),其中obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,则用户可以使用Field类中提供的带类型名的值设置方法,如setBoolean(Object obj,boolean value)等。

在访问private或者protected成员变量和方法时,必须通过setAccessible(boolean access)方法取消Java语言检查,否则将抛出IllegalAccessException。如果JVM的安全管理器设置了相应的安全机制,那么调用该方法将抛出SecurityException。如下代码:

package com.smart reflect;
public class PrivateCar{
	private String color;
	protected void drive(){
		System.out.println("drive private car!the color is:"+color);
	}
}

public class PrivateCarReflect{
	public static void main(String args[])throws Throwable{
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		Class clazz = loader.loadClass("com.smart.reflect.PrivateCar");
		PrivateCar pcar = (PrivateCar)clazz.newInstance();
		
		Field colorFld = clazz.getDeclaredField("color");
		colorFld.setAccessible(true);
		colorFld.set(pcar,"红色");
		
		Method driveMtd = clazz.getDeclaredMethod("drive", (Class[])null);
		driveMtd.setAccessible(true);
		driveMtd.invoke(pcar, (Object[])null);
	}
}

三、资源访问

1、资源抽象接口

Spring设计了一个Resource接口,该接口拥有对应不同资源类型的实现类。Resource接口的主要方法如下:

(1)boolean exists():资源是否存在

(2)boolean isOpen():资源是否打开

(3)URL getURL()throws IOException:如果底层资源可以表示成URL,返回对应的URL对象。

(4)File getFile()throws IOException:如果底层资源对应一个文件,返回对应的File对象。

(5)InputStream getInputStream()throws IOException:返回资源对应的输入流

下图是Resource的具体实现类:


WritableResource:可写资源接口,有两个实现类FileSystemResource和PathResource。

FileSystemResource:文件系统资源,资源以文件系统路径的方式表示,如:D:/conf/bean.xml

PathResource:Spring4.0提供的读取资源文件的新类。Path封装了java.net.URL、java.nio.file.Path、文件系统资源,它使用户能够访问任何可以通过URL、Path、系统文件路径表示的资源,如文件系统的资源、HTTP资源、FTP资源等。

ByteArrayResource:二进制数组表示的资源,二进制数组资源可以在内存中通过程序构造。

ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示。

InputStreamResource:以输入流返回表示的资源

ServletContextResource:为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用根目录的路径加载资源。它支持以流和URL的方式访问,在WAR解包的情况下,也可以通过File方式访问。该类还可以直接从JAR包中访问资源。

UrlResource:URL封装了java.net.URL,它使用户能够访问任何可以通过URL表示的资源,如文件系统的资源、HTTP资源、FTP资源等。

假设有一个文件位于Web应用的类路径下,可以通过以下方式对此文件资源进行访问:

(1)通过FileSystemResource以文件系统绝对路径的方式进行访问

(2)通过ClassPathResource以类路径的方式进行访问

(3)通过ServletContextResource以相对于Web应用根目录的方式进行访问。

下面代码分别通过FileSystemResource和ClassPathResource访问同一个文件资源:

public class FileSourceExample{
	public static void main(String args[])throws Throwable{
		try{
			String filePath = "D:/masterSpring/code/chapter4/src/main/resources/conf/file1.txt";
			
			//使用系统文件路径方式加载文件
			WritableResource res1 = new PathResource(filePath);
			
			//使用WritableResource接口写资源文件
			OutputStream stream1 = res1.getOutputStream();
			stream1.write("Hello!");
			stream1.close();
			
			//使用类路径方式加载文件
			Resource res2 = new ClassPathResource("conf/file1.txt");
			
			//使用Resource接口读资源文件
			InputStream ins1 = res1.getInputStream();
			InputStream ins2 = res2.getInputStream();
			
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			int i;
			while((i = ins1.read()) != -1)
				baos.write(i);
			System.out.println(baos.toString());
			
			System.out.println("res1:" + res1.getFilename());
			System.out.println("res2:" + res2.getFilename());
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}
2、资源加载

(1)资源地址表达

Spring提供了一个强大的加载资源的机制,能够通过“classpath:”、“file“等资源地址前缀识别不同的资源类型,而不需要显示使用Resource的实现类,还支持Ant风格带通配符的 资源地址。如下图是资源类型的地址前缀:


当有多个JAR包或文件系统类路径都拥有一个相同的包名(如com.smart)时,"classpath:"只会在第一个加载的com.smart包的类路径下查找,而"classpath*:"会扫描所有这些JAR包及类路径下出现的com.smart类路径。

Ant风格的资源地址支持3种匹配符:

?:匹配文件名中的一个字符

*:匹配文件名中的任意字符

**:匹配多层路径

(2)资源加载器

Spring定义了一套资源加载的接口,并提供了实现类,如下图所示:


ResourceLoader接口仅有一个getResource(String location)方法,可以根据一个资源地址加载文件资源,但仅支持带资源类型前缀的表达式,不支持Ant风格的资源路径表达式。

ResourcePatternResolver扩展ResourceLoader接口,定义了一个新的接口方法getResource(String locationPattern),该方法支持带资源类型前缀及Ant风格的资源路径表达式。

PathMatchingResourcePatternResolver是Spring提供的标准实现类,如下代码使用此类,将扫描所有类路径下及JAR包中对应com.smart类包下的路径,读取所有以.xml为后缀的文件资源。

public class PatternResolverTest{
	@Test
	public void getResources() throws Throwable{
		ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		Resource resources[] = resolver.getResources("classpath*:com/smart/**/*.xml");
		assertNotNull(resources);
		for(Resource resource : resources){
			System.out.println(resource.getDescription());
		}
	}
}
注:用Resource操作文件时,如果资源配置文件在项目发布时会被打包到JAR中,那么不能使用Resource#getFile()方法,否则会抛出FileNotFoundException,但可以使用Resource#getInputStream()方法读取。

四、BeanFactory和ApplicationContext

BeanFactory是Spring框架最核心的接口,它提供了高级IoC的配置机制。BeanFactory使管理不同类型的Java对象成为可能,ApplicationContext建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory。
1、BeanFactory
(1)BeanFactory的类体系结构
如下图是BeanFactory的类体系结构

BeanFactory最主要的方法是getBean(String beanName)该方法从容器中返回特定名称的Bean。BeanFactory的功能通过其他接口得到不断扩展。
ListableBeanFactory:该接口定义了访问容器中Bean基本信息的若干方法,如查看Bean的个数,获取某一类型Bean的配置名、查看容器中是否包括某一Bean等。
HierarchicalBeanFactory:父子级联IoC容器的接口,子容器可以通过接口方法访问父容器。
ConfigurableBeanFactory:它定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法。
AutowireCapableBeanFactory:定义了将容器中的Bean按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法。
SingletonBeanFactory:定义了允许在运行期向容器注册单实例Bean的方法。
BeanDefinitionRegistry:Spring配置文件中每一个<bean>节点元素在Spring容器里都通过一个BeanDefinition对象表示,它描述了Bean的配置信息。而BeanDefinitionRegistry接口提供了向容器手工注册BeanDefinition对象的方法。
(2)初始化BeanFactory
使用Spring配置文件为Car提供配置信息,如下代码:
<?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="car1" class="com.smart.Car" init-method="myInit" destroy-method="myDestory"
         p:brand="红旗CA72"
         p:maxSpeed="200"/>
</beans>
然后通过BeanFactory装载配置文件,启动Spring Ioc容器。下面代码通过XmlBeanDefinitionReader、DefaultListableBeanFactory实现类启动Spring IoC容器
public class BeanFactoryTest{
	@Test
	public void getBean() throws Throwable{
		ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		Resource res = resolver.getResource("classpath:com/smart/beanfactory/beans.xml");
		
		DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
		reader.loadBeanDefinitions(res);
		
		Car car = factory.getBean("Car",Car.class);
		car.introduce();
	}
}
通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时。对于单例的Bean,BeanFactory会缓存Bean实例,所以第二次使用getBean()获取Bean时,将直接从IoC容器的缓存中获取Bean实例。
Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。

2、ApplicationContext
ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径加载配置文件,后者默认从文件系统中装载配置文件。
如果配置文件放在类路径下,可以使用ClassPathXmlApplicationContext初始化ApplicationContext,如下:
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/context/beans.xml");
如果配置文件放在文件系统的路径下,可以使用FileSystemXmlApplicationContext实现类,如下:
ApplicationContext ctx = new FileSystemXmlApplicationContext("com/smart/context/beans.xml");
还可以指定一组配置文件,Spring会自动将多个配置文件在内存中“整合”成一个配置文件,如下:
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});
在获取ApplicationContext实例后,可以调用getBean(String beanName)返回Bean了。BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean;而ApplicationContext在初始化应用上下文时就实例化所有单实例的Bean。
Spring为基于注解类的配置提供了专门的ApplicationContext实现类:AnnotationConfigApplicationContext。如下在类上标注@Configuration注解即可提供Spring所需的Bean配置信息。
@Configuration
public class Beans {

	@Bean(name = "car")
	public Car buildCar() {
		Car car = new Car();
		car.setBrand("红旗CA72");
		car.setMaxSpeed(200);
		return car;
	}
}
使用AnnotationConfigApplicationContext启动Spring容器:
public class AnnotationApplicationContext {

	 public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(Beans.class);
		Car car =ctx.getBean("car",Car.class);
	}
}
3、WebApplicationContext
WebApplicationContext允许从相对于Web根目录的路径装载配置文件完成初始化工作。从ServletContext中可以获得WebApplicationContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。Spring提供了一个工具类WebApplicationContextUtils,通过该类的getWebApplicationContext(ServletContext sc)方法,可以从ServletContext中获取WebApplicationContext实例。
ConfigurableWebApplicationContext扩展了WebApplicationContext,允许通过配置的方式实例化WebApplicationContext,此类的setServletContext(ServletContext servletContext)可以为Spring设置Web应用上下文,setConfigLocations(String[] configLocations):设置Spring配合文件地址,一般配置文件的地址是相对于Web根目录的地址,如/WEB_INF/smart-dao.xml。

WebApplicationContext的初始化需要ServletContext实例,也就是说他必须在拥有Web容器的前提下才能完成启动工作。在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListener),借助二者中的任一个,可以完成启动Spring Web应用上下文的工作。

Spring提供了用于启动WebApplicationContext的Servlet和Web容器监听器:
org.springframework.web.context.contextLoaderServlet和org.springframework.web.context.ContextLoaderListener
如下代码是使用ContextLoaderListener启动WebApplicationContext的配置:
<!-- 指定配置文件--> 
<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>
ContextLoaderListener通过Web容器上下文contextConfigLocation获取Spring配置文件的位置。对于未带资源类型前缀的配置文件路径,WebApplicationContext默认这些路径相对于Web的部署根路径。
在不支持容器监听器的低版本Web容器中,采用ContextLoaderServlet完成相同的工作,如下代码:
<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-name>contextLoaderServlet</servlet-name>
	<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>




如果使用标注@Configuration的Java类提供配置信息,则web.xml需要按以下方式配置:

<webapp>
    <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,com.smart.AppConfig2
        </param-value>
    </context-param>
    <!-- ContextLoaderListener监听器将根据上面配置使用
         AnnotationConfigWebApplicationContext根据contextConfigLocation
         指定的配置类启动Spring容器-->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
</web-app>
4、父子容器

通过HierarchicalBeanFactory接口,Spring的IoC容器可以建立父子层级关联的容器体系,自容器可以访问父容器中的Bean,但父容器不能访问子容器中的Bean。在容器内,Bean的id必须是唯一的,但子容器可以拥有一个和父容器id相同的Bean。

Spring使用父子容器实现类很多功能,比如在SpringMVC中,展现层Bean位于一个自容器中,而业务层和持久层Bean位于父容器中。这样,展现层Bean就可以引用业务层和持久层Bean,而业务层和持久层Bean则看不到展现层Bean。

五、Bean的生命周期

1、BeanFactory中Bean的生命周期

上图描述了BeanFactory中Bean的生命周期的完整过程,可以将这些方法大致划分为4类:

(1)Bean自身方法:如调用Bean构造函数实例化Bean、调用Setter设置Bean的属性值及通过<bean>的init-method和destroy-method所指定的方法。

(2)Bean级生命周期接口方法:如BeanNameAware、BeanFactoryAware、InitializingBean和DisposableBean,这些接口的方法可以由Bean类直接实现。

(3)容器级生命周期接口方法:上图中带星号的步骤是由InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口的方法实现的,一般称它们的实现类为“后处理器”。后处理器接口一般不由Bean本身实现,它们独立于Bean,实现类以容器附加装置的形式注册到Spring容器中,并通过接口反射为Spring容器扫描识别。当Spring容器创建任何Bean的时候, 这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。

(4)工厂后处理器接口方法:包括AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等方法。工厂后处理器也是容器级的,在应用上下文装配配置文件后立即调用。

Spring容器可以注册多个后处理器,只要它们同时实现org.springframework.core.Ordered接口,容器将按特定的顺序依次调用这些后处理器。

2、ApplicationContext中的Bean的生命周期



与BeanFactory中Bean的生命周期不同的是:

(1)如果Bean实现了org.springframework.context.ApplicationContextAware接口,则会增加一个调用该接口方法setApplicationContext()的步骤

(2)ApplicationContext会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFActoryPostProcessor,并自动将它们注册到应用上下文中;而BeanFactory需要在代码中通过手工调用addBeanPostProcessor()方法进行注册。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值