Spring框架入门笔记——IoC

本文只是本人自学的笔记记录和敲得代码记录,希望能有地方让自己多温习和回顾,之前学习设计模式的内容暂时放下,回头补上,希望今年一年能好好坚持学习下去,努力沉淀,积累知识。

一、依赖注入的使用

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="personService"
        class="lee.PersonService">
    <property name="name" value="天水伯约"/>
  </bean>

</beans>
package lee;


public class PersonService {


private String name;
public void setName(String name){
this.name = name;
public void info() {
System.out.println("此人名为:"+name);
}
}
package lee;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class SpringTest {


public static void main(String[] args) {


ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
System.out.println(ctx);
//PersonService p = (PersonService)ctx.getBean("personService");
PersonService p = ctx.getBean("personService", PersonService.class);
p.info();
}


}
2015-3-13 21:52:27 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2dcb25f1: startup date [Fri Mar 13 21:52:27 CST 2015]; root of context hierarchy
2015-3-13 21:52:28 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [bean.xml]
org.springframework.context.support.ClassPathXmlApplicationContext@2dcb25f1: startup date [Fri Mar 13 21:52:27 CST 2015]; root of context hierarchy
此人名为:天水伯约
此段代码中创建的ApplicationContext实例就是我们所说的Spring容器,通过此容器可以获得任何配置的Bean,这种通过Spring容器为对象设置属性的方式称为控制反转(Inversion of Control,IoC),也叫作依赖注入(Dependency Injection ,DI)

依赖注入不仅可以注入普通属性,还可以注入其他Bean的引用。依赖注入让Spring的Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起,达到很好的解耦目的。

依赖注入通常有两种方式,设置注入:IoC容器使用属性的setter方法来注入被依赖的实例。构造注入:IoC容器使用构造器来注入被依赖的实例。

Spring推荐面向接口的编程,这样可以更好的让规范和实现分离,从而提供更好的解耦。对于Java EE应用,不管是DAO组件还是业务逻辑组件,都应该先定义一个接口,结构定义组件应该实现的功能,功能的具体实现由实现类来提供。

<bean id="stoneAxe" class="org.crazyit.app.service.impl.StoneAxe" />
  <bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
  	<property name="axe" ref="stoneAxe" />
  </bean>
package org.crazyit.app.service;


public interface Persion {


//定义Person接口 ,定义一个使用斧子的方法
public void useAxe();
}
package org.crazyit.app.service;


public interface Axe {


//斧子Axe接口有一个砍的方法
public String chop();
}
package org.crazyit.app.service.impl;


import org.crazyit.app.service.Axe;
import org.crazyit.app.service.Persion;


public class Chinese implements Persion {


private Axe axe;
public void setAxe(Axe axe) {
this.axe = axe;
}
@Override
public void useAxe() {
System.out.println(axe.chop());
}


}
package org.crazyit.app.service.impl;


import org.crazyit.app.service.Axe;


public class StoneAxe implements Axe {


@Override
public String chop() {

return "石斧砍柴很慢!";
}


}
public static void main(String[] args) {


ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
Chinese c = ctx.getBean("chinese", Chinese.class);
c.useAxe();
}
石斧砍柴很慢!
如果Bean.xml中为chinese实例配置依赖是写的是<property name="axe" value="stoneAxe" /> 会提示错误,大致是说stionAxe是字符串类型,需要的属性axe是Axe类型的,无法注入,所以成员变量中是其他类的实例的时候,需要用ref去注入另外一个类的实例。

这里Spring用到了反射,根据反射来创建类的实例。所以StoneAxe类必须提供默认的构造方法,Chinese类必须有setAxe方法。否则无法完成注入。

下面来解释上面这段代码的好处,以前没用Spring框架的话,我们是这样写测试类的:

package org.crazyit.app.service.impl;


import org.crazyit.app.service.Axe;


public class SteelAxe implements Axe {


@Override
public String chop() {
return "钢斧砍柴真快!";
}
}
public static void main(String[] args) {
		Chinese c = new Chinese();
		Axe axe = new StoneAxe();
		c.setAxe(axe);
		c.useAxe();
	}
现在如果人觉得石斧砍柴太慢,想换成其他的斧子,比如钢斧的话,需要改动java中的代码,改成Axe axe = new SteelAxe();这种改变代码是不建议去做的,我们使用了Spring的话,我们只需要定义一个钢斧SteelAxe的实例,并将该实例注入到Chinese实例中。

