struts-2.3.1+spring-framework-3.2.2+hibernate-4.1.1整合历程<第三部分>(详解)

    在前两部分的编写中,Struts2和Hibernate已经成功整合,Struts2是MVC模式的实现,将业务逻辑和数据显示分离,Hibernate负责数据库与实体对象的映射,将关系数据模型转变成对象模型加以操作,符合面向对象的编程方式,但Spring究竟怎么整合进来,又负责怎样的功能,IOC(Inversion of Control),AOP(Aspect Oriented Programming)到底是什么概念,Spring带来了哪些好处,本文将都会涉及到,而且都会通过整个整合过程中的实例加以说明。

    和之前的操作步骤一样,首先要进行导入支持Spring的jar包,然后创建Spring的配置文件,然后进行配置文件的编写,然后可以通过实例检测是否成功。

    步骤一:导入支持Spring的Jar包,进入解压后文件路径spring-framework-3.2.2\libs下面,找到以下10个基本jar包拷贝到项目结构中lib目录下,具体的功能如下所示:

jar包(.jar)
作用
spring-aop-3.2.2
基于AOP的Spring特性如声明型事务管理
spring-aspects-3.2.2
Spring提供对AspectJ框架的整合
spring-beans-3.2.2
它包含访问配置文件,创建和管理文件,创建和管理bean以及进行(IoC/DI)操作相关的所有类
spring-context-3.2.2
Spring提供在基础IoC功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI定位、EJB集成、远程访问、缓存以及各种视图层框架的封装等
spring-core-3.2.2
Spring3.2.2的核心工具包
spring-expression-3.2.2
Spring表达式语言
spring-jdbc-3.2.2
对JDBC的简单封装
spring-orm-3.2.2
整合第三方的ORM框架,如hibernate,ibatis,jdo,以及spring的JPA实现
spring-tx-3.2.2
spring对事物的管理
spring-web-3.2.2
包含Web应用开发时,用到的Spring框架
    

    注:这里面没有将所有的jar包都导入,一个是因为本文中没有用到相应的jar包,二是在整合过程中会遇到jar包缺少的异常,或者为了解决某些问题,要手动导入某些jar包,这些都会在整合过程中遇到,有经验的读者可以一开始就将所有的jar包导入,这样可以避免不需要的异常,但为了发现一些整合时候可能遇到的异常,本文将具体问题具体分析。

    此时项目中的jar包如图1所示:


               图1

    步骤二:创建文件applicationContext.xml,首先如图2所示,


                           图2

然后如图3所示,


                            图3

点击finish,完成applicationContext.xml创建,此时完整的项目结构如图4所示:


             图4

    步骤三:编写配置文件,一般在开发中,自己从头到尾写配置的开发是不现实的,也是不需要的,都是copy改,当然本文也是采取这样的方式,但是不一样的是,这些配置文件我会通过spring说明文档,从中进行拷贝,然后spring说明文档在解压后的spring-framework-3.2.2\docs\spring-framework-reference\htmlsingle目录下,双击index.html打开说明文档,然后在网页中CTRL+F键弹出搜索框,然后输入<?xml version="1.0" encoding="UTF-8"?>,搜索到如图5所示的配置信息:


                                         图5

    然后将搜到的内容copy到项目中applicationContext.xml的配置文件中,并更改成以下内容,此时applicationContext.xml中的内容如下所示:

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:context="http://www.springframework.org/schema/context"
	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-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd">
	<context:annotation-config />
	
</beans>

有警告信息不要担心,可以将配置文件关闭后重新打开警告信息将会消失。此时可以写一个测试类完成Spring是否成功配置的测试,在test下com.steven.test新建一个SpringTest.java的类,

SpringTest.java代码如下所示:

package com.steven.test;

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

/**
 * 用来进行Spring的测试类
 * 
 * @author Steven
 * 
 */
public class SpringTest {
	@Test
	/**
	 * 用来测试是否Spring导入成功,配置成功
	 */
	public void testSpring() {
		ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
		System.out.println(appContext);
	}
}

    其中ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");依旧来自Spring文档,如图6所示:


                                             图6

此时进行JUnit测试,但运行中会出现一个异常,具体如下所示:

java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
	at org.springframework.context.support.AbstractApplicationContext.<init>(AbstractApplicationContext.java:164)
	at org.springframework.context.support.AbstractRefreshableApplicationContext.<init>(AbstractRefreshableApplicationContext.java:89)
	at org.springframework.context.support.AbstractRefreshableConfigApplicationContext.<init>(AbstractRefreshableConfigApplicationContext.java:58)
	at org.springframework.context.support.AbstractXmlApplicationContext.<init>(AbstractXmlApplicationContext.java:61)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:136)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
	at com.steven.test.SpringTest.testSpring(SpringTest.java:19)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
	at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
	at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
	... 31 more

