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>