 <bean id="steelAxe" class="org.crazyit.app.service.impl.SteelAxe" />
  <bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
  	<property name="axe" ref="steelAxe" />
  </bean>
钢斧砍柴真快!
从这个例子也可以看得出来,面向接口编程的好处,切换实现类的时候很方便,改动量最小。

上面的注入是通过Chinese类中setAxe(Axe axe)方法注入的,这种setter方法为目标Bean注入所依赖的Bean,称为设置注入。

下面来介绍构造注入:

只需要将Chinese中的setAxe方法改成构造方法,并将配置文件中的<property /> 改为<constructor-arg />即可

public Chinese (Axe axe) {
		this.axe = axe;
	}
 <bean id="chinese" class="org.crazyit.app.service.impl.Chinese">
<constructor-arg name="axe" ref="steelAxe"/>
  </bean>
钢斧砍柴真快!
<constructor-arg  />这个元素有一个index属性,表示作为构造函数中的第几个参数传进去。例如

public class PersonService {
	private String name;
	private String age;
	public PersonService(String name, String age) {
		super();
		this.name = name;
		this.age = age;
	}
	public void info() {
		System.out.println("此人名为:"+name+" , 今年 "+age +" 岁了!");
	}
}
  <bean id="personService" class="lee.PersonService">
    <constructor-arg index="0" value="天水伯约" />
    <constructor-arg index="1" type="String" value="1" />
  </bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
PersonService p = ctx.getBean("personService", PersonService.class);
<pre name="code" class="java"><span style="font-family: Arial, Helvetica, sans-serif;">p.info();</span>
 
此人名为:天水伯约 , 今年 1 岁了!
设值注入和构造注入效果是一样的,区别在于创建Person实例中Axe属性的时机不同,设值注入是先通过无参数构造器创建一个Bean实例,之后在调用setter方法注入axe的依赖关系;而构造注入则是直接调用带参数的构造器,当Bean实例创建完成后,已经完成了依赖关系的注入。

设值注入有以下好处:与传统的JavaBean写法更类似,程序开发人员更容易理解和接受,通过setter方法设定依赖关系显得直观和自然;对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读,尤其是在某些属性可选的情况下,多参数的构造器更加笨重。

建议采用设值注入为主,构造注入为辅,对于依赖关系无须变化的注入,尽量采用构造注入;而其他的依赖关系注入,则考虑设值注入。


二、了解Spring容器,Spring容器的使用

Spring有两个核心接口:BeanFactory和ApplicationContext,其中ApplicationContext接口是BeanFactory接口的子接口。他们都可以代表Spring的容器,Spring容器是生产Bean实例的工厂,并管理容器中的Bean。在Java EE应用中,数据源,Hibernate的SessionFactory,事务管理器都可以被当成Bean处理。

下面是BeanFactory中的方法:


ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		System.out.println(ctx.getClassLoader());
		String[] arr = ctx.getAliases("stoneAxe");
		System.out.println(arr.length);
		System.out.println(arr[0]);
		arr = ctx.getAliases("myAxe");
		System.out.println(arr.length);
		System.out.println(arr[0]);
		System.out.println(ctx.getBean(SteelAxe.class).chop());
		System.out.println(ctx.getType("stoneAxe"));
		System.out.println(ctx.isPrototype("stoneAxe"));
		System.out.println(ctx.isSingleton("stoneAxe"));
		System.out.println(ctx.isTypeMatch("stoneAxe", SteelAxe.class));
sun.misc.Launcher$AppClassLoader@53004901
1
myAxe
1
stoneAxe
钢斧砍柴真快!
class org.crazyit.app.service.impl.StoneAxe
false
true
false

BeanFactory有一个常用的实现类:org.springframework.beans.factory.xml.XmlBeanFactory类

ApplicationContext是BeanFactory类的子接口,对于大部分Java EE应用来说,使用它作为Spring的容器更方便。其常用实现类是FileSystemXmlApplicationContext、ClassPathXmlApplicationContext和 AnnotationConfigApplicationContext。如果在Web应用中使用Spring容器,通常有XmlWebApplicationContext、AnnotationConfigWebApplicationContext两个实现类。

创建Spring容器实例的时候,必须提供Spring容器管理的Bean详细配置信息。Spring的配置信息通常采用XML配置文件来设置,XML配置文件通常使用Resource对象传入。

(Resource是一个接口,为Spring提供资源访问的接口,详细请看后续介绍)

XmlBeanFactory类实例化需要传入一个Resource资源,可以通过下面两种方式创建FileSystemResource 或者ClassPathResource 传入。XmlBeanFactory在Spring3.1之后已经被废弃了,不建议再使用了。

//		FileSystemResource resource = new FileSystemResource("bean.xml");
//bean.xml需要放在项目的根目录下,如D:\myproject\job_study\myspring\bean.xml
//		XmlBeanFactory ctx = new XmlBeanFactory(resource);
		
		ClassPathResource resource = new ClassPathResource("bean.xml");//bean.xml需要放在src的根目录下
		XmlBeanFactory ctx = new XmlBeanFactory(resource);
大部分时候我们都不是用BeanFactory作为容器,而建议使用ApplicationContext接口作为容器(也称作上下文),如果加载多个配置文件,则必须使用ApplicationContext,有如下两种方法实现加载多个配置文件:

//配置文件需要放在src的根目录下  或者"classpath:/lee/bean.xml"这种写法
		ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"bean.xml","service.xml"});
		//配置文件需要放在SteeAxe包的路径下
		ctx = new ClassPathXmlApplicationContext(new String[]{"bean.xml","service.xml"},SteelAxe.class);
		//配置文件放在项目的根目录下
		ctx = new FileSystemXmlApplicationContext(new String[]{"bean.xml","service.xml"});
Applicationcontext实现BeanFactory接口的方法外,还增加以下几个功能:

