标签:
我们看看Spring中的事务处理的代码,使用Spring管理事务有声明式和编程式两种方式,声明式事务处理通过AOP的实现把事物管理代码作为方面封装来横向插入到业务代码中,使得事务管理 代码和业务代码解藕。在这种方式我们结合IoC容器和Spirng已有的FactoryBean来对事务管理进行属性配置,比如传播行为,隔离级别等。其 中最简单的方式就是通过配置TransactionProxyFactoryBean来实现声明式事物;
在整个源代码分析中,我们可以大致可以看到Spring实现声明式事物管理有这么几个部分:
对在上下文中配置的属性的处理,这里涉及的类是TransactionAttributeSourceAdvisor,这是一个通知器,用它来对属性值进行处理,属性信息放在TransactionAttribute中来使用,而这些属性的处理往往是和对切入点的处理是结合起来的。对属性的处理放在类TransactionAttributeSource中完成。
创建事物的过程,这个过程是委托给具体的事物管理器来创建的,但Spring通过TransactionStatus来传递相关的信息。
对事物的处理通过对相关信息的判断来委托给具体的事物管理器完成。
我们下面看看具体的实现,在TransactionFactoryBean中:
public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBean
implements FactoryBean, BeanFactoryAware {
//这里是Spring事务处理而使用的AOP拦截器,中间封装了Spring对事务处理的代码来支持声明式事务处理的实现
private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
private Pointcut pointcut;
//这里Spring把TransactionManager注入到TransactionInterceptor中去
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionInterceptor.setTransactionManager(transactionManager);
}
//这里把在bean配置文件中读到的事务管理的属性信息注入到TransactionInterceptor中去
public void setTransactionAttributes(Properties transactionAttributes) {
this.transactionInterceptor.setTransactionAttributes(transactionAttributes);
}
.........中间省略了其他一些方法.......
//这里创建Spring AOP对事务处理的Advisor
protected Object createMainInterceptor() {
this.transactionInterceptor.afterPropertiesSet();
if (this.pointcut != null) {
//这里使用默认的通知器
return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor);
}
else {
// 使用上面定义好的TransactionInterceptor作为拦截器,同时使用TransactionAttributeSourceAdvisor
return new TransactionAttributeSourceAdvisor(this.transactionInterceptor);
}
}
}
那什么时候Spring的TransactionInterceptor被注入到Spring AOP中成为Advisor中的一部分呢?我们看到在TransactionProxyFactoryBean中,这个方法在IOC初始化bean的时候被执行:
public void afterPropertiesSet() {
.......
//TransactionProxyFactoryBean实际上使用ProxyFactory完成AOP的基本功能。
ProxyFactory proxyFactory = new ProxyFactory();
if (this.preInterceptors != null) {
for (int i = 0; i < this.preInterceptors.length; i++) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(this.preInterceptors[i]));
}
}
//这里是Spring加入通知器的地方
//有两种通知器可以被加入DefaultPointcutAdvisor或者TransactionAttributeSourceAdvisor
//这里把Spring处理声明式事务处理的AOP代码都放到ProxyFactory中去,怎样加入advisor我们可以参考ProxyFactory的父类AdvisedSupport()
//由它来维护一个advice的链表,通过这个链表的增删改来抽象我们对整个通知器配置的增删改操作。
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));
if (this.postInterceptors != null) {
for (int i = 0; i < this.postInterceptors.length; i++) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(this.postInterceptors[i]));
}
}
proxyFactory.copyFrom(this);
//这里创建AOP的目标源
TargetSource targetSource = createTargetSource(this.target);
proxyFactory.setTargetSource(targetSource);
if (this.proxyInterfaces != null) {
proxyFactory.setInterfaces(this.proxyInterfaces);
}
else if (!isProxyTargetClass()) {
proxyFactory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass()));
}
this.proxy = getProxy(proxyFactory);
}
Spring已经定义了一个transctionInterceptor作为拦截器或者AOP advice的实现,在IOC容器中定义的其他属性比如transactionManager和事务管理的属性都会传到已经定义好的TransactionInterceptor那里去进行处理。以上反映了基本的Spring AOP的定义过程,其中pointcut和advice都已经定义好,同时也通过通知器配置到ProxyFactory中去了。
下面让我们回到TransactionProxyFactoryBean中看看TransactionAttributeSourceAdvisor是怎样定义的,这样我们可以理解具体的属性是怎样起作用,这里我们分析一下类TransactionAttributeSourceAdvisor:
public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor {
//和其他Advisor一样,同样需要定义AOP中的用到的Interceptor和Pointcut
//Interceptor使用传进来的TransactionInterceptor
//而对于pointcut,这里定义了一个内部类,参见下面的代码
private TransactionInterceptor transactionInterceptor;
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut();
.........
//定义的PointCut内部类
private class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
.......
//方法匹配的实现,使用了TransactionAttributeSource类
public boolean matches(Method method, Class targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
//这里使用TransactionAttributeSource来对配置属性进行处理
return (tas != null && tas.getTransactionAttribute(method, targetClass) != null);
}
........省略了equal,hashcode,tostring的代码
}
这里我们看看属性值是怎样被读入的:AbstractFallbackTransactionAttributeSource负责具体的属性读入任务,我们可以有两种读入方式,比如annotation和直接配置.我们下面看看直接配置的读入方式,在Spring中同时对读入的属性值进行了缓存处理,这是一个decorator模式:
public final TransactionAttribute getTransactionAttribute(Method method, Class targetClass) {
//这里先查一下缓存里有没有事务管理的属性配置,如果有从缓存中取得TransactionAttribute
Object cacheKey = getCacheKey(method, targetClass);
Object cached = this.cache.get(cacheKey);
if (cached != null) {
if (cached == NULL_TRANSACTION_ATTRIBUTE) {
return null;
}
else {
return (TransactionAttribute) cached;
}
}
else {
// 这里通过对方法和目标对象的信息来计算事务缓存属性
TransactionAttribute txAtt = computeTransactionAttribute(method, targetClass);
//把得到的事务缓存属性存到缓存中,下次可以直接从缓存中取得。
if (txAtt == null) {
this.cache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
}
else {
...........
this.cache.put(cacheKey, txAtt);
}
return txAtt;
}
}
别急,基本的处理在computeTransactionAttribute()中:
private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) {
//这里检测是不是public方法
if(allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAtt = findTransactionAttribute(findAllAttributes(specificMethod));
if (txAtt != null) {
return txAtt;
}
// Second try is the transaction attribute on the target class.
txAtt = findTransactionAttribute(findAllAttributes(specificMethod.getDeclaringClass()));
if (txAtt != null) {
return txAtt;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAtt = findTransactionAttribute(findAllAttributes(method));
if (txAtt != null) {
return txAtt;
}
// Last fallback is the class of the original method.
return findTransactionAttribute(findAllAttributes(method.getDeclaringClass()));
}
return null;
}
经过一系列的尝试我们可以通过findTransactionAttribute()通过调用findAllAttribute()得到TransactionAttribute的对象,如果返回的是null,这说明该方法不是我们需要事务处理的方法。
在完成把需要的通知器加到ProxyFactory中去的基础上,我们看看具体的看事务处理代码怎样起作用,在TransactionInterceptor中:
public Object invoke(final MethodInvocation invocation) throws Throwable {
//这里得到目标对象
Class targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null);
//这里同样的通过判断是否能够得到TransactionAttribute来决定是否对当前方法进行事务处理,有可能该属性已经被缓存,
//具体可以参考上面对getTransactionAttribute的分析,同样是通过TransactionAttributeSource
final TransactionAttribute txAttr =
getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass);
final String joinpointIdentification = methodIdentification(invocation.getMethod());
//这里判断我们使用了什么TransactionManager
if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) {
// 这里创建事务,同时把创建事务过程中得到的信息放到TransactionInfo中去
TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification);
Object retVal = null;
try {
retVal = invocation.proceed();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// 使用的是Spring定义的PlatformTransactionManager同时实现了回调接口,我们通过其回调函数完成事务处理,就像我们使用编程式事务处理一样。
try {
Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr,
new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
//同样的需要一个TransactonInfo
TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status);
try {
return invocation.proceed();
}
.....这里省去了异常处理和事务信息的清理代码
});
...........
}
}
这里面涉及到事务的创建,我们可以在TransactionAspectSupport实现的事务管理代码:
protected TransactionInfo createTransactionIfNecessary(
TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
//这里使用了我们定义好的事务配置信息,有事务管理器来创建事务,同时返回TransactionInfo
status = getTransactionManager().getTransaction(txAttr);
}
return prepareTransactionInfo(txAttr, joinpointIdentification, status);
}
首先通过TransactionManager得到需要的事务,事务的创建根据我们定义的事务配置决定,在AbstractTransactionManager中给出一个标准的创建过程,当然创建什么样的事务还是需要具体的PlatformTransactionManager来决定,但这里给出了创建事务的模板:
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
......
if (definition == null) {
//如果事务信息没有被配置,我们使用Spring默认的配置方式
definition = new DefaultTransactionDefinition();
}
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// Check definition settings for new transaction.
//下面就是使用配置信息来创建我们需要的事务;比如传播属性和同步属性等
//最后把创建过程中的信息收集起来放到TransactionStatus中返回;
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to behave.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"Transaction propagation ‘mandatory‘ but no existing transaction found");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//这里是事务管理器创建事务的地方,并将创建过程中得到的信息放到TransactionStatus中去,包括创建出来的事务
doBegin(transaction, definition);
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
}
else {
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return newTransactionStatus(definition, null, false, newSynchronization, debugEnabled, null);
}
}
接着通过调用prepareTransactionInfo完成事务创建的准备,创建过程中得到的信息存储在TransactionInfo对象中进行传递同时把信息和当前线程绑定;
protected TransactionInfo prepareTransactionInfo(
TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
TransactionInfo txInfo = new TransactionInfo(txAttr, joinpointIdentification);
if (txAttr != null) {
.....
// 同样的需要把在getTransaction中得到的TransactionStatus放到TransactionInfo中来。
txInfo.newTransactionStatus(status);
}
else {
.......
}
// 绑定事务创建信息到当前线程
txInfo.bindToThread();
return txInfo;
}
将创建事务的信息返回,然后看到其他的事务管理代码:
protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
if (txInfo != null && txInfo.hasTransaction()) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking commit for transaction on " + txInfo.getJoinpointIdentification());
}
this.transactionManager.commit(txInfo.getTransactionStatus());
}
}
通过transactionManager对事务进行处理,包括异常抛出和正常的提交事务,具体的事务管理器由用户程序设定。
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.hasTransaction()) {
if (txInfo.transactionAttribute.rollbackOn(ex)) {
......
try {
this.transactionManager.rollback(txInfo.getTransactionStatus());
}
..........
}
else {
.........
try {
this.transactionManager.commit(txInfo.getTransactionStatus());
}
...........
}
protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
if (txInfo != null && txInfo.hasTransaction()) {
......
this.transactionManager.commit(txInfo.getTransactionStatus());
}
}
Spring通过以上代码对transactionManager进行事务处理的过程进行了AOP包装,到这里我们看到为了方便客户实现声明式的事务处理,Spring还是做了许多工作的。如果说使用编程式事务处理,过程其实比较清楚,我们可以参考书中的例子:
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(td);
try{
......//这里是我们的业务方法
}catch (ApplicationException e) {
transactionManager.rollback(status);
throw e
}
transactionManager.commit(status);
........
我们看到这里选取了默认的事务配置DefaultTransactionDefinition,同时在创建事物的过程中得到TransactionStatus,然后通过直接调用事务管理器的相关方法就能完成事务处理。
声明式事务处理也同样实现了类似的过程,只是因为采用了声明的方法,需要增加对属性的读取处理,并且需要把整个过程整合到Spring AOP框架中和IoC容器中去的过程。
下面我们选取一个具体的transactionManager - DataSourceTransactionManager来看看其中事务处理的实现:
同样的通过使用AbstractPlatformTransactionManager使用模板方法,这些都体现了对具体平台相关的事务管理器操作的封装,比如commit:
public final void commit(TransactionStatus status) throws TransactionException {
......
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
......
processRollback(defStatus);
return;
}
.......
processRollback(defStatus);
......
}
processCommit(defStatus);
}
通过对TransactionStatus的具体状态的判断,来决定具体的事务处理:
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
boolean globalRollbackOnly = false;
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
globalRollbackOnly = status.isGlobalRollbackOnly();
}
if (status.hasSavepoint()) {
........
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
......
doCommit(status);
}
.........
}
这些模板方法的实现由具体的transactionManager来实现,比如在DataSourceTransactionManager:
protected void doCommit(DefaultTransactionStatus status) {
//这里得到存在TransactionInfo中已经创建好的事务
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
//这里得到和事务绑定的数据库连接
Connection con = txObject.getConnectionHolder().getConnection();
........
try {
//这里通过数据库连接来提交事务
con.commit();
}
.......
}
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
//这里通过数据库连接来回滚事务
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
我们看到在DataSourceTransactionManager中最后还是交给connection来实现事务的提交和rollback。整个声明式事务处理是事务处理在Spring AOP中的应用,我们看到了一个很好的使用Spring AOP的例子,在Spring声明式事务处理的源代码中我们可以看到:
1.怎样封装各种不同平台下的事务处理代码
2.怎样读取属性值和结合事务处理代码来完成既定的事务处理策略
3.怎样灵活的使用SpringAOP框架。
如果能够结合前面的Spring AOP的源代码来学习,理解可能会更深刻些。
发表者 jiwenke 位置在: 下午9:44 0 评论 指向此文章的链接
2007年6月8日 星期五
下面我们来看看Spring的AOP的一些相关代码是怎么得到Proxy的,让我们我们先看看AOP和Spring AOP的一些基本概念:
Advice:
通知,制定在连接点做什么,在Sping中,他主要描述Spring围绕方法调用注入的额外的行为,Spring提供的通知类型有:
before advice,AfterReturningAdvice,ThrowAdvice,MethodBeforeAdvice,这些都是Spring AOP定义的接口类,具体的动作实现需要用户程序来完成。
Pointcut:
切点,其决定一个advice应该应用于哪个连接点,也就是需要插入额外处理的地方的集合,例如,被某个advice作为目标的一组方法。Spring pointcut通常意味着标示方法,可以选择一组方法调用作为pointcut,Spring提供了具体的切点来给用户使用,比如正则表达式切点 JdkRegexpMethodPointcut通过正则表达式对方法名进行匹配,其通过使用 AbstractJdkRegexpMethodPointcut中的对MethodMatcher接口的实现来完成pointcut功能:
public final boolean matches(Method method, Class targetClass) {
// TODO use target class here?
String patt = method.getDeclaringClass().getName() + "." + method.getName();
for (int i = 0; i < this.patterns.length; i++) {
// 这里是判断是否和方法名匹配的代码,当然知道true或者false
boolean matched = matches(patt, i);
if (matched) {
for (int j = 0; j < this.excludedPatterns.length; j++) {
boolean excluded = matchesExclusion(patt, j);
if(excluded) {
return false;
}
}
return true;
}
}
return false;
}
在JDKRegexpMethodPointcut中通过JDK中的正则表达式匹配来完成pointcut的最终锁定
protected boolean matches(String pattern, int patternIndex) {
Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
return matcher.matches();
}
Advisor:
当 我们完成额外完成的动作设计(advice)和动作插入点的设计(pointcut)以后,我们需要一个对象把他们结合起来,这就是通知器 - advisor,定义应该在哪里应用哪个通知。Advisor的实现有:DefaultPointcutAdvisor他有两个属性advice和 pointcut来让我们配置advice和pointcut。
接着我们就可以通过ProxyFactoryBean来配置我们的代理对象和方面 行为,在ProxyFactoryBean中有interceptorNames来配置已经定义好的通知器-advisor,具体的代理实现通过JDK 的Proxy或者CGLIB的技术来完成。我们可以看看具体的代码实现,在ProxyFactoryBean中我们看看怎样得到Proxy:
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
//根据定义需要生成单件的Proxy
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable." +
"Enable prototype proxies by setting the ‘targetName‘ property.");
}
//根据定义需要生成Prototype的Proxy
return newPrototypeInstance();
}
}
我们看看怎样生成单件的Proxy:
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// 这里设置Proxy的接口
setInterfaces(ClassUtils.getAllInterfacesForClass(this.targetSource.getTargetClass()));
}
// Eagerly initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
// 注意这里的方法会使用ProxyFactory来生成我们需要的Proxy
this.singletonInstance = getProxy(createAopProxy());
// We must listen to superclass advice change events to recache the singleton
// instance if necessary.
addListener(this);
}
return this.singletonInstance;
}
ProxyFactoryBean的父类是AdvisedSupport,Spring使用AopProxyFactory接口把AOP代理的实现与框架的其他部分分离开来;在AdvisedSupport中通过这样的方式来得到AopProxy,这里还需要AopProxyFactory的帮助 - 下面我们看到Spring为我们提供了默认的实现可以帮助我们方便的从JDK或者cglib中得到我们想要的:
protected synchronized AopProxy createAopProxy() {
if (!this.isActive) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
而在ProxyConfig中对使用的AopProxyFactory做了定义:
//这个DefaultAopProxyFactory是Spring用来生成AopProxy的地方,
//当然了它包含JDK和Cglib两种实现方式。
private transient AopProxyFactory aopProxyFactory = new DefaultAopProxyFactory();
其中在DefaultAopProxyFactory中是这样生成AopProxy的:
public AopProxy createAopProxy(AdvisedSupport advisedSupport) throws AopConfigException {
if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass() ||
advisedSupport.getProxiedInterfaces().length == 0) {
if (!cglibAvailable) {
throw new AopConfigException(
"Cannot proxy target class because CGLIB2 is not available. " +
"Add CGLIB to the class path or specify proxy interfaces.");
}
// 这里使用Cglib来生成Proxy,如果target不是接口的实现的话
return CglibProxyFactory.createCglibProxy(advisedSupport);
}
else {
// 这里使用JDK来生成Proxy
return new JdkDynamicAopProxy(advisedSupport);
}
}
于是我们就可以看到其中的Proxy可以有JDK或者Cglib来生成,我们看到JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,在JDK实现中我们可以看到Proxy是怎样生成的:
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
Class targetClass = this.advised.getTargetSource().getTargetClass();
logger.debug("Creating JDK dynamic proxy" +
(targetClass != null ? " for [" + targetClass.getName() + "]" : ""));
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//这里我们调用JDK Proxy来生成需要的Proxy实例
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
这样用Proxy包装target之后,对其的调用就被Proxy拦截了,ProxyFactoryBean的getObject()方法得到的实际上已经是Proxy了。
发表者 jiwenke 位置在: 上午2:51 0 评论 指向此文章的链接
2007年6月7日 星期四
下面我们对Spring MVC框架代码进行分析,对于webApplicationContext的相关分析可以参见以前的文档,我们这里着重分析Spring Web MVC框架的实现.我们从分析DispatcherServlet入手:
//这里是对DispatcherServlet的初始化方法,根据名字我们很方面的看到对各个Spring MVC主要元素的初始化
protected void initFrameworkServlet() throws ServletException, BeansException {
initMultipartResolver();
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initRequestToViewNameTranslator();
initViewResolvers();
}
看 到注解我们知道,这是DispatcherSerlvet的初始化过程,它是在WebApplicationContext已经存在的情况下进行的,也就 意味着在初始化它的时候,IOC容器应该已经工作了,这也是我们在web.xml中配置Spring的时候,需要把DispatcherServlet的 load-on-startup的属性配置为2的原因。
对于具体的初始化过程,很容易理解,我们拿initHandlerMappings()来看看:
private void initHandlerMappings() throws BeansException {
if (this.detectAllHandlerMappings) {
// 这里找到所有在上下文中定义的HandlerMapping,同时把他们排序
// 因为在同一个上下文中可以有不止一个handlerMapping,所以我们把他们都载入到一个链里进行维护和管理
Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
getWebApplicationContext(), HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
// 这里通过order属性来对handlerMapping来在list中排序
Collections.sort(this.handlerMappings, new OrderComparator());
}
}
else {
try {
Object hm = getWebApplicationContext().getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we‘ll add a default HandlerMapping later.
}
}
//如果在上下文中没有定义的话,那么我们使用默认的BeanNameUrlHandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(HandlerMapping.class);
........
}
}
怎样获得上下文环境,可以参见我们前面的对IOC容器在web环境中加载的分析。 DispatcherServlet把定义了的所有HandlerMapping都加载了放在一个List里待以后进行使用,这个链的每一个元素都是一个handlerMapping的配置,而一般每一个handlerMapping可以持有一系列从URL请求到 Spring Controller的映射,比如SimpleUrl
HandlerMaaping中就定义了一个map来持有这一系列的映射关系。
DisptcherServlet通过HandlerMapping使得Web应用程序确定一个执行路径,就像我们在HanderMapping中看到的那样,HandlerMapping只是一个借口:
public interface HandlerMapping {
public static final String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE =
Conventions.getQualifiedAttributeName(HandlerMapping.class, "pathWithinHandlerMapping");
//实际上维护一个HandlerExecutionChain,这是典型的Command的模式的使用,这个执行链里面维护handler和拦截器
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
他的具体实现只需要实现一个接口方法,而这个接口方法返回的是一个HandlerExecutionChain,实际上就是一个执行链,就像在Command模式描述的那样,这个类很简单,就是一个持有一个Interceptor链和一个Controller:
public class HandlerExecutionChain {
private Object handler;
private HandlerInterceptor[] interceptors;
........
}
而这些Handler和Interceptor需要我们定义HandlerMapping的时候配置好,比如对具体的SimpleURLHandlerMapping,他要做的就是根据URL映射的方式注册Handler和Interceptor,自己维护一个放映映射的handlerMap,当需要匹配Http请求的时候需要使用这个表里的信息来得到执行链。这个注册的过程在IOC容器初始化SimpleUrlHandlerMapping的时候就被完成了,这样以后的解析才可以用到map里的映射信息,这里的信息和bean文件的信息是等价的,下面是具体的注册过程:
protected void registerHandlers(Map urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither ‘urlMap‘ nor ‘mappings‘ set on SimpleUrlHandlerMapping");
}
else {
//这里迭代在SimpleUrlHandlerMapping中定义的所有映射元素
Iterator it = urlMap.keySet().iterator();
while (it.hasNext()) {
//这里取得配置的url
String url = (String) it.next();
//这里根据url在bean定义中取得对应的handler
Object handler = urlMap.get(url);
// Prepend with slash if not already present.
if (!url.startsWith("/")) {
url = "/" + url;
}
//这里调用AbstractHandlerMapping中的注册过程
registerHandler(url, handler);
}
}
}
在AbstractMappingHandler中的注册代码:
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
//试图从handlerMap中取handler,看看是否已经存在同样的Url映射关系
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
........
}
//如果是直接用bean名做映射那就直接从容器中取handler
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
handler = getApplicationContext().getBean(handlerName);
}
}
//或者使用默认的handler.
if (urlPath.equals("/*")) {
setDefaultHandler(handler);
}
else {
//把url和handler的对应关系放到handlerMap中去
this.handlerMap.put(urlPath, handler);
........
}
}
handlerMap是持有的一个HashMap,里面就保存了具体的映射信息:
private final Map handlerMap = new HashMap();
而SimpleUrlHandlerMapping对接口HandlerMapping的实现是这样的,这个getHandler根据在初始化的时候就得到的映射表来生成DispatcherServlet需要的执行链
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//这里根据request中的参数得到其对应的handler,具体处理在AbstractUrlHandlerMapping中
Object handler = getHandlerInternal(request);
//如果找不到对应的,就使用缺省的handler
if (handler == null) {
handler = this.defaultHandler;
}
//如果缺省的也没有,那就没办法了
if (handler == null) {
return null;
}
// 如果handler不是一个具体的handler,那我们还要到上下文中取
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
//生成一个HandlerExecutionChain,其中放了我们匹配上的handler和定义好的拦截器,就像我们在HandlerExecutionChain中看到的那样,它持有一个handler和一个拦截器组。
return new HandlerExecutionChain(handler, this.adaptedInterceptors);
}
我们看看具体的handler查找过程:
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//这里的HTTP Request传进来的参数进行分析,得到具体的路径信息。
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
.......//下面是根据请求信息的查找
return lookupHandler(lookupPath, request);
}
protected Object lookupHandler(String urlPath, HttpServletRequest request) {
// 如果能够直接能在SimpleUrlHandlerMapping的映射表中找到,那最好。
Object handler = this.handlerMap.get(urlPath);
if (handler == null) {
// 这里使用模式来对map中的所有handler进行匹配,调用了Jre中的Matcher类来完成匹配处理。
String bestPathMatch = null;
for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
String registeredPath = (String) it.next();
if (this.pathMatcher.match(registeredPath, urlPath) &&
(bestPathMatch == null || bestPathMatch.length() <= registeredPath.length())) {
//这里根据匹配路径找到最象的一个
handler = this.handlerMap.get(registeredPath);
bestPathMatch = registeredPath;
}
}
if (handler != null) {
exposePathWithinMapping(this.pathMatcher.extractPathWithinPattern(bestPathMatch, urlPath), request);
}
}
else {
exposePathWithinMapping(urlPath, request);
}
//
return handler;
}
我 们可以看到,总是在handlerMap这个HashMap中找,当然如果直接找到最好,如果找不到,就看看是不是能通过Match Pattern的模式找,我们一定还记得在配置HnaderMapping的时候是可以通过ANT语法进行配置的,其中的处理就在这里。
这样可以清楚地看到整个HandlerMapping的初始化过程 - 同时,我们也看到了一个具体的handler映射是怎样被存储和查找的 - 这里生成一个ExecutionChain来储存我们找到的handler和在定义bean的时候定义的Interceptors.
让我们回到DispatcherServlet,初始化完成以后,实际的对web请求是在doService()方法中处理的,我们知道DispatcherServlet只是一个普通的Servlet:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
.......
//这里把属性信息进行保存
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DispatcherServlet.class.getName())) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
try {
//这里使实际的处理入口
doDispatch(request, response);
}
finally {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
我们看到,对于请求的处理实际上是让doDispatch()来完成的 - 这个方法很长,但是过程很简单明了:
protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//这是从handlerMapping中得到的执行链
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
........
try {
//我们熟悉的ModelAndView开始出现了。
ModelAndView mv = null;
try {
processedRequest = checkMultipart(request);
// 这是我们得到handler的过程
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 这里取出执行链中的Interceptor进行前处理
if (mappedHandler.getInterceptors() != null) {
for (int i = 0; i < mappedHandler.getInterceptors().length; i++) {
HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
//在执行handler之前,用HandlerAdapter先检查一下handler的合法性:是不是按Spring的要求编写的。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 这里取出执行链中的Interceptor进行后处理
if (mappedHandler.getInterceptors() != null) {
for (int i = mappedHandler.getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
........
// Did the handler return a view to render?
//这里对视图生成进行处理
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}
.......
}
我们很清楚的看到和MVC框架紧密相关的代码,比如如何得到和http请求相对应的执行链,怎样执行执行链和怎样把模型数据展现到视图中去。
先看怎样取得Command对象,对我们来说就是Handler - 下面是getHandler的代码:
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
//在ServletContext取得执行链 - 实际上第一次得到它的时候,我们把它放在ServletContext进行了缓存。
HandlerExecutionChain handler =
(HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
if (handler != null) {
if (!cache) {
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
}
return handler;
}
//这里的迭代器迭代的时在initHandlerMapping中载入的上下文所有的HandlerMapping
Iterator it = this.handlerMappings.iterator();
while (it.hasNext()) {
HandlerMapping hm = (HandlerMapping) it.next();
.......
//这里是实际取得handler的过程,在每个HandlerMapping中建立的映射表进行检索得到请求对应的handler
handler = hm.getHandler(request);
//然后把handler存到ServletContext中去进行缓存
if (handler != null) {
if (cache) {
request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
}
return handler;
}
}
return null;
}
如果在ServletContext中可以取得handler则直接返回,实际上这个handler是缓冲了上次处理的结果 - 总要有第一次把这个handler放到ServletContext中去:
如 果在ServletContext中找不到handler,那就通过持有的handlerMapping生成一个,我们看到它会迭代当前持有的所有的 handlerMapping,因为可以定义不止一个,他们在定义的时候也可以指定顺序,直到找到第一个,然后返回。先找到一个 handlerMapping,然后通过这个handlerMapping返回一个执行链,里面包含了最终的Handler和我们定义的一连串的 Interceptor。具体的我们可以参考上面的SimpleUrlHandlerMapping的代码分析知道getHandler是怎样得到一个 HandlerExecutionChain的。
得到HandlerExecutionChain以后,我们通过HandlerAdapter对这个Handler的合法性进行判断:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
Iterator it = this.handlerAdapters.iterator();
while (it.hasNext()) {
//同样对持有的所有adapter进行匹配
HandlerAdapter ha = (HandlerAdapter) it.next();
if (ha.supports(handler)) {
return ha;
}
}
........
}
通过判断,我们知道这个handler是不是一个Controller接口的实现,比如对于具体的HandlerAdapter - SimpleControllerHandlerAdapter:
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
.......
}
简单的判断一下handler是不是实现了Controller接口。这也体现了一种对配置文件进行验证的机制。
让我们再回到DispatcherServlet看到代码:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这个就是对handle的具体调用!相当于Command模式里的Command.execute();理所当然的返回一个ModelAndView,下面就是一个对View进行处理的过程:
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}
调用的是render方法:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
throws Exception {response.setLocale(locale);
View view = null;
//这里把默认的视图放到ModelAndView中去。
if (!mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
if (mv.isReference()) {
// 这里对视图名字进行解析
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
.......
}
else {
// 有可能在ModelAndView里已经直接包含了View对象,那我们就直接使用。
view = mv.getView();
........
}
//得到具体的View对象以后,我们用它来生成视图。
view.render(mv.getModelInternal(), request, response);
}
从整个过程我们看到先在ModelAndView中寻找视图的逻辑名,如果找不到那就使用缺省的视图,如果能够找到视图的名字,那就对他进行解析得到 实际的需要使用的视图对象。还有一种可能就是在ModelAndView中已经包含了实际的视图对象,这个视图对象是可以直接使用的。
不管怎样,得到一个视图对象以后,通过调用视图对象的render来完成数据的显示过程,我们可以看看具体的JstlView是怎样实现的,我们在JstlView的抽象父类 AbstractView中找到render方法:
public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
......
// 这里把所有的相关信息都收集到一个Map里
Map mergedModel = new HashMap(this.staticAttributes.size() + (model != null ? model.size() : 0));
mergedModel.putAll(this.staticAttributes);
if (model != null) {
mergedModel.putAll(model);
}
// Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, mergedModel));
}
//这是实际的展现模型数据到视图的调用。
renderMergedOutputModel(mergedModel, request, response);
}
注 解写的很清楚了,先把所有的数据模型进行整合放到一个Map - mergedModel里,然后调用renderMergedOutputModel();这个renderMergedOutputModel是一个模 板方法,他的实现在InternalResourceView也就是JstlView的父类:
protected void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// 这里得到InternalResource定义的内部资源路径。
String dispatcherPath = prepareForRendering(request, response);
//这里把请求转发到前面得到的内部资源路径中去。
RequestDispatcher rd = request.getRequestDispatcher(dispatcherPath);
if (rd == null) {
throw new ServletException(
"Could not get RequestDispatcher for [" + getUrl() + "]: check that this file exists within your WAR");
}
.......
}
首 先对模型数据进行处理,exposeModelAsRequestAttributes是在AbstractView中实现的,这个方法把 ModelAndView中的模型数据和其他request数据统统放到ServletContext当中去,这样整个模型数据就通过 ServletContext暴露并得到共享使用了:
protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) throws Exception {
Iterator it = model.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
..........
String modelName = (String) entry.getKey();
Object modelValue = entry.getValue();
if (modelValue != null) {
request.setAttribute(modelName, modelValue);
...........
}
else {
request.removeAttribute(modelName);
.......
}
}
}
让我们回到数据处理部分的exposeHelper();这是一个模板方法,其实现在JstlView中实现:
public class JstlView extends InternalResourceView {
private MessageSource jstlAwareMessageSource;
protected void initApplicationContext() {
super.initApplicationContext();
this.jstlAwareMessageSource =
JstlUtils.getJstlAwareMessageSource(getServletContext(), getApplicationContext());
}
protected void exposeHelpers(HttpServletRequest request) throws Exception {
JstlUtils.exposeLocalizationContext(request, this.jstlAwareMessageSource);
}
}
在JstlUtils中包含了对于其他而言jstl特殊的数据处理和设置。
过程是不是很长?我们现在在哪里了?呵呵,我们刚刚完成的事MVC中View的render,对于InternalResourceView的render过程比较简单只是完成一个资源的重定向处理。需要做的就是得到实际view的internalResource路径,然后转发到那个资源中去。怎样得到资源的路径呢通过调用:
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
throws Exception {
return getUrl();
}
那这个url在哪里生成呢?我们在View相关的代码中没有找到,实际上,他在ViewRosolve的时候就生成了,在UrlBasedViewResolver中:
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
return view;
}
这里是生成View的地方,自然也把生成的url和其他一些和view相关的属性也配置好了。
那这个ViewResolve是什么时候被调用的呢?哈哈,我们这样又要回到DispatcherServlet中去看看究竟,在DispatcherServlet中:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
throws Exception {
........
View view = null;
// 这里设置视图名为默认的名字
if (!mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
if (mv.isReference()) {
//这里对视图名进行解析,在解析的过程中根据需要生成实际需要的视图对象。
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
..........
}
......
}
下面是对视图名进行解析的具体过程:
protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request)
throws Exception {
//我们有可能不止一个视图解析器
for (Iterator it = this.viewResolvers.iterator(); it.hasNext();) {
ViewResolver viewResolver = (ViewResolver) it.next();
//这里是视图解析器进行解析并生成视图的过程。
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
这 里调用具体的ViewResolver对视图的名字进行解析 - 除了单纯的解析之外,它还根据我们的要求生成了我们实际需要的视图对象。具体的viewResolver在bean定义文件中进行定义同时在initViewResolver()方法中被初始化到viewResolver变量中,我们看看具体的 InternalResourceViewResolver是怎样对视图名进行处理的并生成V视图对象的:对resolveViewName的调用模板在AbstractCachingViewResolver中,
public View resolveViewName(String viewName, Locale locale) throws Exception {
//如果没有打开缓存设置,那创建需要的视图
if (!isCache()) {
logger.warn("View caching is SWITCHED OFF -- DEVELOPMENT SETTING ONLY: This can severely impair performance");
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
// No synchronization, as we can live with occasional double caching.
synchronized (this.viewCache) {
//这里查找缓存里的视图对象
View view = (View) this.viewCache.get(cacheKey);
if (view == null) {
//如果在缓存中没有找到,创建一个并把创建的放到缓存中去
view = createView(viewName, locale);
this.viewCache.put(cacheKey, view);
........
}
return view;
}
}
}
关于这些createView(),loadView(),buildView()的关系,我们看看Eclipse里的call hiearchy
然后我们回到view.render中完成数据的最终对httpResponse的写入,比如在AbstractExcelView中的实现:
protected final void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
.........
// response.setContentLength(workbook.getBytes().length);
response.setContentType(getContentType());
ServletOutputStream out = response.getOutputStream();
workbook.write(out);
out.flush();
}
这样就和我们前面的分析一致起来了:DispatcherServlet在解析视图名的时候就根据要求生成了视图对象,包括在InternalResourceView中需要使用的url和其他各种和HTTP response相关的属性都会写保持在生成的视图对象中,然后就直接调用视图对象的render来完成数据的展示。
这就是整个Spring Web MVC框架的大致流程,整个MVC流程由DispatcherServlet来控制。MVC的关键过程包括:
配置到handler的映射关系和怎样根据请求参数得到对应的handler,在Spring中,这是由handlerMapping通过执行链来完成的,而具体的映射关系我们在bean定义文件中定义并在HandlerMapping载入上下文的时候就被配置好了。然后DispatcherServlet调用HandlerMapping来得到对应的执行链,最后通过视图来展现模型数据,但我们要注意的是视图对象是在解析视图名的时候生成配置好的。这些作为核心类的HanderMapping,ViewResolver,View,Handler的紧密协作实现了MVC的功能。
发表者 jiwenke 位置在: 上午1:39 0 评论 指向此文章的链接
2007年6月5日 星期二
下面我们看看Spring JDBC相关的实现,
在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,也许多类似JdbcTemplate的Template,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,一般而言这种Template中都是通过回调函数CallBack类的使用来重新对需要客户订制的行为进行定制,比如使用客户想要用的SQL语句(Spring又不是神仙,它只是做好模板来减少用户程序的工作量,至于具体要做什么要查询删除什么,那还得劳您大驾),一般来说回调函数的用法采用一下这种一个匿名类的方式,比如:
JdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.execute(new CallBack(){
public CallbackInterface(){
......
//用户定义的代码或者说Spring替我们实现的代码
}
}
实际在Template的过程中,用户定义的代码就是模板代码的实现,在模板中嵌入客户代码把模板客户化事项客户程序要求的功能。下面让我们具体看看在JdbcTemplate中的代码是怎样具体完成自己的使命的,我们举出JdbcTemplate.execute()为例,这个方法是在JdbcTemplate中被其他方法调用的基本方法之一来执行基本的SQL语句:
public Object execute(ConnectionCallback action) throws DataAccessException {
//这里得到数据库联接
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
//有些特殊的数据库需要我们使用特别的方法取得datasource
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
//这里调用的是传递进来的匿名类的方法,也就是用户程序需要实现CallBack接口的地方。
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
//如果捕捉到数据库异常,把数据库联接释放,同时抛出一个经过Spring转换过的Spring数据库异常,
//我们知道,Spring做了一个有意义的工作是把这些数据库异常统一到自己的异常体系里了。
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
对于JdbcTemplate中给出的其他方法,比如query,update,execute,我们看看query的基本处理:
public Object query(
PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
throws DataAccessException {
..........
//这里调用了我们上面看到的execute()基本方法;这里基本上是Spring为我们代劳
return execute(psc, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
//这里执行的SQL查询
rs = ps.executeQuery();
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
//返回需要的记录集合
return rse.extractData(rsToUse);
}
finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
其中同过辅助类DataSourceUtils来对数据库连接进行管理,比如打开和关闭等操作。
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//把对数据库连接放到事务管理里面进行管理
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
那我们实际的DataSource对象是怎样得到的?我们对JdbcTemplate配置DataSource,它是在JdbcTemplate的父类中被定义的:
public abstract class JdbcAccessor implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
/** Used to obtain connections throughout the lifecycle of this object */
private DataSource dataSource;
/** Helper to translate SQL exceptions to DataAccessExceptions */
private SQLExceptionTranslator exceptionTranslator;
private boolean lazyInit = true;
........
}
而对于DataSource, 我们可以通过定义Apache Jakarta Commons DBCP或者C3P0提供的DataSource实现来完成,然后只要让JdbcTemplate保持对它的引用就可以直接使用了。 JdbcTemplate提供了简单查询和更新功能,但是可能需要更高层次的抽象,以及更面向对象的方法来访问数据库。这种功能是由 org.springframework.jdbc.object来提供的,包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure类,这些类都是Spring JDBC应用程序的主要类,使用这些类,用户需要为他们配置好一个JdbcTemplate,因为他们在内部使用JdbcTemplate用于数据库访 问。
比如说我们使用MappingSqlQuery来将表数据直接映射到一个对象集合:
1.我们需要建立DataSource和sql语句并建立持有这些对象的MappingSqlQuery对象
2.然后我们需要定义传递的SqlParameter,具体的实现我们在MappingSqlQuery的父类RdbmsOperation中可以找到:
public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
if (isCompiled()) {
throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
}
this.declaredParameters.add(param);
}
而这个declareParameters定义的是一个:
/** List of SqlParameter objects */
private List declaredParameters = new LinkedList();
在以后compile的过程中我们会使用到他。
3.然后实现MappingSqlQuery的mapRow接口,将具体的ResultSet数据生成我们需要的对象,这是我们迭代使用的方法。1,2,3步实际上为我们定义好了一个操作模板。
4.在应用程序,我们直接调用execute()方法得到我们需要的对象列表,列表中的每一个对象的数据来自于执行SQL语句得到记录集的每一条记录,事实上执行的方法在父类SqlQuery中:
public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException {
validateNamedParameters(paramMap);
Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap);
RowMapper rowMapper = newRowMapper(parameters, context);
String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql(), new MapSqlParameterSource(paramMap));
//我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数据库的查询操作,所以我们说JdbcTemplate是基本的操作类。
return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
}
在这里我 们可以看到template模式的精彩应用和对JdbcTemplate的灵活使用。通过使用它,我们免去了手工迭代ResultSet并将其中的数据转 化为对象列表的重复过程。在这里我们只需要定义SQL语句和SqlParameter - 如果需要的话,往往SQL语句就常常能够满足我们的要求了。
Spring还为其他数据库操作提供了许多服务,比如使用SqlUpdate插入和更新数据库,使用UpdatableSqlQuery更新ResultSet,生成主键,调用存储过程等。
书中还给出了对BLOB数据和CLOB数据进行数据库操作的例子:
对BLOB数据的操作通过LobHander来完成,通过调用JdbcTemplate和RDBMS都可以进行操作:
在JdbcTemplate中,具体的调用可以参考书中的例子 - 是通过以下调用起作用的:
public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action);
}
然后通过对实现PreparedStatementCallback接口的AbstractLobCreatingPreparedStatementCallback的回调函数来完成:
public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
LobCreator lobCreator = this.lobHandler.getLobCreator();
try {
setValues(ps, lobCreator);
return new Integer(ps.executeUpdate());
}
finally {
lobCreator.close();
}
}
protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)
throws SQLException, DataAccessException;
而我们注意到setValues()是一个需要实现的抽象方法,应用程序通过实现setValues来定义自己的操作 - 在setValues中调用lobCreator.setBlobAsBinaryStrem()。让我们看看具体的BLOB操作在LobCreator是怎样完 成的,在DefaultLobCreator中的实现:
public void setBlobAsBinaryStream(
PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
throws SQLException {
//通过JDBC来完成对BLOB数据的操作,对Oracle,Spring提供了OracleLobHandler来支持BLOB操作。
ps.setBinaryStream(paramIndex, binaryStream, contentLength);
if (logger.isDebugEnabled()) {
logger.debug(binaryStream != null ? "Set binary stream for BLOB with length " + contentLength :
"Set BLOB to null");
}
}
书中还提到关于execute和update方法之间的区别,update方法返回的是受影响的记录数目的 一个计数,并且如果传入参数的话,使用的是java.sql.PreparedStatement,而execute方法总是使用 java.sql.Statement,不接受参数,而且他不返回受影响记录的计数,更适合于创建和丢弃表的语句,而update方法更适合于插入,更新 和删除操作,这也是我们在使用时需要注意的。
发表者 jiwenke 位置在: 上午12:19 0 评论 指向此文章的链接
2007年6月3日 星期日
上面我们分析了IOC容器本身的实现,下面我们看看在典型的web环境中,Spring IOC容器是怎样起作用的。
对于一个Spring激活的J2EE web应用程序,可以通过使用Spring代码声明式的指定在web应用程序启动时载入应用程序上下文 (WebApplicationContext),Spring的ContextLoader是提供这样性能的类,我们可以使用 ContextLoaderServlet或者ContextLoaderListener的启动时载入的Servlet来实例化Spring IOC容器 - 为什么会有两个不同的类来装载它呢,这是因为它们的使用需要区别不同的Servlet容器支持的Serlvet版本。但不管是ContextLoaderSevlet还是 ContextLoaderListener都使用ContextLoader来完成实际的WebApplicationContext的初始化工作。这个ContextLoder就像是Spring Web应用程序在Web容器中的booter。当然这些Servlet的具体使用我们都要借助web容器中的部署描述符来进行相关的定义。
下面我们使用ContextLoaderListener作为载入器作一个详细的分析,首先从中得到ServletContext,然后可以读到配置好的web.xml的中的各个属性值,包括bean配置文件的 位置等,然后ContextLoder实例化WebApplicationContext完成其载入。当 ApplicationContext载入后,他被绑定到web应用程序的ServletContext上。任何需要访问该ApplicationContext的应用程序代码都可以从 WebApplicationContextUtils类通过调用静态方法来得到它:
WebApplicationContext getWebApplicationContext(ServletContext sc)
以Tomcat作为Servlet容器为例,下面是具体的步骤:
1.Tomcat启动时需要从web.xml中读取启动参数,在web.xml中我们需要对ContextLoaderListener进行配置,对于在web应用启动入口是在ContextLoaderListener中的初始化部分;从Spring MVC上看,实际上在web容器中维护了一系列的IOC容器,其中在ContextLoader中载入的IOC容器作为根上下文而存在于ServletContext中。
//这里对根上下文进行初始化。
public void contextInitialized(ServletContextEvent event) {
//这里创建需要的ContextLoader
this.contextLoader = createContextLoader(
标签:
原文地址:http://www.cnblogs.com/kangyanxiang/p/4582483.html