这个异常是缺少org.apache.commons.logging.LogFactory这个类,所以要解决就要下载commons-logging-1.1.1.jar这个jar包(http://ishare.iask.sina.com.cn/download/explain.php?fileid=13433467),然后添加到lib路径下,此时再次运行,会出现以下结果,表示开始的导入和配置成功。

2013-12-12 22:54:00 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1d225a7: startup date [Thu Dec 12 22:54:00 CST 2013]; root of context hierarchy
2013-12-12 22:54:01 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
2013-12-12 22:54:02 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@a3d3b: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
org.springframework.context.support.ClassPathXmlApplicationContext@1d225a7: startup date [Thu Dec 12 22:54:00 CST 2013]; root of context hierarchy
经过这几个步骤,初始的Spring已经加载进来了,但具体的含义是什么,下面将进行介绍。

    注:首先applicationContext.xml的文件名不是必须的,也可以是其他的名称,比如常用的还有beans.xml,这个xml中头部都是一些xsd(XML Schemas Definition)文件,其作用是定义一份XML文档的合法组件群,定义了可以出现在文档里的元素;可以出现在文档里的属性;哪些元素是子元素;子元素的顺序;子元素的数量;一个元素应是否能包含文本,或应该是空的;元素和属性的数据类型;元素和属性的默认值和固定值。这时候,就可以在配置文件中进行配置了,在解压后的jar包spring-framework-3.2.2\schema路径下面,定义了很多xsd文件,一般在电脑可以上网的情况下,工具中如果没有,就会通过网络将schema文件下载并缓存在硬盘上,第二种可以通过手动添加schema文件,可以通过点击windwos->preferences->myeclipse->files and editors->xml->xmlcatalog点"add",在出现的窗口中的Key Type中选择URI,在location中选"File system",然后在spring解压目录的dist/resources目录中选择spring-beans-3.2.xsd,回到设置窗口的时候不要急着关闭窗口,应把窗口中的Key Type改为Schema location,Key改为http://www.springframework.org/schema/beans/spring-beans-3.2.xsd,如图7所示:


                           图7

    这样就可以解析导入的schema文件了。

    在测试的时候,得到ApplicationContext是使用ClassPathXmlApplicationContext这个类获得的,这是方法一,第二种方法可以通过ApplicationContext appContext = new FileSystemXmlApplicationContext(new String[] {"src/applicationContext.xml"});获取到,两种方式都可以进行测试,一个是通过class路径找到配置文件,一个是通过文件路径找到。

    

            然后通过下面的一个小例子看看Spring,IOC的魅力,

    首先在applicationContext.xml中加入如下的配置,然后此时配置如下所示:

    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:context="http://www.springframework.org/schema/context"
	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-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd">
	<context:annotation-config />
	<!-- 用来格式化日期属性 -->
	<bean id="dateFormat" class="java.text.SimpleDateFormat">
		<constructor-arg value="yyyy-MM-dd" />
	</bean>
	<!-- 通过手动配置 xml 实现 spring IOC -->
	<bean id="user" class="com.steven.entity.User">
		<property name="userId" value="1" />
		<property name="userName" value="Steven" />
		<property name="birthday">
			<bean factory-bean="dateFormat" factory-method="parse">
				<constructor-arg value="1990-01-01" />
			</bean>
		</property>
		<property name="age" value="23" />
		<property name="isVip" value="true" />
	</bean>
</beans>

此时测试类如下所示,

SpringTest.java

package com.steven.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.steven.entity.User;

/**
 * 用来进行Spring的测试类
 * 
 * @author Steven
 * 
 */
public class SpringTest {
	@Test
	/**
	 * 用来测试是否Spring导入成功,配置成功
	 */
	public void testSpring() {
		// 方式一
		// ApplicationContext appContext = new
		// ClassPathXmlApplicationContext("applicationContext.xml");
		// 方式二
		ApplicationContext appContext = new FileSystemXmlApplicationContext(
				new String[] { "src/applicationContext.xml" });
		System.out.println(appContext);
	}

	@Test
	/**
	 * 通过IOC方式进行获取Spring容器代理生成的User对象
	 */
	public void testGetUser() {
		ApplicationContext ac = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		User user = (User) ac.getBean("user");
		System.out.println(user);
	}
}

然后执行JUnit测试,运行结果如下所示:

2013-12-13 22:04:00 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1d225a7: startup date [Fri Dec 13 22:04:00 CST 2013]; root of context hierarchy
2013-12-13 22:04:00 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
2013-12-13 22:04:00 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@140984b: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,dateFormat,user,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
用户信息: [年龄是23, 生日是Mon Jan 01 00:00:00 CST 1990, 用户编号是1, 用户名是Steven, 是会员]

    此时配置的用户信息已经打印出来。这个时候我们来看看发生了什么事情了,通过ac.getBean("user")获得了配置中的User对象,其实是当Spring容器启动的时候就会对配置文件进行解析,在配置文件中有<bean>的节点,就会根据<bean>中的内容通过一系列Spring内部操作获得一个key为user的对象User,其实是通过java的反射机制创建User的对象,而且还是无参构造,如果将User中的无参构造去掉,或者写一个有参构造,这时候执行就会报以下的异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.steven.entity.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.steven.entity.User.<init>()
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1013)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:959)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:490)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:461)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:626)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
	at com.steven.test.SpringTest.testGetUser(SpringTest.java:36)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
	at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.steven.entity.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.steven.entity.User.<init>()
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1006)
	... 37 more
Caused by: java.lang.NoSuchMethodException: com.steven.entity.User.<init>()
	at java.lang.Class.getConstructor0(Class.java:2706)
	at java.lang.Class.getDeclaredConstructor(Class.java:1985)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
	... 38 more
其实将配置文件中的value值设置到对象属性中是通过调用setXx的方法,如果将set后面的改成和属性不一样的单词就会报错。这个可以自己手动试试。

    所以Ioc的本质就是将创建对象(new User())的主动权交给Spring容器了,这时候我想要User对象,只要问Spring要,然后Spring就会给我注射一个User对象,这就是Ioc(控制反转),或者叫DI(依赖注入),User依赖Spring容器注入给自己一个实例。这样做的好处是将对象的创建交给了Spring容器,就好似实现了热插拔的效果,具体的只要外部定义规定一样的,拿过来用就好了,这样就做到代码解耦和了。

    以上是通过手动配置bean文件,通过Spring上下文(ApplicationContext)获得容器产生的对象,下面通过配置将hibernate.cfg.xml替换掉,这段代码也可以从文档中查看,为了简单起见,这里不再给出查找过程,想必大家已经可以掌握了如何查找配置的方法。可以复制一下给出的配置即可。

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:context="http://www.springframework.org/schema/context"
	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-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd">
	<context:annotation-config />
	<!-- 用来格式化日期属性 -->
	<bean id="dateFormat" class="java.text.SimpleDateFormat">
		<constructor-arg value="yyyy-MM-dd" />
	</bean>
	<!-- 通过手动配置 xml 实现 spring IOC -->
	<bean id="user" class="com.steven.entity.User">
		<property name="userId" value="1" />
		<property name="userName" value="Steven" />
		<property name="birthday">
			<bean factory-bean="dateFormat" factory-method="parse">
				<constructor-arg value="1990-01-01" />
			</bean>
		</property>
		<property name="age" value="23" />
		<property name="isVip" value="true" />
	</bean>

	<!-- 通过spring配置取代hibernate.cfg.xml -->
	<bean
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<value>classpath:jdbc.properties</value>
		</property>
	</bean>
	<!-- 配置数据库连接所需要的基本信息 -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置sessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan">
			<list>
				<value>com.steven</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect
				</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
				<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
				</prop>
			</props>
		</property>
	</bean>
</beans>
此时将不再需要hibernate.cfg.xml的配置文件,但这时候添加了jdbc.properties的资源文件,项目结构图如图8所示:


           图8

jdbc.properties的内容如下:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://localhost\:3306/steven
jdbc.username=root
jdbc.password=mysqladmin
到底是否已经将hibernate.cfg.xml配置文件取代,此时可以通过测试查看,在SpringTest.java中添加如下的测试方法:

@Test
	/**
	 * 通过spring配置文件替代hibernate配置文件,测试是否成功
	 */
	public void testUser() {
		//获取spring容器上下文
		ApplicationContext ac = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		//获取配置文件中的session工厂(通过Spring的控制反转)
		SessionFactory sessionFactory = (SessionFactory) ac
				.getBean("sessionFactory");
		//获取session(连接)
		Session session = sessionFactory.openSession();
		//通过hibernate方式的获取
		User user = (User) session.get(User.class, 1);
		//打印获取信息F
		System.out.println(user);
	}
然后执行JUnit测试,此时的执行效果如下所示:

…………………………………………
…………………………………………
Hibernate: 
    select
        user0_.userId as userId0_0_,
        user0_.age as age0_0_,
        user0_.birthday as birthday0_0_,
        user0_.isVip as isVip0_0_,
        user0_.userName as userName0_0_ 
    from
        t_user user0_ 
    where
        user0_.userId=?
用户信息: [年龄是1, 生日是2013-12-10 20:39:16.0, 用户编号是1, 用户名是Steven, 是会员]
    所以此时可以断定,SessionFactory已经通过Spring容器获取,而Hibernate的配置文件已经被Spring配置文件替代,所以这个时候可以说是Hibernate已经和Spring整合。

    既然Hibernate已经整合进了Spring,那么就可以利用Spring的控制反转来替代Hibernate时候对于Dao类编写时候不必要的冗余代码。
    在实现Hibernate和Spring的完全整合的时候,可以通过注解的方式进行,此时需要在applicationContext.xml中<context:annotation-config />后添加一句<context:component-scan base-package="com.steven" />,既是注解方式注入对象的时候寻找Spring容器产生的对象是在包的范围下面寻找,此时配置文件添加为:

<context:annotation-config />
	<context:component-scan base-package="com.steven" />
    下面将实施将Dao中HibernateUtil使用Spring容器的注入产生SessionFactory对象,从而可以获得Session,继而可以通过Session下面的方法实现增删改查(crud)的功能。

    这个时候会引入一个新的层次service层,这个层次一则是考虑到dao仅仅是对一个实体类的操作,如果有事务的的操作就可以放到service层次解决,而且可以实现Spring的事务管理机制。

    此时新建两个包com.steven.service.iface,com.steven.service.impl,而且新增了BaseDao类,实现所有重复的增删改查的操作,然后具体的UserDao在实现IUserDao的时候要继承这个类,这样所有的重复方法就可以实现复用了。

    这个时候的项目框架就搭建成如图9所示:


           图9

此时变更的代码有新增的BaseDao.java,UserDao.java,新增的IUserService.java,UserService.java,具体的代码如下所示:

BaseDao.java

package com.steven.dao.iface;

import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 集成hibernate3和spring的时候,
 * spring的ORM包里提供了HibernateSupport和HibernateTemplate这两个辅助类,
 * 建议使用HibernateTemplate。 不过在Hibernate4里,spring不再提供这种辅助类,将使用hibernate4的原生API
 * http://blog.csdn.net/dlutxie/article/details/8771371
 * 
 * @author Steven
 * 
 * @param <T>
 */
public abstract class BaseDao<T> implements IBaseDao<T> {

	// 具体到哪个实体类的DAO功能
	private Class<T> entityClass;

	// 自动注入
	@Autowired
	private SessionFactory sessionFactory;

	/**
	 * 通过Spring注入的sessionFactory获取session
	 * 
	 * @return
	 */
	public Session getSession() {
		return this.sessionFactory.getCurrentSession();
	}

	/**
	 * 当前进行操作的类的实例
	 * 
	 * @param entityClass
	 */
	public BaseDao(Class<T> entityClass) {
		this.entityClass = entityClass;
	}

	/**
	 * 删除操作
	 */
	public void delete(T t) {
		this.getSession().delete(t);
	}

	/**
	 * 获取所有的对象集合
	 */
	@SuppressWarnings("unchecked")
	public List<T> getAll() {
		String hql = "from " + entityClass.getSimpleName();
		Query query = this.getSession().createQuery(hql);
		return query.list();
	}

	/**
	 * 根据Id获取对象的操作
	 */
	@SuppressWarnings("unchecked")
	public T getById(int id) {
		return (T) this.getSession().get(this.entityClass, id);
	}

	/**
	 * 插入对象操作
	 */
	public void insert(T t) {
		this.getSession().save(t);
	}

	/**
	 * 更新操作
	 */
	public void update(T t) {
		this.getSession().update(t);
	}
}

UserDao.java

package com.steven.dao.impl;

import org.springframework.stereotype.Repository;

import com.steven.dao.iface.BaseDao;
import com.steven.dao.iface.IUserDao;
import com.steven.entity.User;

/**
 * UserDao已经继承了BaseDao,其中的所有方法均已经被BaseDao实现
 * 
 * @author Steven
 * 
 */

//@Component("userDao")
@Repository
//一定要实现IUserDao这个接口
public class UserDao extends BaseDao<User> implements IUserDao {

	public UserDao() {
		super(User.class);
	}
}


IUserService.java

package com.steven.service.iface;

import java.util.List;

import com.steven.entity.User;

/**
 * 业务层
 * 
 * @author Steven
 * 
 */
public interface IUserService {
	/**
	 * 新增一个用户业务操作
	 * 
	 * @param user
	 */
	public void addUser(User user);

	/**
	 * 根据用户Id获取用户操作
	 * 
	 * @param userId
	 * @return
	 */
	public User getUserById(int userId);

	/**
	 * 获取所有的用户集合
	 * 
	 * @return
	 */
	public List<User> getAll();

}

UserService.java

package com.steven.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.steven.dao.iface.IUserDao;
import com.steven.entity.User;
import com.steven.service.iface.IUserService;

/**
 * 具体的业务层实现
 */
@Service
public class UserService implements IUserService {

	@Autowired
	private IUserDao userDao;

	public void addUser(User user) {
		userDao.insert(user);
	}

	public User getUserById(int userId) {
		return userDao.getById(userId);
	}

	public List<User> getAll() {
		return userDao.getAll();
	}

}

在上面的代码中用到了几个注解,这里将进行详细讲解

@Repository代表仓库. 一般注解在DAO实现类上, 只要看到这个注解, 就知道这个类是一个跟数据存储有关的类. 
@Service代表业务实现. 一般注解在Service实现类上.
如果你的类不是以上类型(数据存储类, 业务类), 可以笼统的使用@Component

@Autowire是注入对象的方式,自动注入,默认是按照类型的,此时就是需要<context:component-scan base-package="com.steven" />,自动在这个包下面进行搜索,搜索不到将会报错Could not autowire field,所以如果用到这个注解,在配置过程中要注意包的范围。


    这个时候可以查看是否配置成功,是否注解可以进行工作,是否BaseDao的编写已经趋于正常。依旧在SpringTest.java中编写JUnit测试方法,但此时在进行测试的时候就不是和原来一样的环境了,首先这个是Spring容器进行配置的,而且所有的bean都是自动注入的,因此需要一个Spring的环境,此时更改的地方或者添加了什么地方我将详细列出,

1,增加了spring-framework-3.2.2\libs\spring-test-3.2.2.jar这个jar包,为测试营造了一个Spring的环境,

2,将src\applicationContext.xml中添加一句配置,事务的配置,因为在Dao中获得Session的时候是通过sessionFactory.getCurrentSession()获取的,如果通过sessionFactory.openSession()获取就不需要配置事务,( 这个可以自己动手试一下,就将BaseDao中获取Session的方法改为openSession(),然后测试类中不要加事务的注解即可,本文考虑复杂一些的使用方式)因为getCurrentSession()要从当前事务中获取得到Session,

<!-- 配置事务 -->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

3,将添加过配置的app licationContext.xml文件复制到测试路径的com.steven.test包下,既test\com\steven\test\applicationContext.xml这个路径下,

4,在SpringTest.java中加了几个注解@RunWith(既SpringTest的环境中初始化Spring容器),@ContextConfiguration(找到test路径下的配置文件),@Transactional(打开事务,避免找不到currentSession)。

下面给出更改后的项目结构图,如图10所示:


              图10

此时的applicationContext.xml(两个路径下都要求配置同步)的内容如下:

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:context="http://www.springframework.org/schema/context"
	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-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.steven" />
	<!-- 用来格式化日期属性 -->
	<bean id="dateFormat" class="java.text.SimpleDateFormat">
		<constructor-arg value="yyyy-MM-dd" />
	</bean>
	<!-- 通过手动配置 xml 实现 spring IOC -->
	<bean id="user" class="com.steven.entity.User">
		<property name="userId" value="1" />
		<property name="userName" value="Steven" />
		<property name="birthday">
			<bean factory-bean="dateFormat" factory-method="parse">
				<constructor-arg value="1990-01-01" />
			</bean>
		</property>
		<property name="age" value="23" />
		<property name="isVip" value="true" />
	</bean>

	<!-- 通过spring配置取代hibernate.cfg.xml -->
	<bean
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<value>classpath:jdbc.properties</value>
		</property>
	</bean>
	<!-- 配置数据库连接所需要的基本信息 -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置sessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan">
			<list>
				<value>com.steven</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect
				</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
				<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
				</prop>
			</props>
		</property>
	</bean>
	<!-- 配置事务 -->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
