Spring源代码解析


jiwenke的博客文章 - Spring源代码解析 作者: jiwenke http://jiwenke.javaeye.com 我的博客文章精选 -Spring源代码解析 http://www.javaeye.com - 做最棒的软件开发交流社区 第 1 / 90 页 本书由JavaEye提供的电子书DIY功能自动生成于 2008-11-19 目 录 1. 默认类别 1.1 Spring源代码解析(一):IOC容器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.3 Spring源代码解析(三):Spring JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 1.4 Spring源代码解析(四):Spring MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 1.5 Spring源代码解析(五):Spring AOP获取Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 1.6 Spring源代码解析(六):Spring声明式事务处理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 http://jiwenke.javaeye.com 第 2 / 90 页 1.1 Spring源代码解析(一):IOC容器 发表时间: 2007-06-03 在认真学习Rod.Johnson的三部曲之一:<>,顺便也看了看源代码想知道个 究竟,抛砖引玉,有兴趣的同志一起讨论研究吧! 以下内容引自博客:http://jiwenke-spring.blogspot.com/,欢迎指导:) 在Spring中,IOC容器的重要地位我们就不多说了,对于Spring的使用者而言,IOC容器实际上是什么呢?我们可以说BeanFactory就是 我们看到的IoC容器,当然了Spring为我们准备了许多种IoC容器来使用,这样可以方便我们从不同的层面,不同的资源位置,不同的形 式的定义信息来建立我们需要的IoC容器。 在Spring中,最基本的IOC容器接口是BeanFactory - 这个接口为具体的IOC容器的实现作了最基本的功能规定 - 不管怎么着,作为IOC容 器,这些接口你必须要满足应用程序的最基本要求: public interface BeanFactory { //这里是对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象, //如果需要得到工厂本身,需要转义 String FACTORY_BEAN_PREFIX = "&"; //这里根据bean的名字,在IOC容器中得到bean实例,这个IOC容器就是一个大的抽象工厂。 Object getBean(String name) throws BeansException; //这里根据bean的名字和Class类型来得到bean实例,和上面的方法不同在于它会抛出异常:如果根据名字取得的bean实例的Class类型和需要的不同的话。 Object getBean(String name, Class requiredType) throws BeansException; //这里提供对bean的检索,看看是否在IOC容器有这个名字的bean boolean containsBean(String name); //这里根据bean名字得到bean实例,并同时判断这个bean是不是单件 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; //这里对得到bean实例的Class类型 Class getType(String name) throws NoSuchBeanDefinitionException; //这里得到bean的别名,如果根据别名检索,那么其原名也会被检索出来 String[] getAliases(String name); } 在BeanFactory里只对IOC容器的基本行为作了定义,根本不关心你的bean是怎样定义怎样加载的 - 就像我们只关心从这个工厂里我们 得到到什么产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心这些。如果要关心工厂是怎样产生对象的,应用程序 需要使用具体的IOC容器实现- 当然你可以自己根据这个BeanFactory来实现自己的IOC容器,但这个没有必要,因为Spring已经为我们 http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 3 / 90 页 准备好了一系列工厂来让我们使用。比如XmlBeanFactory就是针对最基础的BeanFactory的IOC容器的实现 - 这个实现使用xml来定义IOC 容器中的bean。 Spring提供了一个BeanFactory的基本实现,XmlBeanFactory同样的通过使用模板模式来得到对IOC容器的抽象- AbstractBeanFactory,DefaultListableBeanFactory这些抽象类为其提供模板服务。其中通过resource 接口来抽象bean定义数据,对Xml 定义文件的解析通过委托给XmlBeanDefinitionReader来完成。下面我们根据书上的例子,简单的演示IOC容器的创建过程: ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res); 这些代码演示了以下几个步骤: 1. 创建IOC配置文件的抽象资源 2. 创建一个BeanFactory 3. 把读取配置信息的BeanDefinitionReader,这里是XmlBeanDefinitionReader配置给BeanFactory 4. 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成,这样完成整个载入bean定义的过程。我 们的IoC容器就建立起来了。在BeanFactory的源代码中我们可以看到: public class XmlBeanFactory extends DefaultListableBeanFactory { //这里为容器定义了一个默认使用的bean定义读取器 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } //在初始化函数中使用读取器来对资源进行读取,得到bean定义信息。 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } 我们在后面会看到读取器读取资源和注册bean定义信息的整个过程,基本上是和上下文的处理是一样的,从这里我们可以看到上下文 和 XmlBeanFactory这两种IOC容器的区别,BeanFactory往往不具备对资源定义的能力,而上下文可以自己完成资源定义,从这个角度 上看上下文更好用一些。 仔细分析Spring BeanFactory的结构,我们来看看在BeanFactory基础上扩展出的ApplicationContext - 我们最常使用的上下文。除了具备 BeanFactory的全部能力,上下文为应用程序又增添了许多便利: * 可以支持不同的信息源,我们看到ApplicationContext扩展了MessageSource * 访问资源 , 体现在对ResourceLoader和Resource的支持上面,这样我们可以从不同地方得到bean定义资源 * 支持应用事件,继承了接口ApplicationEventPublisher,这样在上下文中引入了事件机制而BeanFactory是没有的。 ApplicationContext允许上下文嵌套 - 通过保持父上下文可以维持一个上下文体系 - 这个体系我们在以后对Web容器中的上下文环境的 http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 4 / 90 页 分析中可以清楚地看到。对于bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为 不同的Spring应用提供了一个共享的bean定义环境。这个我们在分析Web容器中的上下文环境时也能看到。 ApplicationContext提供IoC容器的主要接口,在其体系中有许多抽象子类比如AbstractApplicationContext为具体的BeanFactory的实现, 比如FileSystemXmlApplicationContext和 ClassPathXmlApplicationContext提供上下文的模板,使得他们只需要关心具体的资源定位问 题。当应用程序代码实例化 FileSystemXmlApplicationContext的时候,得到IoC容器的一种具体表现 - ApplicationContext,从而应用程 序通过ApplicationContext来管理对bean的操作。 BeanFactory 是一个接口,在实际应用中我们一般使用ApplicationContext来使用IOC容器,它们也是IOC容器展现给应用开发者的使用 接口。对应用程序开发者来说,可以认为BeanFactory和ApplicationFactory在不同的使用层面上代表了SPRING提供的IOC容器服务。 下面我们具体看看通过FileSystemXmlApplicationContext是怎样建立起IOC容器的, 显而易见我们可以通过new来得到IoC容器: ApplicationContext = new FileSystemXmlApplicationContext(xmlPath); 调用的是它初始化代码: public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); this.configLocations = configLocations; if (refresh) {  //这里是IoC容器的初始化过程,其初始化过程的大致步骤由AbstractApplicationContext来定义 refresh(); } } refresh的模板在AbstractApplicationContext: public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { synchronized (this.activeMonitor) { this.active = true; } // 这里需要子类来协助完成资源位置定义,bean载入和向IOC容器注册的过程 refreshBeanFactory(); ............ } 这个方法包含了整个BeanFactory初始化的过程,对于特定的FileSystemXmlBeanFactory,我们看到定位资源位置由refreshBeanFactory() 来实现: 在AbstractXmlApplicationContext中定义了对资源的读取过程,默认由XmlBeanDefinitionReader来读取: http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 5 / 90 页 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { // 这里使用XMLBeanDefinitionReader来载入bean定义信息的XML文件 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //这里配置reader的环境,其中ResourceLoader是我们用来定位bean定义信息资源位置的 ///因为上下文本身实现了ResourceLoader接口,所以可以直接把上下文作为ResourceLoader传递给XmlBeanDefinitionReader beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); initBeanDefinitionReader(beanDefinitionReader); //这里转到定义好的XmlBeanDefinitionReader中对载入bean信息进行处理 loadBeanDefinitions(beanDefinitionReader); } 转到beanDefinitionReader中进行处理: protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { //调用XmlBeanDefinitionReader来载入bean定义信息。 reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } } 而在作为其抽象父类的AbstractBeanDefinitionReader中来定义载入过程: public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { //这里得到当前定义的ResourceLoader,默认的我们使用DefaultResourceLoader ResourceLoader resourceLoader = getResourceLoader(); .........//如果没有找到我们需要的ResourceLoader,直接抛出异常 if (resourceLoader instanceof ResourcePatternResolver) { // 这里处理我们在定义位置时使用的各种pattern,需要ResourcePatternResolver来完成 try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); return loadCount; } http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 6 / 90 页 ........ } else { // 这里通过ResourceLoader来完成位置定位 Resource resource = resourceLoader.getResource(location); // 这里已经把一个位置定义转化为Resource接口,可以供XmlBeanDefinitionReader来使用了 int loadCount = loadBeanDefinitions(resource); return loadCount; } } 当我们通过ResourceLoader来载入资源,别忘了了我们的GenericApplicationContext也实现了ResourceLoader接口: public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { public Resource getResource(String location) { //这里调用当前的loader也就是DefaultResourceLoader来完成载入 if (this.resourceLoader != null) { return this.resourceLoader.getResource(location); } return super.getResource(location); } ....... } 而我们的FileSystemXmlApplicationContext就是一个DefaultResourceLoader - GenericApplicationContext()通过DefaultResourceLoader: public Resource getResource(String location) { //如果是类路径的方式,那需要使用ClassPathResource来得到bean文件的资源对象 if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // 如果是URL方式,使用UrlResource作为bean文件的资源对象 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // 如果都不是,那我们只能委托给子类由子类来决定使用什么样的资源对象了 return getResourceByPath(location); } http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 7 / 90 页 } } 我们的FileSystemXmlApplicationContext本身就是是DefaultResourceLoader的实现类,他实现了以下的接口: protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } //这里使用文件系统资源对象来定义bean文件 return new FileSystemResource(path); } 这样代码就回到了FileSystemXmlApplicationContext中来,他提供了FileSystemResource来完成从文件系统得到配置文件的资源定义。 这样,就可以从文件系统路径上对IOC配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方加载,在Spring中我们看到它提供的 各种资源抽象,比如ClassPathResource, URLResource,FileSystemResource等来供我们使用。上面我们看到的是定位Resource的一个 过程,而这只是加载过程的一部分 - 我们回到AbstractBeanDefinitionReaderz中的loadDefinitions(resource)来看看得到代表bean文件的 资源定义以后的载入过程,默认的我们使用XmlBeanDefinitionReader: public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { ....... try { //这里通过Resource得到InputStream的IO流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { //从InputStream中得到XML的解析源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //这里是具体的解析和注册过程 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { //关闭从Resource中得到的IO流 inputStream.close(); } } ......... } http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 8 / 90 页 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource); //通过解析得到DOM,然后完成bean在IOC容器中的注册 Document doc = this.documentLoader.loadDocument( inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware); return registerBeanDefinitions(doc, resource); } ....... } 我们看到先把定义文件解析为DOM对象,然后进行具体的注册过程: public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 这里定义解析器,使用XmlBeanDefinitionParser来解析xml方式的bean定义文件 - 现在的版本不用这个解析器了,使用的是XmlBeanDefinitionReader if (this.parserClass != null) { XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); return parser.registerBeanDefinitions(this, doc, resource); } // 具体的注册过程,首先得到XmlBeanDefinitionReader,来处理xml的bean定义文件 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getBeanFactory().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getBeanFactory().getBeanDefinitionCount() - countBefore; } 具体的在BeanDefinitionDocumentReader中完成对,下面是一个简要的注册过程来完成bean定义文件的解析和IOC容器中bean的初始 化 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); preProcessXml(root); parseBeanDefinitions(root, delegate); http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 9 / 90 页 postProcessXml(root); } protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root.getNamespaceURI())) { //这里得到xml文件的子节点,比如各个bean节点 NodeList nl = root.getChildNodes(); //这里对每个节点进行分析处理 for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; String namespaceUri = ele.getNamespaceURI(); if (delegate.isDefaultNamespace(namespaceUri)) { //这里是解析过程的调用,对缺省的元素进行分析比如bean元素 parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } } private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //这里对元素Import进行处理 if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); getReaderContext().getReader().getBeanFactory().registerAlias(name, alias); getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } //这里对我们最熟悉的bean元素进行处理 else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) { //委托给BeanDefinitionParserDelegate来完成对bean元素的处理,这个类包含了具体的bean解析的过程。 http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 10 / 90 页 // 把解析bean文件得到的信息放到BeanDefinition里,他是bean信息的主要载体,也是IOC容器的管理对象。 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); // 这里是向IOC容器注册,实际上是放到IOC容器的一个map里 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 这里向IOC容器发送事件,表示解析和注册完成。 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } } } 我们看到在parseBeanDefinition中对具体bean元素的解析式交给BeanDefinitionParserDelegate来完成的,下面我们看看解析完的bean 是怎样在IOC容器中注册的: 在BeanDefinitionReaderUtils调用的是: public static void registerBeanDefinition( BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException { // 这里得到需要注册bean的名字; String beanName = bdHolder.getBeanName(); //这是调用IOC来注册的bean的过程,需要得到BeanDefinition beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()); // 别名也是可以通过IOC容器和bean联系起来的进行注册 String[] aliases = bdHolder.getAliases(); if (aliases != null) { for (int i = 0; i < aliases.length; i++) { beanFactory.registerAlias(beanName, aliases[i]); } } } 我们看看XmlBeanFactory中的注册实现: //--------------------------------------------------------------------- // 这里是IOC容器对BeanDefinitionRegistry接口的实现 //--------------------------------------------------------------------- public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 11 / 90 页 throws BeanDefinitionStoreException { .....//这里省略了对BeanDefinition的验证过程 //先看看在容器里是不是已经有了同名的bean,如果有抛出异常。 Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!this.allowBeanDefinitionOverriding) { ........... } else { //把bean的名字加到IOC容器中去 this.beanDefinitionNames.add(beanName); } //这里把bean的名字和Bean定义联系起来放到一个HashMap中去,IOC容器通过这个Map来维护容器里的Bean定义信息。 this.beanDefinitionMap.put(beanName, beanDefinition); removeSingleton(beanName); } 这样就完成了Bean定义在IOC容器中的注册,就可被IOC容器进行管理和使用了。 从上面的代码来看,我们总结一下IOC容器初始化的基本步骤: * 初始化的入口在容器实现中的refresh()调用来完成 * 对bean 定义载入IOC容器使用的方法是loadBeanDefinition,其中的大致过程如下:通过ResourceLoader来完成资源文件位置的定 位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了ResourceLoader的实现,可以从类路径,文件系统, URL等方式来 定为资源位置。如果是XmlBeanFactory作为IOC容器,那么需要为它指定bean定义的资源,也就是说bean定义文件时通过抽象成 Resource来被IOC容器处理的,容器通过BeanDefinitionReader来完成定义信息的解析和Bean信息的注册,往往使用的是 XmlBeanDefinitionReader来解析bean的xml定义文件 - 实际的处理过程是委托给BeanDefinitionParserDelegate来完成的,从而得到 bean的定义信息,这些信息在Spring中使用BeanDefinition对象来表示 - 这个名字可以让我们想到 loadBeanDefinition,RegisterBeanDefinition这些相关的方法 - 他们都是为处理BeanDefinitin服务的,IoC容器解析得到BeanDefinition以 后,需要把它在IOC容器中注册,这由IOC实现 BeanDefinitionRegistry接口来实现。注册过程就是在IOC容器内部维护的一个HashMap 来保存得到的 BeanDefinition的过程。这个HashMap是IoC容器持有bean信息的场所,以后对bean的操作都是围绕这个HashMap来实现 的。 * 然后我们就可以通过BeanFactory和ApplicationContext来享受到Spring IOC的服务了. 在使用IOC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确IoC风格编写的应用程序代码完全不用关心如何到达工厂,因 为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义 的地方,以及代码将实际需要访问工厂的地方。 Spring本身提供了对声明式载入web应用程序用法的应用程序上下文,并将其存储在 ServletContext中的框架实现。具体可以参见以后的文章。 在使用Spring IOC容器的时候我们还需要区别两个概念: Beanfactory 和Factory bean,其中BeanFactory指的是IOC容器的编程抽象,比如ApplicationContext, XmlBeanFactory等,这些都是IOC 容器的具体表现,需要使用什么样的容器由客户决定但Spring为我们提供了丰富的选择。而 FactoryBean只是一个可以在IOC容器中被 管理的一个bean,是对各种处理过程和资源使用的抽象,Factory bean在需要时产生另一个对象,而不返回FactoryBean本省,我们可以把 它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的 Factory bean都实现特殊的 http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 12 / 90 页 org.springframework.beans.factory.FactoryBean接口,当使用容器中factory bean的时候,该容器不会返回factory bean本身,而是返回 其生成的对象。Spring包括了大部分的通用资源和服务访问抽象的Factory bean的实现,其中包括: 对JNDI查询的处理,对代理对象的处理,对事务性代理的处理,对RMI代理的处理等,这些我们都可以看成是具体的工厂,看成是 SPRING为我们建立好的工厂。也就是说Spring通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工 重复的工作,我们要使用时只需要在IOC容器里配置好就能很方便的使用了。 现在我们来看看在Spring的事件机制,Spring中有3个标准事件,ContextRefreshEvent, ContextCloseEvent,RequestHandledEvent他们 通过ApplicationEvent接口,同样的如果需要自定义时间也只需要实现ApplicationEvent接口,参照ContextCloseEvent的实现可以定制自 己的事件实现: public class ContextClosedEvent extends ApplicationEvent { public ContextClosedEvent(ApplicationContext source) { super(source); } public ApplicationContext getApplicationContext() { return (ApplicationContext) getSource(); } } 可以通过显现ApplicationEventPublishAware接口,将事件发布器耦合到ApplicationContext这样可以使用 ApplicationContext框架来传递 和消费消息,然后在ApplicationContext中配置好bean就可以了,在消费消息的过程中,接受者通过实现ApplicationListener接收消息。 比如可以直接使用Spring的ScheduleTimerTask和TimerFactoryBean作为定时器定时产生消息,具体可以参见《Spring框架高级编 程》。 TimerFactoryBean是一个工厂bean,对其中的ScheduleTimerTask进行处理后输出,参考ScheduleTimerTask的实现发现它最后调用的 是jre的TimerTask: public void setRunnable(Runnable timerTask) { this.timerTask = new DelegatingTimerTask(timerTask); } 在书中给出了一个定时发送消息的例子,当然可以可以通过定时器作其他的动作,有两种方法: 1.定义MethodInvokingTimerTaskFactoryBean定义要执行的特定bean的特定方法,对需要做什么进行封装定义; 2.定义TimerTask类,通过extends TimerTask来得到,同时对需要做什么进行自定义 然后需要定义具体的定时器参数,通过配置ScheduledTimerTask中的参数和timerTask来完成,以下是它需要定义的具体属性, timerTask是在前面已经定义好的bean private TimerTask timerTask; private long delay = 0; private long period = 0; http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 13 / 90 页 private boolean fixedRate = false; 最后,需要在ApplicationContext中注册,需要把ScheduledTimerTask配置到FactoryBean - TimerFactoryBean,这样就由IOC容器来管理 定时器了。参照 TimerFactoryBean的属性,可以定制一组定时器。 public class TimerFactoryBean implements FactoryBean, InitializingBean, DisposableBean { protected final Log logger = LogFactory.getLog(getClass()); private ScheduledTimerTask[] scheduledTimerTasks; private boolean daemon = false; private Timer timer; ........... } 如果要发送时间我们只需要在定义好的ScheduledTimerTasks中publish定义好的事件就可以了。具体可以参考书中例子的实现,这里只 是结合FactoryBean的原理做一些解释。如果结合事件和定时器机制,我们可以很方便的实现heartbeat(看门狗),书中给出了这个例 子,这个例子实际上结合了Spring事件和定时机制的使用两个方面的知识 - 当然了还有IOC容器的知识(任何Spring应用我想都逃不掉 IOC的魔爪:) http://jiwenke.javaeye.com 1.1 Spring源代码解析(一):IOC容器 第 14 / 90 页 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 发表时间: 2007-06-04 以下引用自博客:http://jiwenke-spring.blogspot.com/ 上面我们分析了IOC容器本身的实现,下面我们看看在典型的web环境中,Spring IOC容器是怎样被载入和起作用的。 简单的说,在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要 建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的 基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。在web容器中启 动Spring应用程序就是一个建立这个上下文体系的过程。Spring为web应用提供了上下文的扩展接口 WebApplicationContext: public interface WebApplicationContext extends ApplicationContext { //这里定义的常量用于在ServletContext中存取根上下文 String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; ...... //对WebApplicationContext来说,需要得到Web容器的ServletContext ServletContext getServletContext(); } 而一般的启动过程,Spring会使用一个默认的实现,XmlWebApplicationContext - 这个上下文实现作为在web容器中的根上下文容器被 建立起来,具体的建立过程在下面我们会详细分析。 public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { /** 这是和web部署相关的位置信息,用来作为默认的根上下文bean定义信息的存放位置*/ public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; //我们又看到了熟悉的loadBeanDefinition,就像我们前面对IOC容器的分析中一样,这个加载工程在容器的refresh()的时候启动。 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { //对于XmlWebApplicationContext,当然使用的是XmlBeanDefinitionReader来对bean定义信息来进行解析 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); } protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) { http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 15 / 90 页 } //使用XmlBeanDefinitionReader来读入bean定义信息 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { String[] configLocations = getConfigLocations(); if (configLocations != null) { for (int i = 0; i < configLocations.length; i++) { reader.loadBeanDefinitions(configLocations[i]); } } } //这里取得bean定义信息位置,默认的地方是/WEB-INF/applicationContext.xml protected String[] getDefaultConfigLocations() { if (getNamespace() != null) { return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; } else { return new String[] {DEFAULT_CONFIG_LOCATION}; } } } 对于一个Spring激活的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作为载入器作一个详细的分析,这个Servlet的监听器是根上下文被载入的地方,也是整个 Spring web应用加载上下文的第一个地方;从加载过程我们可以看到,首先从Servlet事件中得到ServletContext,然后可以读到配置 好的在web.xml的中的各个属性值,然后ContextLoder实例化WebApplicationContext并完成其载入和初始化作为根上下文。当这个根上 下文被载入后,它被绑定到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中。 http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 16 / 90 页 //这里对根上下文进行初始化。 public void contextInitialized(ServletContextEvent event) { //这里创建需要的ContextLoader this.contextLoader = createContextLoader(); //这里使用ContextLoader对根上下文进行载入和初始化 this.contextLoader.initWebApplicationContext(event.getServletContext()); } 通过ContextLoader建立起根上下文的过程,我们可以在ContextLoader中看到: public WebApplicationContext initWebApplicationContext(ServletContext servletContext) throws IllegalStateException, BeansException { //这里先看看是不是已经在ServletContext中存在上下文,如果有说明前面已经被载入过,或者是配置文件有错误。 if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { //直接抛出异常 ......... } ............... try { // 这里载入根上下文的父上下文 ApplicationContext parent = loadParentContext(servletContext); //这里创建根上下文作为整个应用的上下文同时把它存到ServletContext中去,注意这里使用的ServletContext的属性值是 //ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后的应用都是根据这个属性值来取得根上下文的 - 往往作为自己上下文的父上下文 this.context = createWebApplicationContext(servletContext, parent); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); .......... return this.context; } ............ } 建立根上下文的父上下文使用的是下面的代码,取决于在web.xml中定义的参数:locatorFactorySelector,这是一个可选参数: protected ApplicationContext loadParentContext(ServletContext servletContext) throws BeansException { ApplicationContext parentContext = null; http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 17 / 90 页 String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); if (locatorFactorySelector != null) { BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); ........ //得到根上下文的父上下文的引用 this.parentContextRef = locator.useBeanFactory(parentContextKey); //这里建立得到根上下文的父上下文 parentContext = (ApplicationContext) this.parentContextRef.getFactory(); } return parentContext; } 得到根上下文的父上下文以后,就是根上下文的创建过程: protected WebApplicationContext createWebApplicationContext( ServletContext servletContext, ApplicationContext parent) throws BeansException { //这里需要确定我们载入的根WebApplication的类型,由在web.xml中配置的contextClass中配置的参数可以决定我们需要载入什么样的ApplicationContext, //如果没有使用默认的。 Class contextClass = determineContextClass(servletContext); ......... //这里就是上下文的创建过程 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); //这里保持对父上下文和ServletContext的引用到根上下文中 wac.setParent(parent); wac.setServletContext(servletContext); //这里从web.xml中取得相关的初始化参数 String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocation != null) { wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation, ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); } //这里对WebApplicationContext进行初始化,我们又看到了熟悉的refresh调用。 wac.refresh(); return wac; } http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 18 / 90 页 初始化根ApplicationContext后将其存储到SevletContext中去以后,这样就建立了一个全局的关于整个应用的上下文。这个根上下文会被 以后的DispatcherServlet初始化自己的时候作为自己ApplicationContext的父上下文。这个在对 DispatcherServlet做分析的时候我们可 以看看到。 3.完成对ContextLoaderListener的初始化以后, Tomcat开始初始化DispatchServlet,- 还记得我们在web.xml中队载入次序进行了定 义。DispatcherServlet会建立自己的ApplicationContext,同时建立这个自己的上下文的时候会从ServletContext中得到根上下文作为父上 下文,然后再对自己的上下文进行初始化,并最后存到 ServletContext中去供以后检索和使用。 可以从DispatchServlet的父类FrameworkServlet的代码中看到大致的初始化过程,整个ApplicationContext的创建过程和ContextLoder创 建的过程相类似: protected final void initServletBean() throws ServletException, BeansException { ......... try { //这里是对上下文的初始化过程。 this.webApplicationContext = initWebApplicationContext(); //在完成对上下文的初始化过程结束后,根据bean配置信息建立MVC框架的各个主要元素 initFrameworkServlet(); } ........ } 对initWebApplicationContext()调用的代码如下: protected WebApplicationContext initWebApplicationContext() throws BeansException { //这里调用WebApplicationContextUtils静态类来得到根上下文 WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //创建当前DispatcherServlet的上下文,其上下文种类使用默认的在FrameworkServlet定义好的:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; WebApplicationContext wac = createWebApplicationContext(parent); ........ if (isPublishContext()) { //把当前建立的上下文存到ServletContext中去,注意使用的属性名是和当前Servlet名相关的。 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; } 其中我们看到调用了WebApplicationContextUtils的静态方法得到根ApplicationContext: http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 19 / 90 页 public static WebApplicationContext getWebApplicationContext(ServletContext sc) { //很简单,直接从ServletContext中通过属性名得到根上下文 Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); ....... return (WebApplicationContext) attr; } 然后创建DispatcherServlet自己的WebApplicationContext: protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) throws BeansException { ....... //这里使用了BeanUtils直接得到WebApplicationContext,ContextClass是前面定义好的DEFAULT_CONTEXT_CLASS = //XmlWebApplicationContext.class; ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass()); //这里配置父上下文,就是在ContextLoader中建立的根上下文 wac.setParent(parent); //保留ServletContext的引用和相关的配置信息。 wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); //这里得到ApplicationContext配置文件的位置 if (getContextConfigLocation() != null) { wac.setConfigLocations( StringUtils.tokenizeToStringArray( getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); } //这里调用ApplicationContext的初始化过程,同样需要使用refresh() wac.refresh(); return wac; } 4. 然后就是DispatchServlet中对Spring MVC的配置过程,首先对配置文件中的定义元素进行配置 - 请注意这个时候我们的 WebApplicationContext已经建立起来了,也意味着DispatcherServlet有自己的定义资源,可以需要从web.xml中读取bean的配置信息, 通常我们会使用单独的xml文件来配置MVC中各个要素定义,这里和web容器相关的加载过程实际上已经完成了,下面的处理和普通的 Spring应用程序的编写没有什么太大的差别,我们先看看MVC的初始化过程: protected void initFrameworkServlet() throws ServletException, BeansException { initMultipartResolver(); http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 20 / 90 页 initLocaleResolver(); initThemeResolver(); initHandlerMappings(); initHandlerAdapters(); initHandlerExceptionResolvers(); initRequestToViewNameTranslator(); initViewResolvers(); } 5. 这样MVC的框架就建立起来了,DispatchServlet对接受到的HTTP Request进行分发处理由doService()完成,具体的MVC处理过程我 们在doDispatch()中完成,其中包括使用Command模式建立执行链,显示模型数据等,这些处理我们都可以在DispatcherServlet的代码 中看到: protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { ...... try { doDispatch(request, response); } ....... } 实际的请求分发由doDispatch(request,response)来完成: protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception { ....... // 这是Spring定义的执行链,里面放了映射关系对应的handler和定义的相关拦截器。 HandlerExecutionChain mappedHandler = null; ...... try { //我们熟悉的ModelAndView在这里出现了。 ModelAndView mv = null; try { processedRequest = checkMultipart(request); //这里更具request中的参数和映射关系定义决定使用的handler mappedHandler = getHandler(processedRequest, false); ...... //这里是handler的调用过程,类似于Command模式中的execute. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 21 / 90 页 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ....... //这里将模型数据通过视图进行展现 if (mv != null && !mv.wasCleared()) { render(mv, processedRequest, response); } ........ } 这样具体的MVC模型的实现就由bean配置文件里定义好的view resolver,handler这些类来实现用户代码的功能。 总结上面的过程,我们看到在web容器中,ServletContext可以持有一系列的web上下文,而在整个web上下文中存在一个根上下文来作 为其它 Servlet上下文的父上下文。这个根上下文是由ContextLoader载入并进行初始化的,对于我们的web应用, DispatcherSerlvet载 入并初始化自己的上下文,这个上下文的父上下文是根上下文,并且我们也能从ServletContext中根据 Servlet的名字来检索到我们需要 的对应于这个Servlet的上下文,但是根上下文的名字是由Spring唯一确定的。这个 DispactcherServlet建立的上下文就是我们开发 Spring MVC应用的IOC容器。 具体的web请求处理在上下文体系建立完成以后由DispactcherServlet来完成,上面对MVC的运作做了一个大致的描述,下面我们会具 体就SpringMVC的框架实现作一个详细的分析。 http://jiwenke.javaeye.com 1.2 Spring源代码解析(二):IoC容器在Web容器中的启动 第 22 / 90 页 1.3 Spring源代码解析(三):Spring JDBC 发表时间: 2007-06-05 引用自博客:http://jiwenke-spring.blogspot.com/ 下面我们看看Spring JDBC相关的实现, 在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方 法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯 用手法,一般而言这种Template中都是通过回调函数CallBack类的使用来完成功能的,客户需要在回调接口中实现自己需要的定制行 为,比如使用客户想要用的SQL语句等。不过往往Spring通过这种回调函数的实现已经为我们提供了许多现成的方法供客户使用。一般 来说回调函数的用法采用匿名类的方式来实现,比如: JdbcTemplate = new JdbcTemplate(datasource); jdbcTemplate.execute(new CallBack(){ public CallbackInterfacedoInAction(){ ...... //用户定义的代码或者说Spring替我们实现的代码 } } 在模板中嵌入的是需要客户化的代码,由Spring来作或者需要客户程序亲自动手完成。下面让我们具体看看在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数据库异常, http://jiwenke.javaeye.com 1.3 Spring源代码解析(三):Spring JDBC 第 23 / 90 页 //我们知道,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 { //这里配置SQL参数 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 { //最后关闭查询的纪录集,对数据库连接的释放在execute()中释放,就像我们在上面分析的看到那样。 JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } http://jiwenke.javaeye.com 1.3 Spring源代码解析(三):Spring JDBC 第 24 / 90 页 } }); } 辅助类DataSourceUtils来用来对数据库连接进行管理的主要工具,比如打开和关闭数据库连接等基本操作: public static Connection doGetConnection(DataSource dataSource) throws SQLException { //把对数据库连接放到事务管理里面进行管理 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(); } // 这里得到需要的数据库连接,在配置文件中定义好的。 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); } } http://jiwenke.javaeye.com 1.3 Spring源代码解析(三):Spring JDBC 第 25 / 90 页 return con; } 那我们实际的DataSource对象是怎样得到的?很清楚我们需要在上下文中进行配置:它作为JdbcTemplate父类JdbcAccessor的属性存 在: public abstract class JdbcAccessor implements InitializingBean { /** 这里是我们依赖注入数据库数据源的地方。 */ 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提供了许多简单查询和更新功能,但是如果需要更高层次的抽象,以及更面 向对象的方法来访问数据库。Spring为我们提供了org.springframework.jdbc.object包,这里面包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure等类,这些类都是Spring JDBC应用程序可以使用的主要类,但我们要注意使用这些类的时候,用户需要 为他们配置好一个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(); http://jiwenke.javaeye.com 1.3 Spring源代码解析(三):Spring JDBC 第 26 / 90 页 这个列表在以后compile的过程中会被使用。 3.然后用户程序需要实现MappingSqlQuery的mapRow接口,将具体的ResultSet数据生成我们需要的对象,这是我们迭代使用的方法。 1,2,3步实际上为我们定义好了一个迭代的基本单元作为操作模板。 4.在应用程序,我们直接调用execute()方法得到我们需要的对象列表,列表中的每一个对象的数据来自于执行SQL语句得到记录集的每 一条记录,事实上执行的execute在父类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语句就常常能够满足我们 的要求了。这是灵活使用JdbcTemplate的一个很好的例子。 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(); } } http://jiwenke.javaeye.com 1.3 Spring源代码解析(三):Spring JDBC 第 27 / 90 页 //定义的需要客户程序实现的虚函数 protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException; 而我们注意到setValues()是一个需要实现的抽象方法,应用程序通过实现setValues来定义自己的操作 - 在setValues中调用 lobCreator.setBlobAsBinaryStrem()。让我们看看具体的BLOB操作在LobCreator是怎样完成的,我们一般使用DefaultLobCreator作为 BLOB操作的驱动: public void setBlobAsBinaryStream( PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength) throws SQLException { //通过JDBC来完成对BLOB数据的操作,对Oracle,Spring提供了OracleLobHandler来支持BLOB操作。 ps.setBinaryStream(paramIndex, binaryStream, contentLength); ........ } 上面提到的是零零碎碎的Spring JDBC使用的例子,可以看到使用Spring JDBC可以帮助我们完成许多数据库的操作。Spring对数据库操 作最基本的服务是通过JdbcTeamplate和他常用的回调函数来实现的,在此之上,又提供了许多RMDB的操作来帮助我们更便利的对数 据库的数据进行操作 - 注意这里没有引入向Hibernate这样的O/R方案。对这些O/R方案的支持,Spring由其他包来完成服务。 书中还提到关于execute和update方法之间的区别,update方法返回的是受影响的记录数目的一个计数,并且如果传入参数的话,使用 的是java.sql.PreparedStatement,而execute方法总是使用 java.sql.Statement,不接受参数,而且他不返回受影响记录的计数,更适合于 创建和丢弃表的语句,而update方法更适合于插入,更新和删除操作,这也是我们在使用时需要注意的。 http://jiwenke.javaeye.com 1.3 Spring源代码解析(三):Spring JDBC 第 28 / 90 页 1.4 Spring源代码解析(四):Spring MVC 发表时间: 2007-06-07 下面我们对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. http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 29 / 90 页 } } //如果在上下文中没有定义的话,那么我们使用默认的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文件的信息是等价的,下面是具体的注册过程: http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 30 / 90 页 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); } http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 31 / 90 页 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); http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 32 / 90 页 } 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"); http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 33 / 90 页 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); http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 34 / 90 页 // 这是我们得到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); } ....... } http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 35 / 90 页 我们很清楚的看到和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的合法性进行判断: http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 36 / 90 页 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; http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 37 / 90 页 //这里把默认的视图放到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); } http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 38 / 90 页 注解写的很清楚了,先把所有的数据模型进行整合放到一个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); ....... } http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 39 / 90 页 } } 让我们回到数据处理部分的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) { http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 40 / 90 页 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; } http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 41 / 90 页 } 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(); http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 42 / 90 页 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的功能。 http://jiwenke.javaeye.com 1.4 Spring源代码解析(四):Spring MVC 第 43 / 90 页 1.5 Spring源代码解析(五):Spring AOP获取Proxy 发表时间: 2007-06-08 下面我们来看看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) { //这里通过放射得到方法的全名 String patt = method.getDeclaringClass().getName() + "." + method.getName(); for (int i = 0; i < this.patterns.length; i++) { // 这里是判断是否和方法名是否匹配的代码 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: http://jiwenke.javaeye.com 1.5 Spring源代码解析(五):Spring AOP获取Proxy 第 44 / 90 页 当我们完成额外的动作设计(advice)和额外动作插入点的设计(pointcut)以后,我们需要一个对象把他们结合起来,这就是通知器 - advisor,定义应该在哪里应用哪个通知。Advisor的实现有:DefaultPointcutAdvisor他有两个属性advice和 pointcut来让我们配置advice 和pointcut。 接着我们就可以通过ProxyFactoryBean来配置我们的代理对象和方面行为,在ProxyFactoryBean中有interceptorNames来配置已经定义 好的通知器-advisor,虽然这里的名字叫做interceptNames,但实际上是供我们配置advisor的地方,具体的代理实现通过JDK 的Proxy或者 CGLIB来完成。因为ProxyFactoryBean是一个FactoryBean,在ProxyFactoryBean中我们通过getObject()可以直接得到代理对象: public Object getObject() throws BeansException { //这里初始化通知器链 initializeAdvisorChain(); if (isSingleton()) { //根据定义需要生成单件的Proxy return getSingletonInstance(); } else { ....... //这里根据定义需要生成Prototype类型的Proxy return newPrototypeInstance(); } } 我们看看怎样生成单件的代理对象: private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { // 这里设置代理对象的接口 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; } //使用createAopProxy放回的AopProxy来得到代理对象。 protected Object getProxy(AopProxy aopProxy) { http://jiwenke.javaeye.com 1.5 Spring源代码解析(五):Spring AOP获取Proxy 第 45 / 90 页 return aopProxy.getProxy(this.beanClassLoader); } ProxyFactoryBean的父类是AdvisedSupport,Spring使用AopProxy接口把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 { //首先考虑使用cglib来实现代理对象,当然如果同时目标对象不是接口的实现类的话 if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass() || advisedSupport.getProxiedInterfaces().length == 0) { //这里判断如果不存在cglib库,直接抛出异常。 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不是接口的实现的话,返回cglib类型的AopProxy return CglibProxyFactory.createCglibProxy(advisedSupport); } else { // 这里使用JDK来生成Proxy,返回JDK类型的AopProxy return new JdkDynamicAopProxy(advisedSupport); } } http://jiwenke.javaeye.com 1.5 Spring源代码解析(五):Spring AOP获取Proxy 第 46 / 90 页 于是我们就可以看到其中的代理对象可以由JDK或者Cglib来生成,我们看到JdkDynamicAopProxy类和Cglib2AopProxy都实现的是 AopProxy的接口,在JdkDynamicAopProxy实现中我们可以看到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之后,通过ProxyFactoryBean得到对其方法的调用就被Proxy拦截了, ProxyFactoryBean的getObject()方法得到的 实际上是一个Proxy了,我们的target对象已经被封装了。对 ProxyFactoryBean这个工厂bean而言,其生产出来的对象是封装了目标对 象的代理对象。 http://jiwenke.javaeye.com 1.5 Spring源代码解析(五):Spring AOP获取Proxy 第 47 / 90 页 1.6 Spring源代码解析(六):Spring声明式事务处理 发表时间: 2007-06-08 我们看看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); } http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 48 / 90 页 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); } http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 49 / 90 页 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); http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 50 / 90 页 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())); http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 51 / 90 页 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; http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 52 / 90 页 } 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 http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 53 / 90 页 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); } http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 54 / 90 页 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对事务进行处理,包括异常抛出和正常的提交事务,具体的事务管理器由用户程序设定。 http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 55 / 90 页 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,然后通过直接调用 事务管理器的相关方法就能完成事务处理。 声明式事务处理也同样实现了类似的过程,只是因为采用了声明的方法,需要增加对属性的读取处理,并且需要把整个过程整合到 http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 56 / 90 页 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); } http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 57 / 90 页 ......... } 这些模板方法的实现由具体的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的源代码来学习,理解可能会更深刻些。 http://jiwenke.javaeye.com 1.6 Spring源代码解析(六):Spring声明式事务处理 第 58 / 90 页 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 发表时间: 2007-08-01 前面我们分析了Spring AOP实现中得到Proxy对象的过程,下面我们看看在Spring AOP中拦截器链是怎样被调用的,也就是Proxy模式是 怎样起作用的,或者说Spring是怎样为我们提供AOP功能的; 在JdkDynamicAopProxy中生成Proxy对象的时候: return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); 这里的this参数对应的是InvocationHandler对象,这里我们的JdkDynamicAopProxy实现了这个接口,也就是说当Proxy对象的函数被调用 的时候,这个InvocationHandler的invoke方法会被作为回调函数调用,下面我们看看这个方法的实现: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation = null; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { // Try special rules for equals() method and implementation of the // Advised AOP configuration interface. if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // What if equals throws exception!? // This class implements the equals(Object) method itself. return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE; } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // This class implements the hashCode() method itself. return new Integer(hashCode()); } if (Advised.class == method.getDeclaringClass()) { // service invocations on ProxyConfig with the proxy config return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal = null; if (this.advised.exposeProxy) { http://jiwenke.javaeye.com 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 第 59 / 90 页 // make invocation available if necessary oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // May be null. Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. // 这里是得到目标对象的地方,当然这个目标对象可能来自于一个实例池或者是一个简单的JAVA对象 target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } // get the interception chain for this method // 这里获得定义好的拦截器链 List chain = this.advised.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( this.advised, proxy, method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. // 如果没有设定拦截器,那么我们就直接调用目标的对应方法 if (chain.isEmpty()) { // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { // We need to create a method invocation... // invocation = advised.getMethodInvocationFactory().getMethodInvocation( // proxy, method, targetClass, target, args, chain, advised); // 如果有拦截器的设定,那么需要调用拦截器之后才调用目标对象的相应方法 // 这里通过构造一个ReflectiveMethodInvocation来实现,下面我们会看这个ReflectiveMethodInvocation类 invocation = new ReflectiveMethodInvocation( proxy, target, method, args, targetClass, chain); // proceed to the joinpoint through the interceptor chain // 这里通过ReflectiveMethodInvocation来调用拦截器链和相应的目标方法 retVal = invocation.proceed(); } // massage return value if necessary http://jiwenke.javaeye.com 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 第 60 / 90 页 if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy)) { // Special case: it returned "this" and the return type of the method is type-compatible // Note that we can't help if the target sets // a reference to itself in another returned object. retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // must have come from TargetSource targetSource.releaseTarget(target); } if (setProxyContext) { // restore old proxy AopContext.setCurrentProxy(oldProxy); } } } 我们先看看目标对象方法的调用,这里是通过AopUtils的方法调用 - 使用反射机制来对目标对象的方法进行调用: public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable { // Use reflection to invoke the method. // 利用放射机制得到相应的方法,并且调用invoke try { if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { method.setAccessible(true); } return method.invoke(target, args); } catch (InvocationTargetException ex) { // Invoked method threw a checked exception. // We must rethrow it. The client won't see the interceptor. throw ex.getTargetException(); } catch (IllegalArgumentException ex) { throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + http://jiwenke.javaeye.com 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 第 61 / 90 页 method + "] on target [" + target + "]", ex); } catch (IllegalAccessException ex) { throw new AopInvocationException("Couldn't access method: " + method, ex); } } 对拦截器链的调用处理是在ReflectiveMethodInvocation里实现的: public Object proceed() throws Throwable { // We start with an index of -1 and increment early. // 这里直接调用目标对象的方法,没有拦截器的调用或者拦截器已经调用完了,这个currentInterceptorIndex的初始值是0 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(this.currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { // Evaluate dynamic method matcher here: static part will already have // been evaluated and found to match. // 这里获得相应的拦截器,如果拦截器可以匹配的上的话,那就调用拦截器的invoke方法 InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(nextInvocation()); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. // 如果拦截器匹配不上,那就调用下一个拦截器,这个时候拦截器链的位置指示后移并迭代调用当前的proceed方法 this.currentInterceptorIndex++; return proceed(); } } else { // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(nextInvocation()); } } http://jiwenke.javaeye.com 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 第 62 / 90 页 这里把当前的拦截器链以及在拦截器链的位置标志都clone到一个MethodInvocation对象了,作用是当前的拦截器执行完之后,会继续 沿着得到这个拦截器链执行下面的拦截行为,也就是会迭代的调用上面这个proceed: private ReflectiveMethodInvocation nextInvocation() throws CloneNotSupportedException { ReflectiveMethodInvocation invocation = (ReflectiveMethodInvocation) clone(); invocation.currentInterceptorIndex = this.currentInterceptorIndex + 1; invocation.parent = this; return invocation; } 这里的nextInvocation就已经包含了当前的拦截链的基本信息,我们看到在Interceptor中的实现比如TransactionInterceptor的实现中: public Object invoke(final MethodInvocation invocation) throws Throwable { ......//这里是TransactionInterceptor插入的事务处理代码,我们会在后面分析事务处理实现的时候进行分析 try { //这里是对配置的拦截器链进行迭代处理的调用 retVal = invocation.proceed(); } ......//省略了和事务处理的异常处理代码 ,也是TransactionInterceptor插入的处理 else { try { Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr, new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { //这里是TransactionInterceptor插入对事务处理的代码 TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status); //这里是对配置的拦截器链进行迭代处理的调用,接着顺着拦截器进行处理 try { return invocation.proceed(); } ......//省略了和事务处理的异常处理代码 ,也是TransactionInterceptor插入的处理 } 从上面的分析我们看到了Spring AOP的基本实现,比如Spring怎样得到Proxy,怎样利用JAVA Proxy以及反射机制对用户定义的拦截器链 进行处理。 http://jiwenke.javaeye.com 1.7 Spring源代码解析(七):Spring AOP中对拦截器调用的实现 第 63 / 90 页 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 发表时间: 2007-08-10 O/R工具出现之后,简化了许多复杂的信息持久化的开发。Spring应用开发者可以通过Spring提供的O/R方案更方便的使用各种持久化 工具,比如Hibernate;下面我们就Spring+Hibernate中的Spring实现做一个简单的剖析。 Spring对Hinberanate的配置是通过LocalSessionFactoryBean来完成的,这是一个工厂Bean的实现,在基类 AbstractSessionFactoryBean中: /** * 这是FactoryBean需要实现的接口方法,直接取得当前的sessionFactory的值 */ public Object getObject() { return this.sessionFactory; } 这个值在afterPropertySet中定义: public void afterPropertiesSet() throws Exception { //这个buildSessionFactory是通过配置信息得到SessionFactory的地方 SessionFactory rawSf = buildSessionFactory(); //这里使用了Proxy方法插入对getCurrentSession的拦截,得到和事务相关的session this.sessionFactory = wrapSessionFactoryIfNecessary(rawSf); } 我们先看看SessionFactory是怎样创建的,这个方法很长,包含了创建Hibernate的SessionFactory的详尽步骤: protected SessionFactory buildSessionFactory() throws Exception { SessionFactory sf = null; // Create Configuration instance. Configuration config = newConfiguration(); //这里配置数据源,事务管理器,LobHander到Holder中,这个Holder是一个ThreadLocal变量,这样这些资源就和线程绑定了 if (this.dataSource != null) { // Make given DataSource available for SessionFactory configuration. configTimeDataSourceHolder.set(this.dataSource); } if (this.jtaTransactionManager != null) { // Make Spring-provided JTA TransactionManager available. configTimeTransactionManagerHolder.set(this.jtaTransactionManager); http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 64 / 90 页 } if (this.lobHandler != null) { // Make given LobHandler available for SessionFactory configuration. // Do early because because mapping resource might refer to custom types. configTimeLobHandlerHolder.set(this.lobHandler); } //这里是使用Hibernate的各个属性的配置,这里使用了Configuration类来抽象这些数据 try { // Set connection release mode "on_close" as default. // This was the case for Hibernate 3.0; Hibernate 3.1 changed // it to "auto" (i.e. "after_statement" or "after_transaction"). // However, for Spring's resource management (in particular for // HibernateTransactionManager), "on_close" is the better default. config.setProperty(Environment.RELEASE_CONNECTIONS, ConnectionReleaseMode.ON_CLOSE.toString()); if (!isExposeTransactionAwareSessionFactory()) { // Not exposing a SessionFactory proxy with transaction-aware // getCurrentSession() method -> set Hibernate 3.1 CurrentSessionContext // implementation instead, providing the Spring-managed Session that way. // Can be overridden by a custom value for corresponding Hibernate property. config.setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS, "org.springframework.orm.hibernate3.SpringSessionContext"); } if (this.entityInterceptor != null) { // Set given entity interceptor at SessionFactory level. config.setInterceptor(this.entityInterceptor); } if (this.namingStrategy != null) { // Pass given naming strategy to Hibernate Configuration. config.setNamingStrategy(this.namingStrategy); } if (this.typeDefinitions != null) { // Register specified Hibernate type definitions. Mappings mappings = config.createMappings(); for (int i = 0; i < this.typeDefinitions.length; i++) { TypeDefinitionBean typeDef = this.typeDefinitions[i]; mappings.addTypeDef(typeDef.getTypeName(), typeDef.getTypeClass(), typeDef.getParameters()); http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 65 / 90 页 } } if (this.filterDefinitions != null) { // Register specified Hibernate FilterDefinitions. for (int i = 0; i < this.filterDefinitions.length; i++) { config.addFilterDefinition(this.filterDefinitions[i]); } } if (this.configLocations != null) { for (int i = 0; i < this.configLocations.length; i++) { // Load Hibernate configuration from given location. config.configure(this.configLocations[i].getURL()); } } if (this.hibernateProperties != null) { // Add given Hibernate properties to Configuration. config.addProperties(this.hibernateProperties); } if (this.dataSource != null) { boolean actuallyTransactionAware = (this.useTransactionAwareDataSource || this.dataSource instanceof TransactionAwareDataSourceProxy); // Set Spring-provided DataSource as Hibernate ConnectionProvider. config.setProperty(Environment.CONNECTION_PROVIDER, actuallyTransactionAware ? TransactionAwareDataSourceConnectionProvider.class.getName() : LocalDataSourceConnectionProvider.class.getName()); } if (this.jtaTransactionManager != null) { // Set Spring-provided JTA TransactionManager as Hibernate property. config.setProperty( Environment.TRANSACTION_MANAGER_STRATEGY, LocalTransactionManagerLookup.class.getName()); } if (this.mappingLocations != null) { // Register given Hibernate mapping definitions, contained in resource files. for (int i = 0; i < this.mappingLocations.length; i++) { config.addInputStream(this.mappingLocations[i].getInputStream()); http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 66 / 90 页 } } if (this.cacheableMappingLocations != null) { // Register given cacheable Hibernate mapping definitions, read from the file system. for (int i = 0; i < this.cacheableMappingLocations.length; i++) { config.addCacheableFile(this.cacheableMappingLocations[i].getFile()); } } if (this.mappingJarLocations != null) { // Register given Hibernate mapping definitions, contained in jar files. for (int i = 0; i < this.mappingJarLocations.length; i++) { Resource resource = this.mappingJarLocations[i]; config.addJar(resource.getFile()); } } if (this.mappingDirectoryLocations != null) { // Register all Hibernate mapping definitions in the given directories. for (int i = 0; i < this.mappingDirectoryLocations.length; i++) { File file = this.mappingDirectoryLocations[i].getFile(); if (!file.isDirectory()) { throw new IllegalArgumentException( "Mapping directory location [" + this.mappingDirectoryLocations[i] + "] does not denote a directory"); } config.addDirectory(file); } } if (this.entityCacheStrategies != null) { // Register cache strategies for mapped entities. for (Enumeration classNames = this.entityCacheStrategies.propertyNames(); classNames.hasMoreElements();) { String className = (String) classNames.nextElement(); String[] strategyAndRegion = StringUtils.commaDelimitedListToStringArray(this.entityCacheStrategies.getProperty(className)); if (strategyAndRegion.length > 1) { config.setCacheConcurrencyStrategy(className, strategyAndRegion[0], strategyAndRegion[1]); } else if (strategyAndRegion.length > 0) { config.setCacheConcurrencyStrategy(className, strategyAndRegion[0]); http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 67 / 90 页 } } } if (this.collectionCacheStrategies != null) { // Register cache strategies for mapped collections. for (Enumeration collRoles = this.collectionCacheStrategies.propertyNames(); collRoles.hasMoreElements();) { String collRole = (String) collRoles.nextElement(); String[] strategyAndRegion = StringUtils.commaDelimitedListToStringArray(this.collectionCacheStrategies.getProperty(collRole)); if (strategyAndRegion.length > 1) { config.setCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0], strategyAndRegion[1]); } else if (strategyAndRegion.length > 0) { config.setCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0]); } } } if (this.eventListeners != null) { // Register specified Hibernate event listeners. for (Iterator it = this.eventListeners.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); Assert.isTrue(entry.getKey() instanceof String, "Event listener key needs to be of type String"); String listenerType = (String) entry.getKey(); Object listenerObject = entry.getValue(); if (listenerObject instanceof Collection) { Collection listeners = (Collection) listenerObject; EventListeners listenerRegistry = config.getEventListeners(); Object[] listenerArray = (Object[]) Array.newInstance(listenerRegistry.getListenerClassFor(listenerType), listeners.size()); listenerArray = listeners.toArray(listenerArray); config.setListeners(listenerType, listenerArray); } else { config.setListener(listenerType, listenerObject); } } } // Perform custom post-processing in subclasses. postProcessConfiguration(config); http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 68 / 90 页 // 这里是根据Configuration配置创建SessionFactory的地方 logger.info("Building new Hibernate SessionFactory"); this.configuration = config; sf = newSessionFactory(config); } //最后把和线程绑定的资源清空 finally { if (this.dataSource != null) { // Reset DataSource holder. configTimeDataSourceHolder.set(null); } if (this.jtaTransactionManager != null) { // Reset TransactionManager holder. configTimeTransactionManagerHolder.set(null); } if (this.lobHandler != null) { // Reset LobHandler holder. configTimeLobHandlerHolder.set(null); } } // Execute schema update if requested. if (this.schemaUpdate) { updateDatabaseSchema(); } return sf; } 而直接调用org.hibernate.cfg.Configuration来得到需要的SessionFactory: protected SessionFactory newSessionFactory(Configuration config) throws HibernateException { return config.buildSessionFactory(); } 所以我们这里看到LocalSessionFactory大致起到的一个读取资源配置然后生成SessionFactory的作用;当然这里在得到 SessionFactory 之后,还需要对session的事务管理作一些处理 - 使用了一个Proxy模式对getCurrentSession方法进行了拦截; http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 69 / 90 页 //这里先根据当前的SessionFactory的类型得到Proxy,然后插入Spring定义好的getCurrentSession拦截器 protected SessionFactory getTransactionAwareSessionFactoryProxy(SessionFactory target) { Class sfInterface = SessionFactory.class; if (target instanceof SessionFactoryImplementor) { sfInterface = SessionFactoryImplementor.class; } return (SessionFactory) Proxy.newProxyInstance(sfInterface.getClassLoader(), new Class[] {sfInterface}, new TransactionAwareInvocationHandler(target)); } 拦截器的实现如下: private static class TransactionAwareInvocationHandler implements InvocationHandler { private final SessionFactory target; public TransactionAwareInvocationHandler(SessionFactory target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Invocation on SessionFactory/SessionFactoryImplementor interface coming in... // 这里对getCurrentSession方法进行拦截,得到一个和当前事务绑定的session交给用户 if (method.getName().equals("getCurrentSession")) { // Handle getCurrentSession method: return transactional Session, if any. try { return SessionFactoryUtils.doGetSession((SessionFactory) proxy, false); } catch (IllegalStateException ex) { throw new HibernateException(ex.getMessage()); } } else if (method.getName().equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE); } else if (method.getName().equals("hashCode")) { // Use hashCode of SessionFactory proxy. return new Integer(hashCode()); } // 这里是需要运行的SessionFactory的目标方法 http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 70 / 90 页 try { return method.invoke(this.target, args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } 我们看看getCurrentSession的实现,在SessionFactoryUtils中: private static Session doGetSession( SessionFactory sessionFactory, Interceptor entityInterceptor, SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate) throws HibernateException, IllegalStateException { Assert.notNull(sessionFactory, "No SessionFactory specified"); //这个TransactionSynchronizationManager的Resource是一个ThreadLocal变量,sessionFactory是一个单例,但ThreadLocal是和线程绑定的 //这样就实现了Hiberante中常用的通过ThreadLocal的session管理机制 SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if (sessionHolder != null && !sessionHolder.isEmpty()) { // pre-bound Hibernate Session Session session = null; if (TransactionSynchronizationManager.isSynchronizationActive() && sessionHolder.doesNotHoldNonDefaultSession()) { // Spring transaction management is active -> // register pre-bound Session with it for transactional flushing. session = sessionHolder.getValidatedSession(); if (session != null && !sessionHolder.isSynchronizedWithTransaction()) { logger.debug("Registering Spring transaction synchronization for existing Hibernate Session"); TransactionSynchronizationManager.registerSynchronization( new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false)); sessionHolder.setSynchronizedWithTransaction(true); // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session // with FlushMode.NEVER, which needs to allow flushing within the transaction. FlushMode flushMode = session.getFlushMode(); if (flushMode.lessThan(FlushMode.COMMIT) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { session.setFlushMode(FlushMode.AUTO); sessionHolder.setPreviousFlushMode(flushMode); } http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 71 / 90 页 } } else { // No Spring transaction management active -> try JTA transaction synchronization. session = getJtaSynchronizedSession(sessionHolder, sessionFactory, jdbcExceptionTranslator); } if (session != null) { return session; } } //这里直接打开一个Session logger.debug("Opening Hibernate Session"); Session session = (entityInterceptor != null ? sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession()); // Use same Session for further Hibernate actions within the transaction. // Thread object will get removed by synchronization at transaction completion. // 把新打开的Session放到SessionHolder,然后放到ThreadLocal里面去和线程绑定起来,这个ThreadLocal是在 TransactionSynchronizationManager中配置好的,可以根据sessionFactory来索取 // 同时根据事务处理的状态来配置session的属性,比如把FlushMode设置为Never,同时把session和事务处理关联起来 if (TransactionSynchronizationManager.isSynchronizationActive()) { // We're within a Spring-managed transaction, possibly from JtaTransactionManager. logger.debug("Registering Spring transaction synchronization for new Hibernate Session"); SessionHolder holderToUse = sessionHolder; if (holderToUse == null) { holderToUse = new SessionHolder(session); } else { holderToUse.addSession(session); } if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { session.setFlushMode(FlushMode.NEVER); } TransactionSynchronizationManager.registerSynchronization( new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != sessionHolder) { TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse); } } else { // No Spring transaction management active -> try JTA transaction synchronization. registerJtaSynchronization(session, sessionFactory, jdbcExceptionTranslator, sessionHolder); http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 72 / 90 页 } // Check whether we are allowed to return the Session. if (!allowCreate && !isSessionTransactional(session, sessionFactory)) { closeSession(session); throw new IllegalStateException("No Hibernate Session bound to thread, " + "and configuration does not allow creation of non-transactional one here"); } return session; } 这里就是在Spring中为使用Hiberante的SessionFactory以及Session做的准备工作,在这个基础上,用户可以通过使用 HibernateTemplate来使用Hibernate的O/R功能,和以前看到的一样这是一个execute的回调: public Object execute(HibernateCallback action, boolean exposeNativeSession) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); //这里得到配置好的Hibernate的Session Session session = getSession(); boolean existingTransaction = SessionFactoryUtils.isSessionTransactional(session, getSessionFactory()); if (existingTransaction) { logger.debug("Found thread-bound Session for HibernateTemplate"); } FlushMode previousFlushMode = null; try { previousFlushMode = applyFlushMode(session, existingTransaction); enableFilters(session); Session sessionToExpose = (exposeNativeSession ? session : createSessionProxy(session)); //这里是回调的入口 Object result = action.doInHibernate(sessionToExpose); flushIfNecessary(session, existingTransaction); return result; } catch (HibernateException ex) { throw convertHibernateAccessException(ex); } catch (SQLException ex) { throw convertJdbcAccessException(ex); } catch (RuntimeException ex) { // Callback code threw application exception... http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 73 / 90 页 throw ex; } finally { //如果这个调用的方法在一个事务当中, if (existingTransaction) { logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate"); disableFilters(session); if (previousFlushMode != null) { session.setFlushMode(previousFlushMode); } } //否则把Session关闭 else { // Never use deferred close for an explicitly new Session. if (isAlwaysUseNewSession()) { SessionFactoryUtils.closeSession(session); } else { SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory()); } } } } 我们看看怎样得到对应的Session的,仍然使用了SessionFactoryUtils的方法doGetSession: protected Session getSession() { if (isAlwaysUseNewSession()) { return SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()); } else if (!isAllowCreate()) { return SessionFactoryUtils.getSession(getSessionFactory(), false); } else { return SessionFactoryUtils.getSession( getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()); } } 这样我们就可以和其他的Template那样使用Hibernate的基本功能了,使用的时候Spring已经为我们对Session的获取和关闭,事务处理 的绑定做好了封装 - 从这个角度看也大大方便了用户的使用。 http://jiwenke.javaeye.com 1.8 Spring源代码解析(八):Spring驱动Hibernate的实现 第 74 / 90 页 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 发表时间: 2007-08-16 简单分析一下Spring Acegi的源代码实现: Servlet.Filter的实现AuthenticationProcessingFilter启动Web页面的验证过程 - 在AbstractProcessingFilter定义了整个验证过程的模板: public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //这里检验是不是符合ServletRequest/SevletResponse的要求 if (!(request instanceof HttpServletRequest)) { throw new ServletException("Can only process HttpServletRequest"); } if (!(response instanceof HttpServletResponse)) { throw new ServletException("Can only process HttpServletResponse"); } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; //根据HttpServletRequest和HttpServletResponse来进行验证 if (requiresAuthentication(httpRequest, httpResponse)) { if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } //这里定义Acegi中的Authentication对象来持有相关的用户验证信息 Authentication authResult; try { onPreAuthentication(httpRequest, httpResponse); //这里的具体验证过程委托给子类完成,比如AuthenticationProcessingFilter来完成基于Web页面的用户验证 authResult = attemptAuthentication(httpRequest); } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(httpRequest, httpResponse, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } //完成验证后的后续工作,比如跳转到相应的页面 http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 75 / 90 页 successfulAuthentication(httpRequest, httpResponse, authResult); return; } chain.doFilter(request, response); } 在AuthenticationProcessingFilter中的具体验证过程是这样的: public Authentication attemptAuthentication(HttpServletRequest request) throws AuthenticationException { //这里从HttpServletRequest中得到用户验证的用户名和密码 String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } //这里根据得到的用户名和密码去构造一个Authentication对象提供给AuthenticationManager进行验证,里面包含了用户的用户名和密码信息 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Place the last username attempted into HttpSession for views request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username); // Allow subclasses to set the "details" property setDetails(request, authRequest); //这里启动AuthenticationManager进行验证过程 return this.getAuthenticationManager().authenticate(authRequest); } 在Acegi框架中,进行验证管理的主要类是AuthenticationManager,我们看看它是怎样进行验证管理的 - 验证的调用入口是authenticate 在AbstractAuthenticationManager的实现中: //这是进行验证的函数,返回一个Authentication对象来记录验证的结果,其中包含了用户的验证信息,权限配置等,同时这个 Authentication会以后被授权模块使用 http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 76 / 90 页 //如果验证失败,那么在验证过程中会直接抛出异常 public final Authentication authenticate(Authentication authRequest) throws AuthenticationException { try {//这里是实际的验证处理,我们下面使用ProviderManager来说明具体的验证过程,传入的参数authRequest里面已经包含了从HttpServletRequest中得到的用户输入的用户名和密码 Authentication authResult = doAuthentication(authRequest); copyDetails(authRequest, authResult); return authResult; } catch (AuthenticationException e) { e.setAuthentication(authRequest); throw e; } } 在ProviderManager中进行实际的验证工作,假设这里使用数据库来存取用户信息: public Authentication doAuthentication(Authentication authentication) throws AuthenticationException { //这里取得配置好的provider链的迭代器,在配置的时候可以配置多个provider,这里我们配置的是DaoAuthenticationProvider来说明, 它使用数据库来保存用户的用户名和密码信息。 Iterator iter = providers.iterator(); Class toTest = authentication.getClass(); AuthenticationException lastException = null; while (iter.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider) iter.next(); if (provider.supports(toTest)) { logger.debug("Authentication attempt using " + provider.getClass().getName()); //这个result包含了验证中得到的结果信息 Authentication result = null; try {//这里是provider进行验证处理的过程 result = provider.authenticate(authentication); sessionController.checkAuthenticationAllowed(result); } catch (AuthenticationException ae) { lastException = ae; result = null; } if (result != null) { http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 77 / 90 页 sessionController.registerSuccessfulAuthentication(result); publishEvent(new AuthenticationSuccessEvent(result)); return result; } } } if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound", new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}")); } // 这里发布事件来通知上下文的监听器 String className = exceptionMappings.getProperty(lastException.getClass().getName()); AbstractAuthenticationEvent event = null; if (className != null) { try { Class clazz = getClass().getClassLoader().loadClass(className); Constructor constructor = clazz.getConstructor(new Class[] { Authentication.class, AuthenticationException.class }); Object obj = constructor.newInstance(new Object[] {authentication, lastException}); Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj, "Must be an AbstractAuthenticationEvent"); event = (AbstractAuthenticationEvent) obj; } catch (ClassNotFoundException ignored) {} catch (NoSuchMethodException ignored) {} catch (IllegalAccessException ignored) {} catch (InstantiationException ignored) {} catch (InvocationTargetException ignored) {} } if (event != null) { publishEvent(event); } else { if (logger.isDebugEnabled()) { logger.debug("No event was found for the exception " + lastException.getClass().getName()); } } // Throw the exception http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 78 / 90 页 throw lastException; } 我们下面看看在DaoAuthenticationProvider是怎样从数据库中取出对应的验证信息进行用户验证的,在它的基类 AbstractUserDetailsAuthenticationProvider定义了验证的处理模板: public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // 这里取得用户输入的用户名 String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); // 如果配置了缓存,从缓存中去取以前存入的用户验证信息 - 这里是UserDetail,是服务器端存在数据库里的用户信息,这样就不用每次都去数据库中取了 boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); //没有取到,设置标志位,下面会把这次取到的服务器端用户信息存入缓存中去 if (user == null) { cacheWasUsed = false; try {//这里是调用UserDetailService去取用户数据库里信息的地方 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } if (!user.isAccountNonLocked()) { throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked")); } if (!user.isEnabled()) { throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 79 / 90 页 "User is disabled")); } if (!user.isAccountNonExpired()) { throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired")); } // This check must come here, as we don't want to tell users // about account status unless they presented the correct credentials try {//这里是验证过程,在retrieveUser中从数据库中得到用户的信息,在additionalAuthenticationChecks中进行对比用户输入和服务器端的用户信息 //如果验证通过,那么构造一个Authentication对象来让以后的授权使用,如果验证不通过,直接抛出异常结束鉴权过程 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (ie not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } if (!user.isCredentialsNonExpired()) { throw new CredentialsExpiredException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired")); } //根据前面的缓存结果决定是不是要把当前的用户信息存入缓存以供下次验证使用 if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } //最后返回Authentication记录了验证结果供以后的授权使用 return createSuccessAuthentication(principalToReturn, authentication, user); } http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 80 / 90 页 //这是是调用UserDetailService去加载服务器端用户信息的地方,从什么地方加载要看设置,这里我们假设由JdbcDaoImp来从数据中进行加载 protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; //这里调用UserDetailService去从数据库中加载用户验证信息,同时返回从数据库中返回的信息,这些信息放到了UserDetails对象中去了 try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (DataAccessException repositoryProblem) { throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } if (loadedUser == null) { throw new AuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } 下面我们重点分析一下JdbcDaoImp这个类来看看具体是怎样从数据库中得到用户信息的: public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService { //~ Static fields/initializers ===================================================================================== //这里是预定义好的对查询语句,对应于默认的数据库表结构,也可以自己定义查询语句对应特定的用户数据库验证表的设计 public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT username,password,enabled FROM users WHERE username = ?"; public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT username,authority FROM authorities WHERE username = ?"; //~ Instance fields ================================================================================================ //这里使用Spring JDBC来进行数据库操作 protected MappingSqlQuery authoritiesByUsernameMapping; protected MappingSqlQuery usersByUsernameMapping; private String authoritiesByUsernameQuery; private String rolePrefix = ""; private String usersByUsernameQuery; private boolean usernameBasedPrimaryKey = true; //~ Constructors =================================================================================================== //在初始化函数中把查询语句设置为预定义的SQL语句 public JdbcDaoImpl() { usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY; authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY; http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 81 / 90 页 } //~ Methods ======================================================================================================== protected void addCustomAuthorities(String username, List authorities) {} public String getAuthoritiesByUsernameQuery() { return authoritiesByUsernameQuery; } public String getRolePrefix() { return rolePrefix; } public String getUsersByUsernameQuery() { return usersByUsernameQuery; } protected void initDao() throws ApplicationContextException { initMappingSqlQueries(); } /** * Extension point to allow other MappingSqlQuery objects to be substituted in a subclass */ protected void initMappingSqlQueries() { this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource()); this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource()); } public boolean isUsernameBasedPrimaryKey() { return usernameBasedPrimaryKey; } //这里是取得数据库用户信息的具体过程 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { //根据用户名在用户表中得到用户信息,包括用户名,密码和用户是否有效的信息 List users = usersByUsernameMapping.execute(username); if (users.size() == 0) { throw new UsernameNotFoundException("User not found"); } http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 82 / 90 页 //取集合中的第一个作为有效的用户对象 UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[] //这里在权限表中去取得用户的权限信息,同样的返回一个权限集合对应于这个用户 List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername()); addCustomAuthorities(user.getUsername(), dbAuths); if (dbAuths.size() == 0) { throw new UsernameNotFoundException("User has no GrantedAuthority"); } //这里根据得到的权限集合来配置返回的User对象供以后使用 GrantedAuthority[] arrayAuths = (GrantedAuthority[]) dbAuths.toArray(new GrantedAuthority[dbAuths.size()]); String returnUsername = user.getUsername(); if (!usernameBasedPrimaryKey) { returnUsername = username; } return new User(returnUsername, user.getPassword(), user.isEnabled(), true, true, true, arrayAuths); } public void setAuthoritiesByUsernameQuery(String queryString) { authoritiesByUsernameQuery = queryString; } public void setRolePrefix(String rolePrefix) { this.rolePrefix = rolePrefix; } public void setUsernameBasedPrimaryKey(boolean usernameBasedPrimaryKey) { this.usernameBasedPrimaryKey = usernameBasedPrimaryKey; } public void setUsersByUsernameQuery(String usersByUsernameQueryString) { this.usersByUsernameQuery = usersByUsernameQueryString; } //~ Inner Classes ================================================================================================== /** * 这里是调用Spring JDBC的数据库操作,具体可以参考对JDBC的分析,这个类的作用是把数据库查询得到的记录集合转换为对象集合 - 一个很简单的O/R实现 http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 83 / 90 页 */ protected class AuthoritiesByUsernameMapping extends MappingSqlQuery { protected AuthoritiesByUsernameMapping(DataSource ds) { super(ds, authoritiesByUsernameQuery); declareParameter(new SqlParameter(Types.VARCHAR)); compile(); } protected Object mapRow(ResultSet rs, int rownum) throws SQLException { String roleName = rolePrefix + rs.getString(2); GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName); return authority; } } /** * Query object to look up a user. */ protected class UsersByUsernameMapping extends MappingSqlQuery { protected UsersByUsernameMapping(DataSource ds) { super(ds, usersByUsernameQuery); declareParameter(new SqlParameter(Types.VARCHAR)); compile(); } protected Object mapRow(ResultSet rs, int rownum) throws SQLException { String username = rs.getString(1); String password = rs.getString(2); boolean enabled = rs.getBoolean(3); UserDetails user = new User(username, password, enabled, true, true, true, new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")}); return user; } } } 从数据库中得到用户信息后,就是一个比对用户输入的信息和这个数据库用户信息的比对过程,这个比对过程在 DaoAuthenticationProvider: http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 84 / 90 页 //这个UserDetail是从数据库中查询到的,这个authentication是从用户输入中得到的 protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } //如果用户没有输入密码,直接抛出异常 if (authentication.getCredentials() == null) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), includeDetailsObject ? userDetails : null); } //这里取得用户输入的密码 String presentedPassword = authentication.getCredentials() == null ? "" : authentication.getCredentials().toString(); //这里判断用户输入的密码是不是和数据库里的密码相同,这里可以使用passwordEncoder来对数据库里的密码加解密 // 如果不相同,抛出异常,如果相同则鉴权成功 if (!passwordEncoder.isPasswordValid( userDetails.getPassword(), presentedPassword, salt)) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), includeDetailsObject ? userDetails : null); } } 上面分析了整个Acegi进行验证的过程,从AuthenticationProcessingFilter中拦截Http请求得到用户输入的用户名和密码,这些用户输入 的验证信息会被放到Authentication对象中持有并传递给AuthenticatioManager来对比在服务端的用户信息来完成整个鉴权。这个鉴权 完成以后会把有效的用户信息放在一个Authentication中供以后的授权模块使用。在具体的鉴权过程中,使用了我们配置好的各种 Provider以及对应的UserDetailService和Encoder类来完成相应的获取服务器端用户数据以及与用户输入的验证信息的比对工作。 http://jiwenke.javaeye.com 1.9 Spring源代码解析(九):Spring Acegi框架鉴权的实现 第 85 / 90 页 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 发表时间: 2007-08-17 我们从FilterSecurityInterceptor我们从入手看看怎样进行授权的: //这里是拦截器拦截HTTP请求的入口 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } //这是具体的拦截调用 public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { //在第一次进行过安全检查之后就不会再做了 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { //这是第一次收到相应的请求,需要做安全检测,同时把标志为设置好 - FILTER_APPLIED,下次就再有请求就不会作相同的安全检查了 if (fi.getRequest() != null) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } //这里是做安全检查的地方 InterceptorStatusToken token = super.beforeInvocation(fi); //接着向拦截器链执行 try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } } 我们看看在AbstractSecurityInterceptor是怎样对HTTP请求作安全检测的: protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " http://jiwenke.javaeye.com 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 第 86 / 90 页 + getSecureObjectClass()); } //这里读取配置FilterSecurityInterceptor的ObjectDefinitionSource属性,这些属性配置了资源的安全设置 ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object); if (attr == null) { if(rejectPublicInvocations) { throw new IllegalArgumentException( "No public invocations are allowed via this AbstractSecurityInterceptor. " + "This indicates a configuration error because the " + "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'"); } if (logger.isDebugEnabled()) { logger.debug("Public object - authentication not attempted"); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } if (logger.isDebugEnabled()) { logger.debug("Secure object: " + object.toString() + "; ConfigAttributes: " + attr.toString()); } //这里从SecurityContextHolder中去取Authentication对象,一般在登录时会放到SecurityContextHolder中去 if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attr); } // 如果前面没有处理鉴权,这里需要对鉴权进行处理 Authentication authenticated; if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || alwaysReauthenticate) { try {//调用配置好的AuthenticationManager处理鉴权,如果鉴权不成功,抛出异常结束处理 authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext() .getAuthentication()); } catch (AuthenticationException authenticationException) { throw authenticationException; } http://jiwenke.javaeye.com 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 第 87 / 90 页 // We don't authenticated.setAuthentication(true), because each provider should do that if (logger.isDebugEnabled()) { logger.debug("Successfully Authenticated: " + authenticated.toString()); } //这里把鉴权成功后得到的Authentication保存到SecurityContextHolder中供下次使用 SecurityContextHolder.getContext().setAuthentication(authenticated); } else {//这里处理前面已经通过鉴权的请求,先从SecurityContextHolder中去取得Authentication authenticated = SecurityContextHolder.getContext().getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Previously Authenticated: " + authenticated.toString()); } } // 这是处理授权的过程 try { //调用配置好的AccessDecisionManager来进行授权 this.accessDecisionManager.decide(authenticated, object, attr); } catch (AccessDeniedException accessDeniedException) { //授权不成功向外发布事件 AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated, accessDeniedException); publishEvent(event); throw accessDeniedException; } if (logger.isDebugEnabled()) { logger.debug("Authorization successful"); } AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated); publishEvent(event); // 这里构建一个RunAsManager来替代当前的Authentication对象,默认情况下使用的是NullRunAsManager会把SecurityContextHolder中的Authentication对象清空 Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr); if (runAs == null) { if (logger.isDebugEnabled()) { logger.debug("RunAsManager did not change Authentication object"); } http://jiwenke.javaeye.com 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 第 88 / 90 页 // no further work post-invocation return new InterceptorStatusToken(authenticated, false, attr, object); } else { if (logger.isDebugEnabled()) { logger.debug("Switching to RunAs Authentication: " + runAs.toString()); } SecurityContextHolder.getContext().setAuthentication(runAs); // revert to token.Authenticated post-invocation return new InterceptorStatusToken(authenticated, true, attr, object); } } 到这里我们假设配置AffirmativeBased作为AccessDecisionManager: //这里定义了决策机制,需要全票才能通过 public void decide(Authentication authentication, Object object, ConfigAttributeDefinition config) throws AccessDeniedException { //这里取得配置好的迭代器集合 Iterator iter = this.getDecisionVoters().iterator(); int deny = 0; //依次使用各个投票器进行投票,并对投票结果进行计票 while (iter.hasNext()) { AccessDecisionVoter voter = (AccessDecisionVoter) iter.next(); int result = voter.vote(authentication, object, config); //这是对投票结果进行处理,如果遇到其中一票通过,那就授权通过,如果是弃权或者反对,那就继续投票 switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: //这里对反对票进行计数 deny++; break; default: break; } } http://jiwenke.javaeye.com 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 第 89 / 90 页 //如果有反对票,抛出异常,整个授权不通过 if (deny > 0) { throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // 这里对弃权票进行处理,看看是全是弃权票的决定情况,默认是不通过,由allowIfAllAbstainDecisions变量控制 checkAllowIfAllAbstainDecisions(); } 具体的投票由投票器进行,我们这里配置了RoleVoter来进行投票: public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) { int result = ACCESS_ABSTAIN; //这里取得资源的安全配置 Iterator iter = config.getConfigAttributes(); while (iter.hasNext()) { ConfigAttribute attribute = (ConfigAttribute) iter.next(); if (this.supports(attribute)) { result = ACCESS_DENIED; // 这里对资源配置的安全授权级别进行判断,也就是匹配ROLE为前缀的角色配置 // 遍历每个配置属性,如果其中一个匹配该主体持有的GrantedAuthority,则访问被允许。 for (int i = 0; i < authentication.getAuthorities().length; i++) { if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) { return ACCESS_GRANTED; } } } } return result; } 上面就是对整个授权过程的一个分析,从FilterSecurityInterceptor拦截Http请求入手,然后读取对资源的安全配置以后,把这些信息交 由AccessDecisionManager来进行决策,Spring为我们提供了若干决策器来使用,在决策器中我们可以配置投票器来完成投票,我们在 上面具体分析了角色投票器的使用过程。 http://jiwenke.javaeye.com 1.10 Spring源代码解析(十):Spring Acegi框架授权的实现 第 90 / 90 页
还剩89页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 13 人已下载

下载pdf

pdf贡献者

lrg30067

贡献于2011-05-30

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf