我的Spring技术笔记
2012年11月18日

Spring很强大,代码写得非常好,像是一件艺术品。我个人对Spring框架也是非常欣赏,拜读过它很多功能的源代码,比如BEAN(IOC)、MVC、JDBC、AOP等。


一、我的Spring总结


    Spring可以用来管理前端的业务控制器,中间的业务逻辑组件,以及底层的DAO组件,方便集成各组件,而且可以大大的降低各组件之间的耦合度。

    Spring有如下几个流行的应用:

1. IOC ( Inversion of Control,控制反转),又称为DI(依赖注入),值的依赖注入和自动装配;

2. AOP(面向切面编程),例如前置通知、后置通知;

3. 持久层的封装和事务管理,包括对JDBC的使用加以封装以及对各种O/R映射工具的整合;


首先要理解这几个术语,什么是IoC、DI?

       IoC又称为DI,因为实质是一样的。比如在A类的某个方法中新建了一个B类的实例,并调用B类的方法,则称A类依赖于B类,当B类中的方法修改时,A类中的调用代码也需要修改。如果要让两者之间低耦合度,可以将在A类中生成B类的实例的工作由第三方来完成,比如Spring的Bean容器。Spring通过配置文件,生成B类的实例,然后再注入到A类中,这样A类就可以使用了,因此称为依赖注入,然而本来这种信赖关系是A类来建立的,现在却由Spring的bean来管理了,因此又称为控制反转。

       例如有Person接口和Student类,使用Student类时我们可以每次都new一个Student,或者我们把new Student()放到工厂类中,调用工厂类的方法Factory.getStudent()如果修改了Person接口中的方法,那就得修改Student类的方法,、工厂类。每次调用Student类时都要继承Person接口。



1、web.xml配置

<servlet-mapping>

 <servlet-name>mvcServlet</servlet-name>

 <url-pattern>/</url-pattern>

</servlet-mapping>

<servlet-mapping>

 <servlet-name>mvcServlet</servlet-name>

 <url-pattern>/*</url-pattern>

</servlet-mapping>

的区别?


前者不会拦截如下Servlet容器发起的请求:

RequestDispatcher rd = request.getRequestDispatcher(path);

rd.include(requestToExpose, response);

其中 在Tomcat容器下 rd 实例为: org.apache.catalina.core.ApplicationDispatcher@1e18e8c


具体原理我也不太清楚。我猜测 WEB容器 对 /* 进行了特殊的定义:

    当 配置了 /* 时,任何请求,即便是服务器自己的请求(比如Tomcat的org.apache.catalina.core.ApplicationDispatcher), 也要被改Servlet拦截。【这个拦截是服务器内部的程序调用,并非是从浏览器端再次提交请求


但是,如果只是 配置 / 时,则容器自身的请求不会被Servlet拦截



二、为Servlet或普通Java程序注入Spring托管的Bean、数据源


S2SH中都是层层注入,action交给Spring托管。即,往Struts的Action中注入Service,往Service中又注入DAO,这个都是通过配置完成的。

经过对Spring原理和源码的研究,发现,可以写一个SpringBeanFactory.java,自己实现获取bean实例的功能。下面分两种情况进行说明。

 

情况1:在web.xml中已经配置Spring的applicationContext文件

一般我们是这么配置Spring的:

<!-- spring上下文 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/service.xml</param-value>
</context-param>
<!-- 启动监听 -->
<listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


此时,可以通过

org.springframework.web.context.ContextLoaderListener来获取bean,

或者org.springframework.web.context.ContextLoader也可以。

关键代码如下:

public SpringBeanFactory{
	private static ApplicationContext ctx;
	/** 通过ContextLoaderListener取得ctx */
	public static void initApplicationContext(){
		ctx=ContextLoaderListener.getCurrentWebApplicationContext();
	}
	/** 通过泛型方法取得bean实例 */
	public static <T> T getBean(String name){
		if(ctx==null){
			initApplicationContext();
		}
		return (T) ctx.getBean(name);
	}
}


这样,无论在Servlet,还是在普通Java类中,都可以调用getBean()方法获取Spring托管的Bean对象了。

 

情况2:在web.xml中未配置Spring

      此时,我们可以自己写程序去加载xml文件,生成ctx,关键代码如下:

public class SpringBeanFactory {
	private static ApplicationContext ctx = null;	
	public static void initApplicationContext(){
		if (ctx == null) {
			ctx = new ClassPathXmlApplicationContext(
					new String[] {
							"classpath:applicationContext-Comm.xml",
							"classpath:applicationContext-B2BPL.xml"
							});
		}
	}
}


其本质和第一种情况是一样的,如果自己再建一个StartListener.java,如下:其本质和第一种情况是一样的,如果自己再建一个StartListener.java,如下:

public class StartListener implements ServletContextListener {
	@Override
	public void contextInitialized(ServletContextEvent event) {
		event.getServletContext().log("Start initing ApplicationContext...");
		SpringBeanFactory.initApplicationContext();
	}
}


并把StartListener.java配置在web.xml中,这样就可以在服务器启动的时候得到ctx了。并把StartListener.java配置在web.xml中,这样就可以在服务器启动的时候得到ctx了。



事务管理是什么?Spring事务管理的原理又是什么?


事务处理必须满足ACID原则,即原子性(A)、一致性(C)、隔离性(I)和持久性(D)。

      a) 原子性:指事务必须执行一个完整的工作,要么执行全部数据的操作,要么全部不执行。

      b) 一致性:一致性是指当事务完成时,必须所有的数据具有一致的状态。

在数据库软件中,比如Oracle,事务分为两类:系统提供的事务和用户自定义的事务。

 1. 系统提供的事务是指在执行某些语句时,一条语句就是一个事务。比如CREATE、DELETE、DROP、SELECT、UPDATE、INSERT(但是要明确,一条语句的对象既可能是表中的一行数据,也可能是表中的多行数据,甚至是表中的全部数据。因此,只有一条语句构成的事务也可能包含了多行数据的处理。)

  2. 用户自定义的事务,一般是使用BEGIN TRANSACTION语句来定义的事务。

     在使用用户定义的事务时,一定要注意事务必须有明确的结束语句来结束。

     事务的明确结束可以使用两个语句中的一个:COMMIT语句和ROLLBACK语句。


Spring的事务管理,默认是只对unchecked(RuntimeException)异常及其子类进行异常回滚。对checked Exception是不会进行回滚的。当然,如果你自己catch了异常,事务管理就不起作用了。


所以如果要进行事务管理,必须throw 继承了RuntimeException的异常了。

JdbcTemplete抛出的异常一般都是将SQLException转换成

DataAccessException(继承了org.springframework.core.NestedRuntimeException抽象类)

另外,我们可以修改Spring的默认配置,当发生RuntimeException我们也可以不让他进行事务回滚

只需要加上一个@Transactional(noRollbackFor=RuntimeException.class)

既然可以配置不对RuntimeException回滚,那我们也可以配置对Exception进行回滚,主要用到的是

@Transactional(rollbackFor=Exception.class)

对于一些查询工作,因为不需要配置事务支持,我们配置事务的传播属性:

@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

readOnly=true表示事务中不允许存在更新操作.


关于事务的传播属性有下面几种配置(仅列举2个):

REQUIRED:业务方法需要在一个事务中运行,如果方法运行时,已经处于一个事务中,那么加入到该事务中,否则自己创建一个新的事务。(Spring默认的事务传播属性)

NOT_SUPPORTED:声明方法不需要事务,如果方法没有关联到一个事务,容器不会为它开启事务,如果方法在一个事务中被调用,该事务被挂起,在方法调用结束后,原先的事务便会恢复执行。


注意Spring DI的Bean是单实例的,也就是说 Spring getBean 每次都得的对象都不是新的!

例如:

BkDailySaleRpt bsr = (BkDailySaleRpt) ServiceBeanFactory.getBean("b2bplBkDailySaleRpt");

bsr.name = "foo";

BkDailySaleRpt bds = (BkDailySaleRpt) ServiceBeanFactory.getBean("b2bplBkDailySaleRpt");

System.out.println(bds.name);

bsr 和 bds 是同一个,说明,只要在一个地方改变了这个bean,在其他地方也会得到改变!bean是全局共享的。

基于这个原理,其实一个tools工具类,也可以做成bean的形式注入到其他类中供使用。


六、Spring + Struts 2 + Hibernate整合开发

1、搭建S2SH环境

首先

1. 把Struts 2的5个核心jar文件加入到WEB-INF/lib目录下,分别是

    commons-logging-版本号.jar

    freemarker-版本号.jar

    ognl-版本号.jar

    struts2-core-版本号.jar

    xwork-版本号.jar(有可能是xwork-core-版本号.jar)

另外,再加入Struts 2的Spring插件,如下

    struts2-spring-plugin-版本号.jar

2. 把Spring的核心jar文件加入到WEB-INF /lib目录下,只有一个

    spring.jar

3. 把Hibernate的核心jar文件加入到WEB-INF /lib目录下,只有一个

    hibernate3.jar(3是版本号)

4.数据库驱动jar文件,如果是用MySQL数据库,则为

    mysql-connector-java-版本号-bin.jar

5. 其他,根据需要,加入其他jar文件

 

其次

    配置WEB-INF /web.xml文件,使Struts 2和Spring生效。

配置文件,示例如下

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0"

    xmlns="http://java.sun.com/xml/ns/javaee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <!--核心Filter FilterDispatcher -->

    <filter>

        <!-- 核心Filter的名称 -->

        <filter-name>struts2</filter-name>

        <!--核心Filter实现类 -->

        <filter-class>

            org.apache.struts2.dispatcher.FilterDispatcher

        </filter-class>

    </filter>

 

    <filter-mapping>

        <!--核心Filter的名称 -->

        <filter-name>struts2</filter-name>

        <!--使用核心Filter来接受所有的Web -->

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <!-- 初始化Spring容器 -->

    <listener>

        <listener-class>

            org.springframework.web.context.ContextLoaderListener

        </listener-class>

    </listener>

</web-app>

 

跳转到action的方法:

    <result type="redirectAction">

        <param name="actionName">showpicsya</param>

    </result>