</beans>
这个时候的SpringTest.java如下所示:

SpringTest.java

package com.steven.test;

import java.util.Date;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import com.steven.dao.iface.IUserDao;
import com.steven.entity.User;
import com.steven.service.iface.IUserService;

/**
 * 用来进行Spring的测试类
 * 
 * @author Steven
 * 
 */
// 需要额外添加spring-test包
// 这个包代表在spring容器中进行测试
@RunWith(SpringJUnit4ClassRunner.class)
// 这个包代表引入test/com/steven/test/applicationContext.xml文件
@ContextConfiguration(locations = "applicationContext.xml")
// 这个包代表开启事物,这样可以阻止No Session found for current thread的异常出现
@Transactional
public class SpringTest {
	@Autowired
	private IUserDao userDao;
	@Autowired
	private IUserService userService;

	@Test
	/**
	 * 用来测试是否Spring导入成功,配置成功
	 */
	public void testSpring() {
		// 方式一
		// ApplicationContext appContext = new
		// ClassPathXmlApplicationContext("applicationContext.xml");
		// 方式二
		ApplicationContext appContext = new FileSystemXmlApplicationContext(
				new String[] { "applicationContext.xml" });
		System.out.println(appContext);
	}

	@Test
	/**
	 * 通过IOC方式进行获取Spring容器代理生成的User对象
	 */
	public void testGetUser() {
		ApplicationContext ac = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		User user = (User) ac.getBean("user");
		System.out.println(user);
	}

	@Test
	/**
	 * 通过spring配置文件替代hibernate配置文件,测试是否成功
	 */
	public void testUser() {
		// 获取spring容器上下文
		ApplicationContext ac = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		// 获取配置文件中的session工厂(通过Spring的控制反转)
		SessionFactory sessionFactory = (SessionFactory) ac
				.getBean("sessionFactory");
		// 获取session(连接)
		Session session = sessionFactory.openSession();
		// 通过hibernate方式的获取
		User user = (User) session.get(User.class, 1);
		// 打印获取信息F
		System.out.println(user);
	}

	@Test
	/**
	 * 用来测试是否可以自动将UserDao对象注入,而且获取所有人的集合的方法是正确的
	 */
	public void testUserDao() {
		List<User> list = userDao.getAll();
		for (User user : list) {
			System.out.println(user);
		}
	}

	@Test
	/**
	 * 用来测试业务层的代码是否正确,UserService是否已经成功注入
	 */
	public void testUserService() {
		User user = new User("song", 1, new Date(), false);
		userService.addUser(user);
	}

}
然后进行测试,这里有个好处,在模拟的Spring容器中测试对数据库的操作都会进行回滚,其结果如下所示:

testUserDao()

………………
Hibernate: 
    select
        user0_.userId as userId0_,
        user0_.age as age0_,
        user0_.birthday as birthday0_,
        user0_.isVip as isVip0_,
        user0_.userName as userName0_ 
    from
        t_user user0_
用户信息: [年龄是1, 生日是2013-12-10 20:39:16.0, 用户编号是1, 用户名是Steven, 是会员]
用户信息: [年龄是1, 生日是2013-12-10 21:24:08.0, 用户编号是2, 用户名是Lily, 不是会员]
用户信息: [年龄是1, 生日是2013-12-10 21:24:23.0, 用户编号是3, 用户名是Tom, 不是会员]
…………

testUserService()

…………Hibernate: 
    insert 
    into
        t_user
        (age, birthday, isVip, userName) 
    values
        (?, ?, ?, ?)
