作者:niumdQ{载请注明出处Q谢?nbsp;
发表旉Q?010 q?nbsp;03 ?nbsp;17 ?nbsp;
原文链接Q?a mce_href="/admin/blogs/618449">http://ari.iteye.com/admin/blogs/618449
一、Spring JDBC 概述
Spring 提供了一个强有力的模板类JdbcTemplate化JDBC操作QDataSource,JdbcTemplate都可以以Bean的方式定义在想xml配置文gQJdbcTemplate创徏只需注入一个DataSourceQ应用程序Dao层只需要承JdbcDaoSupport, 或者注入JdbcTemplateQ便可以获取JdbcTemplateQJdbcTemplate是一个线E安全的c,多个Dao可以注入一个JdbcTemplateQ?/p>
<!-- Oracle数据? --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@oracle.devcake.co.uk:1521:INTL"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- set注入方式获取jdbcTemplate --> <bean id="customerDao" class="JdbcCustomerDao" > <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <!-- 注入dataSourceQcustomerDao通过l承JdbcDaoSupport ,使用this.getJdbcTemplate()获取JdbcTemplate --> <bean id="customerDao" class="JdbcCustomerDao" > <property name="dataSource" ref="dataSource"/> </bean>
然后jdbcTemplate对象注入自定义的Dao、或者承JdbcDaoSupportQ例如:
public class JdbcCustomerDao extends JdbcDaoSupport implements CustomerDao { } public class JdbcCustomerDao implements CustomerDao { private JdbcTemplate jdbcTemplate public void setJdbcTemplate()JdbcTemplate jdbcTemplate{ this.jdbcTemplate=jdbcTemplate } }
二?nbsp;JdbcTemplate 提供以下主要Ҏ化JDBC操作Q?/strong>
2.1、List query(String sql,Ojbect[] args,RowMapper rowMapper)
说明Q常用的查询Qsql待执行的sql语句Qargs是sql语句的参敎ͼrowMapper负责每一行记录{化ؓjava对象存放在listQƈ最l返回,例如Q?/p>
public List<Book> queryByAuthor(String author) { String sql = "select * from book where author=?"; Collection c = getJdoTemplate().find(sql, new Object[] { author },new BookRowMapper()); List<Book> books = new ArrayList<Book>(); books.addAll(c); return books; } class BookRowMapper implements RowMapper{ public Object mapRow(ResultSet res, int index) throws SQLException { Book book = new Book(); book.setId(rs.getInt("id")); //省略set return bookQ? } }
更新、删除、其他查询操作类|举例如下Q详l细节请参考spring apiQ?/p>
//q回gؓ一个长整Ş public long getAverageAge() { return getJdbcTemplate().queryForLong("SELECT AVG(age) FROM employee"); } //q回一个整? public int getTotalNumberOfEmployees() { return getJdbcTemplate().queryForInt("SELECT COUNT(0) FROM employees"); } //更新操作 this.jdbcTemplate.update( "insert into t_actor (first_name, surname) values (?, ?)", new Object[] {"Leonor", "Watling"});
2.2、spring 2.5新功能,另类的jdbc ORMQBeanPropertyRowMapper
上面我们索时必须实现RowMapperQ将l果集{化ؓjava对象。Spring2.5 化了q一操作Q得我们不必再实现RowMapperQ实现此功能的俩个神奇东东便是:ParameterizedRowMapperQParameterizedBeanPropertyRowMapperQ貌似通过java反射机制实现了将resultset字段映射到java对象Q但是数据表的列必须和java对象的属性对应,没有研究源码Q有点类gapache 的BeanUtilQ不知ؓ何这部分在spring开发参考手册没有,N不是l典?/p>
//使用ParameterizedBeanPropertyRowMapper @SuppressWarnings({"unchecked"}) public List<Customer> getAll() { return getJdbcTemplate().query("select * from t_customer", ParameterizedBeanPropertyRowMapper.newInstance(Customer.class)); } //使用BeanPropertyRowMapper @SuppressWarnings({"unchecked"}) public List<Customer> getAll() { return getJdbcTemplate().query("select * from t_customer", new BeanPropertyRowMapper(Customer.class)); }
注意QParameterizedBeanPropertyRowMapper是BeanPropertyRowMapper子类。另外表的字D名U必d实体cȝ成员变量名称一_
2.3、spring之JDBC扚w操作
jdbcTemplate.batchUpdate(final String[] sql) QAPI解释QIssue multiple SQL updates on a single JDBC Statement using batchingQ翻译过来大致ؓQ解军_个sql的插入、更新、删除操作在一个Statement中。性能一般?/span>
jdbcTemplate.batchUpdate(String sql, final BatchPreparedStatementSetter pss),cM于JDBC的PreparedStatementQ性能较上着有所提高?/span>
我们举例说明如何使用Q示例如?
final int count = 2000; final List<String> firstNames = new ArrayList<String>(count); final List<String> lastNames = new ArrayList<String>(count); for (int i = 0; i < count; i++) { firstNames.add("First Name " + i); lastNames.add("Last Name " + i); } jdbcTemplate.batchUpdate( "insert into customer (id, first_name, last_name, last_login, comments) values (?, ?, ?, ?, ?)", new BatchPreparedStatementSetter() { //为prepared statement讄参数。这个方法将在整个过E中被调用的ơ数 public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setLong(1, i + 10); ps.setString(2, firstNames.get(i)); ps.setString(3, lastNames.get(i)); ps.setNull(4, Types.TIMESTAMP); ps.setNull(5, Types.CLOB); } //q回更新的结果集条数 public int getBatchSize() { return count; } }); }
BatchSqlUpdatecLSqlUpdate 的子c,适用于插入、删除、更新批量操作,内部使用PreparedStatementQ所以效率很高,扚w语句辑ֈ讑֮的batchSizeQ或者手动调用flush才会执行扚w操作。注意:此类是非U程安全的,必须为每个用者创Z个实例,或者在同一个线E中使用前调用reset?/span>
下面我们举例说明如何使用BatchSqlUpdateQ来执行扚w操作。示例如下:
class BatchInsert extends BatchSqlUpdate { private static final String SQL = "insert into t_customer (id, first_name, last_name, last_login, " + "comments) values (?, ?, ?, ?, null)"; BatchInsert(DataSource dataSource) { super(dataSource, SQL); declareParameter(new SqlParameter(Types.INTEGER)); declareParameter(new SqlParameter(Types.VARCHAR)); declareParameter(new SqlParameter(Types.VARCHAR)); declareParameter(new SqlParameter(Types.TIMESTAMP)); setBatchSize(10); } }
int count = 5000; for (int i = 0; i < count; i++) { batchInsert.update(new Object[] { i + 100L, "a" + i, "b" + i, null }); }
xQspring JDBC主要的应用基本上都简单罗列一番,所有代码均为文章D例,不是很严谨,仅ؓ演示每一U用法,抛砖引玉Q希望有独特见解的拍砖,有问题的h明问题所?谢谢
目中有个业务处理类大小117KQ代?700行,看此cd炚wLQ如今如要增加业务逻辑大约20个吧Q此cd果随着目工程的二期、三期如ơ添加逻辑q早有一天大达到MQ噢、mygod。细心研d人的工作ȝQ发现其中有点可攚w的蛛丝马迹(本h很笨、别W我才发现如何改??/p>
下面我们对业务流E、以及涉及的相关c进行介l,Msg代表接受到客L的一个消息报文,消息报文l构Q消息头+消息体,消息头参数固定、消息体参数不定Q下面是一个简单的cdQ这只是一个模拟场景,****Req代表各户端请求类Q?span lang="EN-US">***Rsp代表q回l客L的参数类。实际比此复杂,为描q问题我们简单摘除几个类介绍Q别问我Zq么设计l承。类?span lang="EN-US">msg?span lang="EN-US">msgHead是组合关pM许画错了、不当之处请指出Q勿恶语向伤Q?/span>
处理hHandlercȝ代码逻辑如下Q?/p>
//cM主要Ҏ如下 public void execute(Object object) { Message message = (Message)object; int opcode = message.getOpcode(); int connectId = message.getConnectId(); //消息头已l解析,获取消息体,卛_cd性字节数l? byte[] bytes = message.getBytes(); if (opcode == MsgInfo.ADD_RING) { // 订购彩铃 orderRing(connectId, bytes); } else if (opcode == MsgInfo.PRESENT_RING) { // 赠送彩? presentRing(connectId, bytes); } else if (opcode == MsgInfo.DEL_RING) { // 删除个h铃音 delPersonalRing(connectId, bytes); } //此处省略n个else if } //其他删除、赠送与省略的else if中的处理逻辑与之基本相同 private void orderRing(int connectId, byte[] bytes) { //处理Ҏ分ؓ四步Q具体代码省? //1、解析字节数lؓ订购铃音c? //2、处理订购关p? //3、处理结果封装ؓ订购响应c? //4、发送回客户? } //省略presentRing、delPersonalRing{一pd其他ҎQ所有的处理Ҏ参数相同……
鉴于此、想C用命令模式改造此c,如果不了解命令模式请阅读相关书籍Q大话设计模式或设计与模式,q里我们仅给出大致的定于与类图?/p>
何谓命o模式Q将一个请求封装ؓ一个对象,从而是你可用不同的h对客L参数化,对请求排队或记录日志Q以及支持可撤销的操作?/p>
Shit、这句话很难理解哦,那就先别理解了,我们看下命o模式的类图,然后介绍如何使用命o模式攚w上面的elseif?/p>
cd先省略,上班L写的Q?/p>
下面q入正题Q对Handler手术开始,主要考虑如下Q?/p>
1?/span>提炼Ҏ
每?span lang="EN-US">if语句块中的逻辑提取Z个方法,q里我们?span lang="EN-US">handler已经实现Q就是:orderRing?span mce_style="font-family: 'Courier New'; color: black; font-size: 10pt;" style="font-family: 'Courier New'; color: black; font-size: 10pt; ">presentRing?span mce_style="font-family: 'Courier New'; color: black; font-size: 10pt;" style="font-family: 'Courier New'; color: black; font-size: 10pt; ">delPersonalRing?#8230;…?/p>
2?/span>提炼c?/strong>
每个业务处理方法提取ؓ以各c,然后对具体类q行抽象Q提取父cL者接口;代码如下Q?/p>
public Abstract class Command{ public void execute() ? public class OrderRingCommand extends Command { private Handler hander; public OrderRingCommand(Handler hander){ this.hander = hander; } public void execute(int connectId, byte[] bytes){ //1、解析字节数lؓ订购铃音c? //2、增加订购关p? //3、处理结果封装ؓ订购响应c? //4、发送回客户? } /** * 1、解析字节数lؓ订购铃音c? */ public void method1(){ } /** * 2、处理订购关p? */ public void method2(){ } /** * 3、处理结果封装ؓ订购响应c? */ public void method3(){ } /** * 4、结果发送回客户? */ public void method4(){ } } public class DelRingCommand extends Command { private Handler hander; public DelRingCommand(Handler hander){ this.hander = hander; } public void execute(int connectId, byte[] bytes){ //1、解析字节数lؓ订购铃音c? //2、删除购关系 //3、处理结果封装ؓ订购响应c? //4、发送回客户? } //提取Ҏ }
3?/span>命o模式攚w替?/strong>elseifQ?/strong>
Map<Integer, Command> map = new HashMap<Integer,Command>(); static{ map.put(MsgInfo.ADD_RING, new OrderRingCommand()); //省却其他Q这里仅为演C,实际目中实例化c通过spring容器或者其他方? } public void execute(Object object) { Message message = (Message)object; int opcode = message.getOpcode(); int connectId = message.getConnectId(); //消息头已l解析,获取消息体,卛_cd性字节数l? byte[] bytes = message.getBytes(); map.get(opcode).execute(connectId,bytes); }
命o模式替换else if代码坏味道的重构l束Q众多的if条g块烟消云散,取而代之的是一个个_的类Qdoc版本在附件中
一、ThreadLocal概述
学习JDK中的c,首先看下JDK APIҎcȝ描述Q描q如下:
API表达了下面几U观点:
1、ThreadLocal不是U程Q是U程的一个变量,你可以先单理解ؓU程cȝ属性变量?/p>
2、ThreadLocal 在类中通常定义为静态类变量?/p>
3、每个线E有自己的一个ThreadLocalQ它是变量的一?#8216;拯’Q修改它不媄响其他线E?/p>
既然定义为类变量Qؓ何ؓ每个U程l护一个副本(姑且成ؓ‘拯’Ҏ理解Q,让每个线E独立访问?多线E编E的l验告诉我们Q对于线E共享资源(你可以理解ؓ属性)Q资源是否被所有线E共享,也就是说q个资源被一个线E修Ҏ否媄响另一个线E的q行Q如果媄响我们需要用synchronized同步Q让U程序讉K?/p>
ThreadLocal适用于资源共享但不需要维护状态的情况Q也是一个线E对资源的修改,不媄响另一个线E的q行Q这U设计是‘I间换时?#8217;Qsynchronized序执行?strong>‘旉换取I间’?/p>
二、ThreadLocalҎ介绍
T |
get() q回此线E局部变量的当前U程副本中的倹{?/td> |
protected T |
initialValue() q回此线E局部变量的当前U程?#8220;初始?#8221;?/td> |
void |
remove() U除此线E局部变量当前线E的倹{?/td> |
void |
set(T value) 此U程局部变量的当前U程副本中的D|ؓ指定倹{?/td> |
三、深入源?/strong>
ThreadLocal有一个ThreadLocalMap静态内部类Q你可以单理解ؓ一个MAPQ这?#8216;Map’为每个线E复制一个变量的‘拯’存储其中?/p>
当线E调用ThreadLocal.get()Ҏ获取变量?首先获取当前U程引用Q以此ؓkey去获取响应的ThreadLocalMapQ如果此‘Map’不存在则初始化一个,否则q回其中的变量,代码如下Q?/p>
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
调用getҎ如果此Map不存在首先初始化Q创建此mapQ将U程为keyQ初始化的vlaue存入其中Q注意此处的initialValueQ我们可以覆盖此ҎQ在首次调用时初始化一个适当的倹{setInitialValue代码如下Q?/p>
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
setҎ相对比较单如果理解以上俩个方法,获取当前U程的引用,从map中获取该U程对应的mapQ如果map存在更新~存|否则创徏q存储,代码如下Q?/p>
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
对于ThreadLocal在何处存储变量副本,我们看getMapҎQ获取的是当前线E的ThreadLocalcd的threadLocals属性。显然变量副本存储在每一个线E中?/p>
/** * 获取U程的ThreadLocalMap 属性实? */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
上面我们知道变量副本存放于何处,q里我们单说下如何被java的垃圾收集机制收集,当我们不在用是调用set(null)Q此时不在将引用指向?#8216;map’Q而线E退出时会执行资源回收操作,申L资源q行回收Q其实就是将属性的引用讄为null。这时已l不在有M引用指向该mapQ故而会被垃圾收集?/p>
四、ThreadLocal应用CZ
在我的另一文章,对ThreadLocal的用做了一个实例,此示例也可以用作生环境Q请参见Q?a mce_href="/blog/757641">http://ari.iteye.com/blog/757641
如有问题La讨论Q谢?/p>
本文借花献佛Q引用Tim Cull的博?#8220;SimpleDateFormat: Performance Pig”介绍下ThreadLocal的简单用,同时也对SimpleDateFormat的用有个深入的了解?/p>
大致意思:Tim Cull到一个SimpleDateFormat带来的严重的性能问题Q该问题主要有SimpleDateFormat引发Q创Z个SimpleDateFormat实例的开销比较昂贵Q解析字W串旉旉J创建生命周期短暂的实例D性能低下。即使将SimpleDateFormat定义为静态类变量Q貌D解决q个问题Q但是SimpleDateFormat是非U程安全的,同样存在问题Q如果用‘synchronized’U程同步同样面问题Q同步导致性能下降Q线E之间序列化的获取SimpleDateFormat实例Q?/p>
Tim Cull使用Threadlocal解决了此问题Q对于每个线ESimpleDateFormat不存在媄响他们之间协作的状态,为每个线E创Z个SimpleDateFormat变量的拷贝或者叫做副本,代码如下Q?/p>
import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * 使用ThreadLocal以空间换旉解决SimpleDateFormatU程安全问题? * @author * */ public class DateUtil { private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; @SuppressWarnings("rawtypes") private static ThreadLocal threadLocal = new ThreadLocal() { protected synchronized Object initialValue() { return new SimpleDateFormat(DATE_FORMAT); } }; public static DateFormat getDateFormat() { return (DateFormat) threadLocal.get(); } public static Date parse(String textDate) throws ParseException { return getDateFormat().parse(textDate); } }
创徏一个ThreadLocalcd量,q里创徏时用了一个匿名类Q覆盖了initialValueҎQ主要作用是创徏时初始化实例。也可以采用下面方式创徏Q?/p>
//W一ơ调用get返回null private static ThreadLocal threadLocal = new ThreadLocal()Q? //获取U程的变量副本,如果不覆盖initialValueQ第一ơgetq回nullQ故需要初始化一个SimpleDateFormatQƈset到threadLocal? public static DateFormat getDateFormat() { DateFormat df = (DateFormat) threadLocal.get(); if(df==null){ df = new SimpleDateFormat(DATE_FORMAT) threadLocal.set(df); } return df; }
我们看下我们覆盖的initialValueҎQ?/p>
protected T initialValue() { return null;//直接q回null }
作者:niumd
blogQ?a mce_href="/">http://ari.iteye.com
一、概q?/strong>
Struts2的核心是一个FilterQAction可以qweb容器Q那么是什么让httph和action兌在一LQ下面我们深入源码来分析下Struts2是如何工作的?/p>
鉴于常规情况官方推荐使用StrutsPrepareAndExecuteFilter替代FilterDispatcherQ我们此文将剖析StrutsPrepareAndExecuteFilterQ其在工E中作ؓ一个Filter配置在web.xml中,配置如下Q?/p>
<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>
二、源码属性方法简?/strong>
下面我们研究下StrutsPrepareAndExecuteFilter源码Q类的主要信息如下:
属性摘?/strong> | |
---|---|
protected List<Pattern> |
excludedPatterns |
protected ExecuteOperations |
execute |
protected PrepareOperations |
prepare |
StrutsPrepareAndExecuteFilter与普通的Filterq无区别Q方法除l承自Filter外,仅有一个回调方法,W三部分我们按照FilterҎ调用序Q由init?gt;doFilter?gt;destroy序地分析源码?/p>
Ҏ摘要 | |
---|---|
void |
destroy() l承自FilterQ用于资源释?/td> |
void |
doFilter(ServletRequest req, ServletResponse res, FilterChain chain) l承自FilterQ执行方?/td> |
void |
init(FilterConfig filterConfig) l承自FilterQ初始化参数 |
protected void |
postInit(Dispatcher dispatcher, FilterConfig filterConfig) Callback for post initializationQ一个空的方法,用于Ҏ回调初始化) |
三、源码剖?nbsp;
1、initҎ
init是FilterW一个运行的ҎQ我们看下struts2的核心Filter在调用initҎ初始化时做哪些工作:
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); try { //装filterConfigQ其中有个主要方法getInitParameterNames参数名字以String格式存储在List? FilterHostConfig config = new FilterHostConfig(filterConfig); // 初始化struts内部日志 init.initLogging(config); //创徏dispatcher Qƈ初始化,q部分下面我们重点分析,初始化时加蝲那些资源 Dispatcher dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); //初始化类属性:prepare 、execute prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); //回调I的postInitҎ postInit(dispatcher, filterConfig); } finally { init.cleanup(); } }
首先看下FilterHostConfig Q源码如下:
public class FilterHostConfig implements HostConfig { private FilterConfig config; /** *构造函? */ public FilterHostConfig(FilterConfig config) { this.config = config; } /** * Ҏinit-param配置的param-name获取param-value的? */ public String getInitParameter(String key) { return config.getInitParameter(key); } /** * q回初始化参数名的List */ public Iterator<String> getInitParameterNames() { return MakeIterator.convert(config.getInitParameterNames()); } public ServletContext getServletContext() { return config.getServletContext(); } }
只有短短的几行代码,getInitParameterNames是这个类的核心,Filter初始化参数名U有枚Dcd转ؓIterator。此cȝ主要作ؓ是对filterConfig 装?/p>
重点来了Q创建ƈ初始化Dispatcher
public Dispatcher initDispatcher( HostConfig filterConfig ) { Dispatcher dispatcher = createDispatcher(filterConfig); dispatcher.init(); return dispatcher; }
创徏DispatcherQ会d filterConfig 中的配置信息Q将配置信息解析出来Q封装成Z个MapQ然后根lservlet上下文和参数Map构造Dispatcher Q?/p>
private Dispatcher createDispatcher( HostConfig filterConfig ) { Map<String, String> params = new HashMap<String, String>(); for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) { String name = (String) e.next(); String value = filterConfig.getInitParameter(name); params.put(name, value); } return new Dispatcher(filterConfig.getServletContext(), params); }
Dispatcher初始化,加蝲struts2的相关配|文Ӟ按照顺序逐一加蝲Qdefault.propertiesQstruts-default.xml,struts-plugin.xml,struts.xmlQ?#8230;…
/** *初始化过E中依次加蝲如下配置文g */ public void init() { if (configurationManager == null) { configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } try { //加蝲org/apache/struts2/default.properties init_DefaultProperties(); // [1] //加蝲struts-default.xml,struts-plugin.xml,struts.xml init_TraditionalXmlConfigurations(); // [2] init_LegacyStrutsProperties(); // [3] //用户自己实现的ConfigurationProvidersc? init_CustomConfigurationProviders(); // [5] //Filter的初始化参数 init_FilterInitParameters() ; // [6] init_AliasStandardObjects() ; // [7] Container container = init_PreloadConfiguration(); container.inject(this); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } catch (Exception ex) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }
初始化default.propertiesQ具体的初始化操作在DefaultPropertiesProvidercM
private void init_DefaultProperties() { configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); }
下面我们看下DefaultPropertiesProvidercL码:
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { Settings defaultSettings = null; try { defaultSettings = new PropertiesSettings("org/apache/struts2/default"); } catch (Exception e) { throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e); } loadSettings(props, defaultSettings); }
其他的我们再ơ省略,大家可以览下各个初始化操作都加载了那些文g
3、doFilterҎ
doFilter是过滤器的执行方法,它拦截提交的HttpServletRequesthQHttpServletResponse响应Q作为strtus2的核心拦截器Q在doFilter里面到底做了哪些工作Q我们将逐行解读其源码,源码如下Q?/p>
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { //父类向子c{Q强转ؓhttph、响? HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { //讄~码和国际化 prepare.setEncodingAndLocale(request, response); //创徏Action上下文(重点Q? prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
setEncodingAndLocale调用了dispatcherҎ的prepareҎQ?/p>
/** * Sets the request encoding and locale on the response */ public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { dispatcher.prepare(request, response); }
下面我们看下prepareҎQ这个方法很单只是设|了encoding 、locale Q做的只是一些辅助的工作Q?/p>
public void prepare(HttpServletRequest request, HttpServletResponse response) { String encoding = null; if (defaultEncoding != null) { encoding = defaultEncoding; } Locale locale = null; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } if (encoding != null) { try { request.setCharacterEncoding(encoding); } catch (Exception e) { LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e); } } if (locale != null) { response.setLocale(locale); } if (paramsWorkaroundEnabled) { request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request } }
Action上下文创建(重点Q?/strong>
ActionContext是一个容器,q个Ҏ主要存储request、session、application、parameters{相关信?ActionContext是一个线E的本地变量Q这意味着不同的action之间不会׃nActionContextQ所以也不用考虑U程安全问题。其实质是一个MapQkey是标Crequest、session?#8230;…的字W串Q值是其对应的对象Q?/p>
static ThreadLocal actionContext = new ThreadLocal(); Map<String, Object> context;
下面我们看下如何创徏action上下文的Q代码如下:
/** *创徏Action上下文,初始化thread local */ public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } //注意此处是从ThreadLocal中获取此ActionContext变量 ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); //stack.getContext()q回的是一个Map<StringQObject>Q根据此Map构造一个ActionContext ctx = new ActionContext(stack.getContext()); } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); //ActionContext存如ThreadLocal ActionContext.setContext(ctx); return ctx; }
上面代码中dispatcher.createContextMapQ如何封装相兛_敎ͼ
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping, ServletContext context) { // request map wrapping the http request objects Map requestMap = new RequestMap(request); // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately Map params = new HashMap(request.getParameterMap()); // session map wrapping the http session Map session = new SessionMap(request); // application map wrapping the ServletContext Map application = new ApplicationMap(context); //requestMap、params、session{Map装成ؓ一个上下文MapQ逐个调用了map.put(Map p). Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context); if (mapping != null) { extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); } return extraContext; }
我们单看下RequestMapQ其他的省略。RequestMapcdC抽象MapQ故其本w是一个MapQ主要方法实玎ͼ
//map的get实现 public Object get(Object key) { return request.getAttribute(key.toString()); } //map的put实现 public Object put(Object key, Object value) { Object oldValue = get(key); entries = null; request.setAttribute(key.toString(), value); return oldValue; }
下面是源码展CZ如何执行Action控制器:
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { dispatcher.serviceAction(request, response, servletContext, mapping); } public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { //装执行的上下文环境Q主要讲相关信息存储入map Map<String, Object> extraContext = createContextMap(request, response, mapping, context); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); //获取命名I间 String namespace = mapping.getNamespace(); //获取action配置的name属? String name = mapping.getName(); //获取action配置的method属? String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); //Ҏ执行上下文参敎ͼ命名I间Q名U等创徏用户自定义Action的代理对? ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! //执行executeҎQƈ转向l果 if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { // WW-2874 Only log error if in devMode if(devMode) { String reqStr = request.getRequestURI(); if (request.getQueryString() != null) { reqStr = reqStr + "?" + request.getQueryString(); } LOG.error("Could not find action or result\n" + reqStr, e); } else { LOG.warn("Could not find action or result", e); } sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } finally { UtilTimerStack.pop(timerKey); } }
文中对如何解析Struts.xmlQ如何将URL与action映射匚w为分析,有需要的我后l补全,因ؓStrutsXmlConfigurationProviderl承XmlConfigurationProviderQƈ在registerҎ回调父类的registerQ有兴趣的可以深入阅M下XmlConfigurationProvider源码Q?/p>
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { if (servletContext != null && !containerBuilder.contains(ServletContext.class)) { containerBuilder.factory(ServletContext.class, new Factory<ServletContext>() { public ServletContext create(Context context) throws Exception { return servletContext; } }); } //调用父类的registerQ关键点所? super.register(containerBuilder, props); }
struts2-core-2.2.1.jar包中struts-2.1.7.dtd对于Action的定义如下:
<!ELEMENT action (param|result|interceptor-ref|exception-mapping)*> <!ATTLIST action name CDATA #REQUIRED class CDATA #IMPLIED method CDATA #IMPLIED converter CDATA #IMPLIED >
从上qDTD中可见Action元素可以含有name 、class 、method 、converter 属性?/p>
XmlConfigurationProvider解析struts.xml配置的Action元素Q?/p>
protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException { String name = actionElement.getAttribute("name"); String className = actionElement.getAttribute("class"); String methodName = actionElement.getAttribute("method"); Location location = DomHelper.getLocationObject(actionElement); if (location == null) { LOG.warn("location null for " + className); } //methodName should be null if it's not set methodName = (methodName.trim().length() > 0) ? methodName.trim() : null; // if there isnt a class name specified for an <action/> then try to // use the default-class-ref from the <package/> if (StringUtils.isEmpty(className)) { // if there is a package default-class-ref use that, otherwise use action support /* if (StringUtils.isNotEmpty(packageContext.getDefaultClassRef())) { className = packageContext.getDefaultClassRef(); } else { className = ActionSupport.class.getName(); }*/ } else { if (!verifyAction(className, name, location)) { if (LOG.isErrorEnabled()) LOG.error("Unable to verify action [#0] with class [#1], from [#2]", name, className, location.toString()); return; } } Map<String, ResultConfig> results; try { results = buildResults(actionElement, packageContext); } catch (ConfigurationException e) { throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement); } List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext); List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext); ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className) .methodName(methodName) .addResultConfigs(results) .addInterceptors(interceptorList) .addExceptionMappings(exceptionMappings) .addParams(XmlHelper.getParams(actionElement)) .location(location) .build(); packageContext.addActionConfig(name, actionConfig); if (LOG.isDebugEnabled()) { LOG.debug("Loaded " + (StringUtils.isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "") + name + " in '" + packageContext.getName() + "' package:" + actionConfig); } }
工作中不涉及Struts2Q本周工作有?天的I档期,E微看了下struts2的文档,写了个demoQ从源码的角度研I了下运行原理,如有分析不当h出,我后l逐步完善更正Q大家共同提高?/p>