1、ApplicationContext继承MessageResource接口,因此提供国际化支持;

2、资源访问,如URL和文件;

3、事件机制;

4、载入配置多个文件;

5、以声明式的方式启动并创建Spring容器。

实现国际化支持的例子:

<bean id="messageSource"
      class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basenames">
    <list>
      <value>message</value>
    </list>
  </property>
</bean>
message.properties 和 message_zh_CN.properties
hello=\u6B22\u8FCE\u4F60\uFF0C{0}
now=\u73B0\u5728\u662F\uFF1A{0}
message_en_US.properties
hello=welcome:{0}
now=now is :{0}
public static void main(String[] args) {
<span style="white-space:pre">		</span>ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
<span style="white-space:pre">		</span>String msg1 = ctx.getMessage("hello", new String[]{"ReMinD"}, Locale.getDefault());
<span style="white-space:pre">		</span>String msg2 = ctx.getMessage("now", new Object[]{new Date()}, Locale.getDefault());
<span style="white-space:pre">		</span>System.out.println("中文环境下显示:"+msg1);
<span style="white-space:pre">		</span>System.out.println("中文环境下显示:"+msg2);
<span style="white-space:pre">		</span>String msg3 = ctx.getMessage("hello", new String[]{"ReMinD"}, Locale.US);
<span style="white-space:pre">		</span>String msg4 = ctx.getMessage("now", new Object[]{new Date()}, Locale.US);
<span style="white-space:pre">		</span>System.out.println("美国英语环境下显示:"+msg3);
<span style="white-space:pre">		</span>System.out.println("美国英语环境下显示:"+msg4);
<span style="white-space:pre">	</span>}
中文环境下显示:欢迎你,ReMinD
中文环境下显示:现在是:15-3-15 下午2:11
美国英语环境下显示:welcome:ReMinD
美国英语环境下显示:now is :3/15/15 2:11 PM
ApplicationContext的事件机制:

ApplicationContext的事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext的事件处理。如果容器中有一个ApplicationLinterer Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener将自动被触发。

创建一个Spring的容器事件(必须由ApplicationContext发布)

public class EmailEvent extends ApplicationEvent {

	private String address;
	private String text;
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	public EmailEvent(Object source) {
		super(source);
	}
	public EmailEvent(Object source,String address,String text) {
		super(source);
		this.address = address;
		this.text = text;
	}
}

监听器,每当容器中发生任何时间时,方法(onApplicationEvent(ApplicationEvent event))都被触发。

public class EmailNotifier implements ApplicationListener {
	@Override
	public void onApplicationEvent(ApplicationEvent evn) {
		if(evn instanceof EmailEvent) {
			EmailEvent event = (EmailEvent)evn;
			System.out.println("邮件地址为:"+event.getAddress()+" 邮件内容为:"+event.getText());
		} else {
			System.out.println("容器本身的事件 "+ evn);
		}
	}
}
将监听器配置在容器中
<bean class="org.crazyit.app.listener.EmailNotifier" />
测试代码:
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
		EmailEvent ele = new EmailEvent("hello","spring_test@163.com","Spring的事件机制");
		ctx.publishEvent(ele);
测试结果:
容器本身的事件 org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.ClassPathXmlApplicationContext@742808b3: startup date [Sun Mar 15 14:49:54 CST 2015]; root of context hierarchy]
邮件地址为:spring_test@163.com 邮件内容为:Spring的事件机制

让Bean获得Spring容器:

上面的例子中我们都是通过手动创建ApplicationContext类的对象,并表用他的getBean方法,获得容器中管理的Bean,在Web应用中,Spring容器通常是采用声明式方式配置产生,开发者只要在web.xml中配置一个Listener,该Listener会负责初始化Spring容器,并把被依赖的Bean注入到依赖的Bean中。

在特殊情况下,容器中的Bean可能需要主动访问Spring容器本身,为了实现这种要求,只需要将Bean实现BeanFactoryAware接口就可以了。实现该接口需要实现下面方法:

setBeanFactory(BeanFactory beanFactory),该方法的参数beanFactory指向创建他的BeanFactory,这个方法时由Spring调用,Spring调用该方法时, 会将Spring容器作为参数传入该方法。与这种特殊方法类似的接口还有ApplicationContextAware 、BeanNameAware、ResourceLoaderAware

public class Chinese implements ApplicationContextAware {
	private ApplicationContext ctx;
	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.ctx = applicationContext;
	}
	public ApplicationContext getApplicationContext() {
		return ctx;
	}
}
<bean id="chinese" class="org.crazyit.app.service.Chinese" />
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
<span style="white-space:pre">		</span>Chinese chinese = ctx.getBean("chinese", Chinese.class);
<span style="white-space:pre">		</span>ApplicationContext ctx2 = chinese.getApplicationContext();
<span style="white-space:pre">		</span>System.out.println(ctx.equals(ctx2));
true
实现ApplicationContextAware接口,虽然让Bean拥有了访问容器的能力,但是污染了代码,导致代码和Spring接口耦合在一起,因此,如果不是特别必要,建议不要直接访问容器。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值