2013-12-14 12:54:08 org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction
信息: Rolled back transaction after test execution for test context [TestContext@15d45d9 testClass = SpringTest, testInstance = com.steven.test.SpringTest@94aa42, testMethod = testUserService@SpringTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@35dc95 testClass = SpringTest, locations = '{classpath:/com/steven/test/applicationContext.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]

             注:在这个过程中思路是首先在没有更改任何代码和配置的时候,测试方法的运行会报java.lang.NullPointerException异常,然后原因是没有Spring容器的环境,加了Spring-test jar包,配置注解@RunWith后,如果没有将applicationContext.xml拷贝到com.steven.test目录下,会报class path resource [com/steven/test/applicationContext.xml] cannot be opened because it does not exist异常,然后拷贝后,增加注解@ContextConfiguration,如果没有配置事务,会报No bean named 'transactionManager' is defined异常,这个时候加入配置,增加注解@Transactional即可解决这些个异常。

    测试方法的执行过程:当运行JUnit测试方法userService.addUser(user)时候,我们可以看看这个逆过程,运行方法,必须对象要实例化,然后通过SpringTest类中IUserService userService进行@Autowired的配置,自动按照类型注入一个UserService(因为此类使用@Service注解)对象,然后再看UserService.java中IUserDao userDao进行@Autowired的配置,那就自动按照类型注入一个实现类UserDao(因为此类使用@Repository注解)的对象,而UserDao中的方法都被BaseDao实现,而且BaseDao总sessionFactory又是通过Autowired的,然后Spring的配置文件中又有sessionFactory的Id,所以可以自动获取到,这就是这个逆过程,其实是Spring容器启动的时候,这些资源都会被自动注入,然后在使用的时候就可以不需要new了,全部都有Spring容器代理,IOC的精髓也就在这里了。

    有可能在自己搭建的过程中还会遇到其他的异常,那个时候就要好好进行分析,可以google一下,或者百度一下,将问题的实质搞清楚,理解了这些原理后,应该很容易就会找到解决方案,如果按照我的步骤下来,排除自身放错位置,写错文件的问题外,是不会出现异常的,而且可能的异常我都写出来了。所以在这个过程中,大家要留心一些。

    如果有什么我可能没有发现的异常,大家也可以提出来,这样的话,我可以进行修改或者增强文章的可读性,如果我有什么错误的地方,或者由于自己的理解有些偏差,文字的叙述没有清楚,也请大家指出,整合Spring过程真心不简单,我考虑东西一多,就失去了文章的简洁性,引入的东西越多,越会造成大家知识的疯狂补充,会造成阅读理解的困难,所以在文章中我尽量对于一些知识点提一下,然后具体的实现可以参照其他人的博文,或者日后我会给出相应的补充博文介绍,这样对于懂原理的人来说不会因为一些与搭建框架无关的东西而浪费时间,也不会让初学者感到很困惑,东西那么多,什么时候是个头才能学完。

    Spring的事务管理机制,这里仅仅提供配置,具体的介绍可以参照其他博文,因为Spring事务管理也是很重要的东西,下面需要更改配置applicationContext.xml进行简单的事务管理,从而使在对数据库操作的过程中,如果遇到问题,就会回滚之前的操作,不会出现误操作的过程。这个过程中将用到Spring另外一个比较重要的特性,AOP(面向切面编程),这个本质是Java的动态代理机制,具体的动态代理介绍,请参照我的动态代理机制讲解的博文或者其他人动态代理机制讲解的博文。此时applicationContext.xml的内容如下所示:
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:context="http://www.springframework.org/schema/context"
	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-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.steven" />
	<!-- 用来格式化日期属性 -->
	<bean id="dateFormat" class="java.text.SimpleDateFormat">
		<constructor-arg value="yyyy-MM-dd" />
	</bean>
	<!-- 通过手动配置 xml 实现 spring IOC -->
	<bean id="user" class="com.steven.entity.User">
		<property name="userId" value="1" />
		<property name="userName" value="Steven" />
		<property name="birthday">
			<bean factory-bean="dateFormat" factory-method="parse">
				<constructor-arg value="1990-01-01" />
			</bean>
		</property>
		<property name="age" value="23" />
		<property name="isVip" value="true" />
	</bean>

	<!-- 通过spring配置取代hibernate.cfg.xml -->
	<bean
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<value>classpath:jdbc.properties</value>
		</property>
	</bean>
	<!-- 配置数据库连接所需要的基本信息 -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置sessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan">
			<list>
				<value>com.steven</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect
				</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
				<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
				</prop>
			</props>
		</property>
	</bean>

	<!-- 配置事物 -->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<!--
		PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
		PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
		PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
		PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
		PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
		PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
		PROPAGATION_NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
	-->

	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true" propagation="REQUIRED" />
			<!--
				之前是NOT_SUPPORT
			-->
			<tx:method name="find*" read-only="true" propagation="REQUIRED" />
			<!--
				之前是NOT_SUPPORT
			-->
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="remove*" propagation="REQUIRED" />
			<tx:method name="add*" propagation="REQUIRED" />
			<!--默认其他方法都是REQUIRED-->
			<tx:method name="*" propagation="REQUIRED" />
		</tx:attributes>
	</tx:advice>

	<!-- 事务的AOP配置 -->
	<aop:config>
		<!-- 配置一个切入点,匹配UserService的所有方法的执行 -->
		<!-- 方法一定要写切点类最完整的包路径,否则会出现No Session found for current thread异常 -->
		<aop:pointcut expression="execution(public * com.steven.service.impl.*.*(..))"
			id="userPointCut" />
		<!-- 指定在userpointCut切入点应用txAdvice事务增强处理 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="userPointCut" />
	</aop:config>

</beans>

如果此时就运行testUserDao()测试方法,会有以下异常抛出

java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:122)
	at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:105)
	at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:74)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:312)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:284)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
	at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [com/steven/test/applicationContext.xml]; nested exception is java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:412)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
	at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:243)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadBeanDefinitions(AbstractGenericContextLoader.java:233)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:117)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:100)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:248)
	at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContextInternal(CacheAwareContextLoaderDelegate.java:64)
	at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:91)
	... 25 more
Caused by: java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
	at java.net.URLClassLoader.access$000(URLClassLoader.java:56)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
	at org.springframework.transaction.config.TxAdviceBeanDefinitionParser.getBeanClass(TxAdviceBeanDefinitionParser.java:72)
	at org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal(AbstractSingleBeanDefinitionParser.java:66)
	at org.springframework.beans.factory.xml.AbstractBeanDefinitionParser.parse(AbstractBeanDefinitionParser.java:59)
	at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1438)
	at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1428)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:185)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:139)
	at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:108)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
	... 38 more
Caused by: java.lang.ClassNotFoundException: org.aopalliance.intercept.MethodInterceptor
	at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
	... 61 more
同样,这是缺少附加的jar包造成的,这个时候就需要添加aopalliance-1.0.jar(http://mirrors.ibiblio.org/pub/mirrors/maven/aopalliance/jars/aopalliance-1.0.jar),aspectjweaver-1.7.0.jar(http://ishare.iask.sina.com.cn/download/explain.php?fileid=13433573)两个jar包,主要在Spring AOP时候所要用到的包。然后在执行测试方法,就不会抛出异常了。

    下面可以通过一个实例来讲解AOP的具体效果,因为我们有了Service层,为什么要有此层,项目中大家经常会遇到一个场景,就是在进行处理正常的业务逻辑的时候,需要添加一些固定的项目日志,大家可以认为,最多就在每个处理类中写日志不就行了吗,但如果项目很大,而且日志都是冗余的,也就是说日志内容都是规定的,那么在这个过程中除了白费力气,而且浪费很多的时间写日志,而AOP的思想就是,通过动态代理机制,日志只要写完一遍当做模板,以后所有的处理类中,都可以进行复用,而且都可以在处理操作的过程前,后,过程中添加日志,这样就好似你去酒店住宿,在之前之后都会有人帮你清扫房间,而这些都是与你有关,而不是你所控制的,都是酒店分配好了。这里也是,在处理类执行前先执行日志,然后处理完的时候在执行日志,代码还是原来的代码,但执行的效果却增加了日志处理的操作,就是在你正常的业务操作中切入了日志的操作,这就是AOP(面向切面编程)的简单理解。其本质就是动态代理机制。

    AOP面向切面可以通过两种方式实现,同样是配置和注解,我给出两种方式,都可以进行,下面给出手动配置的时候需要在applicationContext中添加的配置内容

<!-- 声明日志切面类 -->
	<bean id="logAspectBean" class="com.steven.aop.LogAspect"></bean>

	<!-- 日志切面的AOP实现 -->
	<aop:config>
		<!-- 配置一个切入点,匹配UserService的所有方法的执行 -->
		<aop:aspect id="logAspect" ref="logAspectBean">
			<!-- 方法一定要写切点类最完整的包路径,否则会出现No Session found for current thread异常 -->
			<aop:pointcut expression="execution(public * com.steven.service.impl.*.*(..))"
				id="allMethod" />
			<!-- 应用前置通知 -->
			<aop:before method="before" pointcut-ref="allMethod" />
			<!-- 应用后置通知 -->
			<aop:after-returning method="afterReturn"
				pointcut-ref="allMethod" />
			<!-- 应用最终通知 -->
			<aop:after method="after" pointcut-ref="allMethod" />
			<!-- 应用抛出异常后通知 -->
			<aop:after-throwing method="afterThrowing"
				pointcut-ref="allMethod" />
		</aop:aspect>
	</aop:config>
或者可以进行如下配置:

<!-- 声明日志切面类 -->
	<bean id="logAspectBean" class="com.steven.aop.LogAspect"></bean>

	<aop:config>
		<!-- 配置一个切面 -->
		<aop:aspect id="logAspect" ref="logAspectBean">
			<!-- 定义切入点,指定切入点表达式 -->
			<aop:pointcut id="allMethod"
				expression="execution(public * com.steven.service.impl.*.*(..))" />
			<!-- 应用环绕通知 -->
			<aop:around method="doAround" pointcut-ref="allMethod" />
		</aop:aspect>
	</aop:config>
此时的切面类LogAspect.java如下所示:

LogAspect.java

package com.steven.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 日志切面类
 * 
 * @author Steven
 * 
 */
public class LogAspect {
	// 定义切入点,提供一个方法,这个方法的名字就是改切入点的id
	@SuppressWarnings("unused")
	private void allMethod() {
	}

	// 任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
	public void before(JoinPoint call) {
		String clazz = call.getTarget().getClass().getName();
		// 获取目标对象上正在执行的方法名
		String methodName = call.getSignature().getName();
		System.out.println("前置通知:" + clazz + "类的" + methodName + "方法开始了...");
	}

	// 访问命名切入点来应用后置通知
	public void afterReturn() {
		System.out.println("后置通知:方法正常结束...");
	}

	// 应用最终通知
	public void after() {
		System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的...");
	}

	// 应用异常抛出后通知
	public void afterThrowing() {
		System.out.println("异常抛出后通知:方法执行时出现异常...");
	}

	// 应用周围通知
	// @Around("allMethod()")
	// 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
	public Object doAround(ProceedingJoinPoint call) throws Throwable {
		Object result = null;
		this.before(call);// 相当于前置通知
		try {
			result = call.proceed();
			this.afterReturn(); // 相当于后置通知
		} catch (Throwable e) {
			this.afterThrowing(); // 相当于异常抛出后通知
			throw e;
		} finally {
			this.after(); // 相当于最终通知
		}
		return result;
	}
}
如果全部用注解的方式,则只要添加如下配置:

<bean id="logAspectAnnotationBean" class="com.steven.aop.LogAspectAnnotation"></bean>
	<!-- 启用spring对AspectJ注解的支持 -->   
	<aop:aspectj-autoproxy />

这个时候对应的LogAspectAnnotation.java代码如下:

LogAspectAnnotation.java

package com.steven.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/**
 * 日志切面类
 * 
 * @author Steven
 * 
 */
@Aspect
public class LogAspectAnnotation {
	// 定义切入点,提供一个方法,这个方法的名字就是改切入点的id
	@SuppressWarnings("unused")
	@Pointcut("execution(* com.steven.service.impl.*.*(..))")
	private void allMethod() {
	}

	// 任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
	@Before("execution(* com.steven.service.impl.*.*(..))")
	public void before(JoinPoint call) {
		String clazz = call.getTarget().getClass().getName();
		// 获取目标对象上正在执行的方法名
		String methodName = call.getSignature().getName();
		System.out.println("前置通知:" + clazz + "类的" + methodName + "方法开始了...");
	}

	// 访问命名切入点来应用后置通知
	@AfterReturning("allMethod()")
	public void afterReturn() {
		System.out.println("后置通知:方法正常结束...");
	}

	// 应用最终通知
	@After("allMethod()")
	public void after() {
		System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的...");
	}

	// 应用异常抛出后通知
	@AfterThrowing("allMethod()")
	public void afterThrowing() {
		System.out.println("异常抛出后通知:方法执行时出现异常...");
	}

	// 应用周围通知
	// @Around("allMethod()")
	// 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
	public Object doAround(ProceedingJoinPoint call) throws Throwable {
		Object result = null;
		this.before(call);// 相当于前置通知
		try {
			result = call.proceed();
			this.afterReturn(); // 相当于后置通知
		} catch (Throwable e) {
			this.afterThrowing(); // 相当于异常抛出后通知
			throw e;
		} finally {
			this.after(); // 相当于最终通知
		}
		return result;
	}
}

此时的项目结构图如图11所示:


          图11

这个时候在执行testUserService()方法的时候,控制台出现以下结果:

……………………
前置通知:com.steven.service.impl.UserService类的addUser方法开始了...
Hibernate: 
    insert 
    into
        t_user
        (age, birthday, isVip, userName) 
    values
        (?, ?, ?, ?)
最终通知:不管方法有没有正常执行完成,一定会返回的...
后置通知:方法正常结束...
…………………………
说明面向切面的日志切面已经成功起作用。

这个时候就完成了IOC和AOP的完全降解,下面将对Struts,Hibernate,Spring进行全方位的整合。

下面对UserAction.java通过注入Ioc的方式注入UserService对象,

UserAction.java

package com.steven.action;

import java.util.List;
import java.util.Map;

import org.apache.struts2.interceptor.RequestAware;
import org.springframework.beans.factory.annotation.Autowired;

import com.opensymphony.xwork2.ActionSupport;
import com.steven.entity.User;
import com.steven.service.iface.IUserService;

/**
 * 通过继承ActionSupport获得Struts2的已有功能,
 * 通过实现RequestAware获取request(这种方式常用,还有其他三种方式,这里不再赘述)
 * 四种获取request方式请参照http://blog.csdn.net/ms2146/article/details/5406004
 * 
 * @author Steven
 * 
 */
public class UserAction extends ActionSupport implements RequestAware {
	private static final long serialVersionUID = 1L;
	private Map<String, Object> request;
	// private UserDao userDao = new UserDao();
	@Autowired
	private IUserService userService;

	@Override
	public String execute() throws Exception {
		// userDao.insert(new User("Lily", 1, new Date(), false));
		// userDao.insert(new User("Tom", 1, new Date(), false));
		// 创建一个用户集合
		List<User> userList = userService.getAll();
		// 创建用户并添加到用户集合中
		// userList.add(new User("Steven", 1, new Date(), true));
		// userList.add(new User("Lily", 1, new Date(), false));
		// userList.add(new User("Tom", 1, new Date(), false));

		// 将用户存储到ActionContext中(可以通过ognl表达式获取)
		request.put("userList", userList);
		return SUCCESS;
	}

	public void setRequest(Map<String, Object> request) {
		this.request = request;
	}

}
这个时候就可以启动服务器,然后看看加上Struts2的效果了,但此时会报如下异常,如图12所示:


                                              图12

    这个异常是在action层出现的,是说userService为空,但之前JUnit中不是没有任何异常吗,但不要忘记了前提是将测试的环境注解成了Spring容器的环境,而此时整合了Struts2,但Spring仅仅是代码中写到,而具体在服务器启动的时候,Struts加载时候,却没有加载Spring,所以这个时候Spring就没有了环境,所有的bean都为空值,所以就报了这个空指针异常。

    解决方法如下:首先将struts-2.3.1\lib\struts2-spring-plugin-2.3.1.jar路径下的jar包拷到lib路径下,然后将web.xml文件更改如下:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

	<!-- Struts2过滤器,将所有的请求进行拦截并进行处理 -->
	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<!-- 用来将spring在服务器启动的时候交给struts2 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/classes/applicationContext.xml</param-value>
	</context-param>

	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>

</web-app>

这个时候进行重新启动服务器,打开首页后,点击这里,就可以出现以下界面了,如图13所示:


                                             图13

此时后台的运行结果为:

前置通知:com.steven.service.impl.UserService类的getAll方法开始了...
Hibernate: 
    select
        user0_.userId as userId0_,
        user0_.age as age0_,
        user0_.birthday as birthday0_,
        user0_.isVip as isVip0_,
        user0_.userName as userName0_ 
    from
        t_user user0_
最终通知:不管方法有没有正常执行完成,一定会返回的...
后置通知:方法正常结束...

到此,SSH已经全部整合完成。

    再来回顾一下整个整合过程,Struts2用来对整个请求和相应的处理,Hibernate对数据库的关系和对象的映射,Spring对全局bean的管理和事务机制,对model进行控制。在这个过程中遇到异常是很正常的现象,但如果能够知道异常产生的原因和解决的方法,那么就可以在使用中随心所欲的使用这SSH带来的好处,但其弊端也很明显,配置太麻烦,效率都要自己进行把关。在开发中SSH的使用很普遍,但仅仅使用Spring或者Hibernate也是存在的,Struts2依赖web容器,而Spring,Hibernate没有这个问题,可以单独使用,所以项目中具体如何搭配,都要看具体的问题具体的分析。

    在此恭祝大家学习愉快!




转载于:https://my.oschina.net/abcijkxyz/blog/721902

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值