该文是学习《精通Spring》的学习笔记。
一、IoC设计模式
1.1 BeanFactory与ApplicationContext
ApplicationContext继承了BeanFactory,BeanFactory主要用于开发java应用,ApplicationContext主要用于j2ee应用
1.1.1 Bean的生命周期
任何处于IoC容器控制下的javabean的生命周期都存在4个阶段
1、实例化JavaBean。
2、JavaBean实例的初始化,即通过IoC注入其依赖性。
3、基于Spring应用对JavaBean实例的使用。
4、IoC容器销毁JavaBean实例。
IoC设计模式,重点关注组件的依赖性、配置及生命周期,具有“依赖注入”这个昵称。具有以下几方面优势:
1、 应用组件不需要在运行时寻找其协作者,因此更易于开发和编写应用
2、 借助于依赖关系,更易于单元测试和集成测试
3、 通过使用IoC能降低组件之间的耦合度
1、 Bean的创建
很多时候,需要借助于工厂方法创建JavaBean实例,
<bean name="fileHelloWorld" class="example4.HelloWorld" factory-method="createHelloWorldInstance">
此时,HelloWorld必须提供createHelloWorldInstance这个静态方法
当然,也可以委托给helloWorldFactory另一个bean
<bean name="fileHelloWorld" class="example4.HelloWorld" factory-bean="helloWorldFactory" factory-method="createHelloWorldInstance">
2、 初始化JavaBean
首先,如果开发者使用了<bean>元素的autowire属性,则借助于Spring提供的autowire功能,Spring能够自动将目标JavaBean需要注入的JavaBean找到,并注入进来。
其次,如果开发者使用了<bean>元素的dependency-check属性,则能够保证各个Spring配置文件中的各个JavaBean之间的相互关系
第三,借助于setter方法,能够将javabean的属性值注入进来,这些属性值可以是java原型,对象类型,其他bean,甚至是Null
第四,如果JavaBean实现了如下接口,则还需要调用setBeanFactory方法。
org.springframework.beans.factory.BeanFactoryAware
第五,Spring框架提供了若干接口,供开发者改变配置在BeanFactory中的Javabean的行为使用。
最后,在bean元素中包含init-method方法达到同InitializingBean一样的目的。
3、 使用JavaBean
一旦Spring创建,并初始化JavaBean实例后,应用就能够使用JavaBean实例了,因此,借助于getBean方法,开发者就能够在应用中使用它了。
4、 销毁JavaBean
一旦将基于Spring的(Web)应用停止Spring将调用那些JavaBean实例中存在的生命周期方法,或者那些在Spring配置文件中指定了destory-method属性的JavaBean。最终,Spring将销毁JavaBean实例。这些仅适用于那些通过“singleton”方式创建的JavaBean实例。对于那些以“prototype”方式创建的JavaBean实例,Spring并不能够控制其生命周期,因为一旦这种方式创建成功,整个JavaBean将交给Spring应用去管理。
1.2 ApplicationContext
对于Spring BeanFactory而言,如果用户没有调用getBean()方法,则使用到的JavaBean实例不会被创建,因此,在BeanFactory中使用了延迟装载机制。对于ApplicationContext,一旦ContextLoaderServlet或ContextLoaderListener初始化成功,所有JavaBean实例会被创建。
ApplicationContext继承BeanFactory,因此推荐使用ApplicationContext。
1.Web应用中创建ApplicationContext
通过ContextLoaderServlet或ContextLoaderListener能自动创建ApplicationContext实例,当然,开发者也可以自己手工创建。
2. ApplicationContext接口实现
基于ApplicationContext接口,Spring提供了若干实现。开发者经常使用到如下3个实现:
l ClassPathXmlApplicationContext
在Web应用中,开发者可以从classpath中即WEB-INF/classes或WEB-INF/lib的jar中装载Spring配置文件
l FileSystemXmlApplicationContext
开发者可以从文件系统中装载Spring配置文件。
l XmlWebApplicationContext:供ContextLoaderListener或ContextLoaderServlet内部装载Spring配置文件使用
依赖注入实例:
1、先编写helloworld.properties文件,内容如下:
helloworld =Hello world!
这是消息文件。
2、在src下,新建一applicationContext.xml文件,内容如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean name="fileHelloWorld" class="example4.HelloWorld">
<!-- 通过构造函数传入一个对象,该对象由fileHello这个bean创建 -->
<constructor-arg>
<ref bean="fileHello" />
</constructor-arg>
</bean>
<!-- 直接将helloworld.properties的值传给FileHelloStr类里的propfilename,通过构造函数传入值,constructor-arg标签是指通过构造函数传参 -->
<bean name="fileHello" class="example4.FileHelloStr">
<!-- 通过构造函数传值 -->
<constructor-arg>
<value>helloworld.properties</value>
</constructor-arg>
<!-- 亦可以通过property标签赋值,但类里的propfilename属性需要有get,set方法,而且还要有空的构造函数 -->
<!-- <property name="propfilename" value="helloworld.properties"></property> -->
</bean>
</beans>
该配置表中,构造了一个叫"fileHelloWorld"的bean,指向example4.HelloWorld.java类,通过向该类的构造方法,传入另一个叫“fileHello”的bean(实际bean也即是一个类),该“fileHello”的bean指向example4.FileHelloStr.java类,也是通过向该类的构造方法,传入一字符串“helloworld.properties”实际是传入消息文件名,以读取消息。
3、现在开始编写类
首先,编写一接口HelloStr,定义一方法 getContent();
package example4;
public interface HelloStr {
public String getContent();
}
FileHelloStr .java该类是负责读取内容的。代码如下:
package example4;
import java.io.InputStream;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class FileHelloStr implements HelloStr {
protected static final Log log = LogFactory.getLog(FileHelloStr.class);
private String propfilename;
/*
* 通过非构造函数赋值,需要添加个空的构造函数,而且propfilename也要有get,set方法
* */
// public FileHelloStr(){
//
// }
// public String getPropfilename() {
// return propfilename;
// }
//
//
// public void setPropfilename(String propfilename) {
// this.propfilename = propfilename;
// }
public FileHelloStr(String propfilename) {
this.propfilename = propfilename;
}
@Override
public String getContent() {
String helloworld = "";
try {
//read the properties file
Properties properties = new Properties();
InputStream is = getClass().getClassLoader().getResourceAsStream(propfilename);
properties.load(is);
is.close();
helloworld = properties.getProperty("helloworld");
} catch (Exception e) {
log.info(e);
}
return helloworld;
}
}
HelloWorld.java是具体的调用类。
package example4;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HelloWorld {
protected static final Log log = LogFactory.getLog(HelloWorld.class);
private HelloStr hStr;
public HelloWorld(HelloStr hStr){
this.hStr = hStr;
}
public String getContent(){
return hStr.getContent();
}
}
客户端类,测试:
package example4;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloWorldClient {
/*
* 创建了单实例的HelloWorld和FileHelloStr,通过spring配置文件中的bean元素的singleton属性可以控制创建java实例的方式
*/
protected static final Log log = LogFactory.getLog(HelloWorldClient.class);
public static void main(String[] args) {
// HelloWorld hw = HelloWorldFactory.getFileHelloWorld();
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld hw = (HelloWorld) ac.getBean("fileHelloWorld");
log.info(hw.getContent());
}
}
运行客户端类,输出结果如下:
2011-4-13 10:36:33 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5: display name [org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5]; startup date [Wed Apr 13 10:36:33 CST 2011]; root of context hierarchy
2011-4-13 10:36:34 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
2011-4-13 10:36:34 org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
信息: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1193779
2011-4-13 10:36:34 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1193779: defining beans [fileHelloWorld,fileHello]; root of factory hierarchy
2011-4-13 10:36:34 example4.HelloWorldClient main
信息: Hello world!
整个项目图如下:
二、Spring AOP
1、前置通知(Before)
先定义接口IHelloWorld.java
package example5;
public interface IHelloWorld {
public String getContentText(String text);
public String getContent(String helloworld);
}
接口实现类HelloWorld.java
package example5;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HelloWorld implements IHelloWorld {
protected static final Log log = LogFactory.getLog(HelloWorld.class);
@Override
public String getContent(String helloworld) {
log.info(helloworld+"...");
return helloworld;
}
@Override
public String getContentText(String text) {
log.info(text+"===");
return text;
}
}
客户端调用类HelloClient.java:
package example5;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/*
* 前置通知
* before : The Invocation of getContent.这句是在调用了getContent方法之前输出的
* */
public class HelloClient {
protected static final Log log = LogFactory.getLog(HelloClient.class);
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext( "example5/appcontext.xml");
IHelloWorld hw = (IHelloWorld) ac.getBean("helloworldbean");
log.info(hw.getContent("TTT"));
log.info(hw.getContentText("text text"));
}
}
前置通知类LoggingBeforeAdvice.java,实现MethodBeforeAdvice接口。
package example5;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;
public class LoggingBeforeAdvice implements MethodBeforeAdvice {
protected static final Log log = LogFactory.getLog(LoggingBeforeAdvice.class);
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
log.info("before : The Invocation of getContent.");
}
}
以上几个类都是独立的类,前置通知类与其他类相对独立,关键是xml配置表中配置使其起作用。
appcontext.xml如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="helloworldbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>example5.IHelloWorld</value>
</property>
<property name="target">
<ref local="helloworldbeanTarget" />
</property>
<property name="interceptorNames">
<list>
<value>loggingBeforeAdvisor</value>
</list>
</property>
</bean>
<bean id="helloworldbeanTarget" class="example5.HelloWorld"></bean>
<bean id="loggingBeforeAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="loggingBeforeAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean>
<bean id="loggingBeforeAdvice" class="example5.LoggingBeforeAdvice"></bean>
</beans>
整个项目图如下:
运行,结果如下:
2011-4-13 10:50:34 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5: display name [org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5]; startup date [Wed Apr 13 10:50:34 CST 2011]; root of context hierarchy
2011-4-13 10:50:34 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [example5/appcontext.xml]
2011-4-13 10:50:34 org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
信息: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@19c26f5]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1193779
2011-4-13 10:50:34 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1193779: defining beans [helloworldbean,helloworldbeanTarget,loggingBeforeAdvisor,loggingBeforeAdvice]; root of factory hierarchy
2011-4-13 10:50:34 example5.LoggingBeforeAdvice before
信息: before : The Invocation of getContent.
2011-4-13 10:50:34 example5.HelloWorld getContent
信息: TTT...
2011-4-13 10:50:34 example5.HelloClient main
信息: TTT
2011-4-13 10:50:34 example5.LoggingBeforeAdvice before
信息: before : The Invocation of getContent.
2011-4-13 10:50:34 example5.HelloWorld getContentText
信息: text text===
2011-4-13 10:50:34 example5.HelloClient main
信息: text text
2、后置通知
先定义接口:
package example6;
public interface IHelloWorld {
public String getContent(String helloworld);
}
HelloWorld.java的代码:
package example6;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HelloWorld implements IHelloWorld {
protected static final Log log = LogFactory.getLog(HelloWorld.class);
@Override
public String getContent(String helloworld) {
log.info(helloworld+"...");
return helloworld;
}
}
HelloClient.java代码:
package example6;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/*
* 后置通知
* after : The Invocation of getContent. 这句是在调用了getContent方法以后输出的
* */
public class HelloClient {
protected static final Log log = LogFactory.getLog(HelloClient.class);
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext( "example6/appcontext.xml");
IHelloWorld hw = (IHelloWorld) ac.getBean("helloworldbean");
log.info("main方法输出:"+hw.getContent("wenbin"));
}
}
后置通知类:
package example6;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;
public class LoggingAfterAdvice implements AfterReturningAdvice{
protected static final Log log = LogFactory.getLog(LoggingAfterAdvice.class);
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
log.info("after : The Invocation of getContent.");
}
}
appcontext.xml内容如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="helloworldbean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 接口名构成的字符串列表,即接口集合 -->
<property name="proxyInterfaces">
<list><value>example6.IHelloWorld</value></list>
</property>
<!-- 执行目标类 -->
<property name="target">
<ref local="helloworldbeanTarget" />
</property>
<!-- 拦截器构成的字符串列表,即拦截器集合 -->
<property name="interceptorNames">
<list>
<value>loggingAfterAdvisor</value>
</list>
</property>
</bean>
<bean id="helloworldbeanTarget" class="example6.HelloWorld"></bean>
<bean id="loggingAfterAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="loggingAfterAdvice"/>
</property>
<property name="pattern">
<value>.*</value>
</property>
</bean>
<bean id="loggingAfterAdvice" class="example6.LoggingAfterAdvice"></bean>
</beans>
环绕通知:该通知最灵活,可以在事件执行前后调用,而且可以更改消息
IHelloWorld .java代码如下:
package example8;
public interface IHelloWorld {
public String getContent(String helloworld);
}
HelloWorld .java代码:
package example8;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HelloWorld implements IHelloWorld {
protected static final Log log = LogFactory.getLog(HelloWorld.class);
@Override
public String getContent(String helloworld) {
log.info(helloworld+"...");
return helloworld;
}
}
客户端HelloClient .java:
package example8;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/*
* 环绕通知
* 在执行方法的前后执行
* */
public class HelloClient {
protected static final Log log = LogFactory.getLog(HelloClient.class);
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext( "example8/appcontext.xml");
IHelloWorld hw = (IHelloWorld) ac.getBean("helloworldbean");
log.info("main方法输出:"+hw.getContent("wenbin"));
}
}
环绕通知类:
package example8;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/*
* 环绕通知
* 可在方法执行前后执行
* */
public class LoggingAroundAdvice implements MethodInterceptor{
protected static final Log log = LogFactory.getLog(LoggingAroundAdvice.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("before : The Invocation of getContent. the output params is :"+invocation.getArguments()[0]);
invocation.getArguments()[0] = "wenbin"; //invocation.getArguments()[0]就是截获的第一个参数,由hw.getContent("wenbin")里传入的参数提供,可以人工改调传输过程的参数,实现截获信息并修改信息
invocation.proceed();
log.info("after : The Invocation of getContent. and the output params is :"+invocation.getArguments()[0]);
return null;
}
}
appcontext.xml代码:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="helloworldbean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 接口名构成的字符串列表,即接口集合 -->
<property name="proxyInterfaces">
<list>
<value>example8.IHelloWorld</value>
</list>
</property>
<!-- 执行目标类 -->
<property name="target">
<ref local="helloworldbeanTarget" />
</property>
<!-- 拦截器构成的字符串列表,即拦截器集合 -->
<property name="interceptorNames">
<list>
<value>loggingAroundAdvisor</value>
</list>
</property>
</bean>
<bean id="helloworldbeanTarget" class="example8.HelloWorld"></bean>
<bean id="loggingAroundAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="loggingAroundAdvice" />
</property>
<property name="pattern">
<value>.*</value> <!-- 对接口集合进行过滤 -->
</property>
</bean>
<bean id="loggingAroundAdvice"
class="example8.LoggingAroundAdvice">
</bean>
</beans>
以上各个通知,都是使用了Spring IoC 依赖注入类
若不利用Spring IoC,则
package example9;
import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
/*
* 环绕通知
* 借助于Spring AOP手工创建AOP代理,不依赖于IoC容器创建对象
* 但是不推荐这样,这样违背了Spring的初衷,依赖注入
* */
public class HelloClient {
protected static final Log log = LogFactory.getLog(HelloClient.class);
public static void main(String[] args) {
//创建LoggingAroundAdvice环绕通知
Advice advice = new LoggingAroundAdvice();
//创建ProxyFactory,从而不需要借助Spring IoC容器提供控制反转功能
ProxyFactory factory = new ProxyFactory(new HelloWorld());
factory.addAdvice(advice);
//调用业务操作
IHelloWorld hw = (IHelloWorld)factory.getProxy();
log.info("main方法输出:"+hw.getContent("wenbin"));
}
}
此时就不需要xml表了,但是不建议这样做,这样违背了Spring的初衷,依赖注入
三、Spring JTA(事务管理)
Spring 同时支持两种事务编程模型:声明式和编程式。
对于声明式服务而言,Spring支持各种事务管理器,而不像EJB,EJB仅仅支持JTA(Java Transaction API).
在Spring中,PlatformTransactionManager
对于Hibernate而言,需要借助org.springframework.orm.hibernate3.HibernateTransactionManager,开发者必须为其提供sessionFactory值,实际上HibernateTransactionManager会把事务委托给Transaction对象办理。在Spring提供的所有事务管理器中,都是对底层事务的封装。它自身并没实现底层事务的管理.
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
其中,HibernateTransactionManager就是实现了PlatformTransactionManager接口,而且,它还是通过Spring IoC容器加载的。