spring 下载
下载地址:http://repo.spring.io
SPRING框架——由来和发展
Spring是java平台上的一个开源应用框架。它的第一个版本是由Rod Johnson写出来的。Rod在他的Expert One-On-One Java EE Design and Development(Java企业应用设计与开发的专家一对一)一书中首次发布了这个框架。该框架也可以移植到.NET的环境下。
Spring的框架首次在2003年6月的Apache 2.0的使用许可中发布。第一个具有里程碑意义的版本是2004年3月发布的1.0。2004年9月和2005年3月先后又有重要的版本面世。
Spring框架本身并没有强制实行任何特别的编程模式。在Java社区里,Spring作为EJB模型之外的另外一个选择甚至是替代品而广为流行。从设计上看,Spring给予了Java程序员许多的自由度,但同时对业界常见的问题也提供了良好的文档和易于使用的方法。
Spring框架的核心功能在任何Java应用中都是适用的。在基于Java企业平台上的web应用中,大量的拓展和改进得以形成。为此,Spring获得了广泛的欢迎,并被许多公司认可为具有战略意义的重要框架。
1. Spring框架的历史
Spring框架最开始的部分是由Rod Johnson于2000年为伦敦的金融界提供独立咨询业务时写出来的。在《Java企业应用设计与开发的专家一对一》一书中,Rod进一步拓展了他的代码,以阐述“如何让应用程序能以超出当时大众所惯于接受的易用性和稳定性与J2EE平台上的不同组件合作”的观点。
在2001年,web应用的主流编程模式为Java Servlet API和EJB。两者都是由太阳微系统公司与其他一些开发商和利益团体提出的,并在Java业界里获得了广泛的共识。那些非Web的应用,比如用户端的或批处理的应用,也可以基于能够提供所需功能的开源或商用工具和项目。
基于最优方法并适用于各种应用类型的Spring框架的建立要归功于Rod Johnson。这些想法也在他的书中得以阐述。书发表后,基于读者的要求,源代码在开源使用协议下得以提供。
一批自愿拓展Spring框架的程序开发员组成了团队,2003年2月在Sourceforge上构建了一个项目。在Spring框架上工作了一年之后,这个团队在2004年3月发布了第一个版本(1.0)。这个版本之后,Spring框架在Java社区里变得异常流行,部分的要归结于它好于一般水准的文档功能和参考文献,特别是对于一个开源项目而言尤其如此。
但是,Spring框架在2004年也备受批评,有时它也成为热烈争论的主题。Spring的第一个版本发布时,许多程序员和领先的设计人员把它看作是远离传统编程模式的一步;特别是对于EJB而言尤其如此。Spring框架的一个重要设计目标就是更容易地与已有的J2EE标准和商用工具整合。在很大程度上,这个目标使得通过受争议的官方委员会控制的规范文档来定义功能变得可有可无。
Spring框架使之前并不受欢迎的技术在短时间内迅速走红,最有名的例子就是反向控制(IOC)。2004年,Spring框架的采用率非常之高;通过推出自身的AOP(面向方向的编程),Spring使AOP整体而言在Java社区里广受欢迎。
2005年,Spring因具有里程碑意义的新的版本的推出,更多功能的添加,从而得到了比2004年更高的采用率。2004年底创建的Spring论坛也对框架的推广而推波助澜。论坛对广大用户而言已经成为最重要的信息和帮助的源泉。
2005年,Spring框架的开发人员成立了自己的公司,来提供对Spring的商业支持,其中最显著的就是与BEA的合作。2005年12月,第一个Spring会议在迈阿密举行,3天的课程吸引了300名开发人员。2006年6月在安特卫普召开的会议有400多名开发人员。
2. Spring框架的主要功能
• 基于Java Beans的配置管理,采用IOC的原理,特别是对依赖注射技术的使用。这些都用来减少各组件间对实施细则的相互依赖性。
• 一个核心的,全局适用的bean工厂
• 一个一般抽象化的层面来管理数据库间的数据处理
• 建立在框架内的,对Java数据处理API和单独的JDBC数据源的一般性策略。因此,在数据处理支持上对Java企业版本环境的依赖性得以消除
• 和一些可持续性的框架,如Hibernate,JDO,iBATIS和db4o,的整合
• web应用中的MVC框架,基于核心的Spring功能,支持多种产生视图的技术,包括JSP,FreeMarker,Velocity,Tiles,iText,和POI
• 大量的AOP框架以提供诸如数据处理管理的服务。同IOC的功能一样,目的是提高系统的模块化程度
Spring 框架的七个模块
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如图 1 所示。
``````````````````````
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
Spring框架的主要功能[NextPage]
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
Spring事务管理
在学习spring事务管理时,我忍不住要问,spring为什么进行事务管理,spring怎么进行的事务管理?
首先,为什么要进行事务,接下来说说spring是怎样进行事务管理的.
① Spring事务策略
Spring事务策略,也就是spring事务管理的实现方式.它有一个统一的抽象是由实现下面这个接口完成的.
org.springframework.transaction.PlatformTransactionManager
此接口的内容如下:
Public interfacePlatformTransactionManager()...{
TransactionStatue getTransaction(TransactionDefinition definition) throws TransactionException;
Void commit(TransactionStatus status) throws TransactionException;
Void rollback(TransactionStatus status) throws TransactionException;
}
不管是声明式的还是编程式的事务管理都需要此抽象来完成.
解释一下这个接口,这样可以更好的理解spring的事务控制的原理.
getTransaction()根据类型为TransactionDefinition的参数返回一个TransactionStatus对象.返回的TransactionStatus对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务).如同J2EE事务上下文,一个TransactionStatus也是和执行的线程关联的.
同时,在框架中还存在TransactionDefinition接口,即上边的参数类型.此接口指定了事务隔离程度、事务传播、事务超时、只读状态。
另外,还有TransactionStatus接口。这个接口为处理事务提供简单的控制事务执行和查询事务状态的方法。
② 两种事务管理方式:编程式、声明式。
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。
ⅰ. TransactionTempale采用和其他Spring模板,如JdbcTempalte和HibernateTemplate一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。
代码片段:
Object result =tt.execute(newTransactionCallback()...{
publicObject doTransaction(TransactionStatus status)...{
updateOperation();
returnresultOfUpdateOperation();
}
});
使用TransactionCallback()可以返回一个值。
如果使用TransactionCallbackWithoutResult则没有返回值。
ⅱ. 也可以使用PlatformTransactionManager直接管理事务。简单地通过一个bean引用给你的bean传递一个你使用的PlatformTransaction对象。然后,使用TransactionDefinition和TransactionStatus对象就可以发起、回滚、提交事务。[NextPage]
如下片段:
DefaultTransactionDefinition def=newDefaultTransactionDefinition(); //new 一个事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //初始化事务,参数定义事务的传播类型;
TransactionStatus status =transactionManager.getTransaction(def); //获得事务状态
try...{
transactionManager.commit(status); //提交事务;
}catch(…..)...{
transactionManager.rollback(status); //回滚事务;
}
Spring也提供声明式事务管理。这是通过AOP实现的。
大多数Spring用户选择声明式事务管理,这是最少影响应用代码的选择,因而这是和非侵入性的轻量级容器的观念是一致的。
① 通常通过TransactionProxyFactoryBean设置Spring事务代理。需要一个目标对象包装在事务代理中。这个目标对象一般是一个普通Javabean。当我们定义TransactionProxyFactoryBean时,必须提供一个相关的PlatformTransactionManager的引用和事务属性。事务属性含有事务定义。例如:
PROPAGATION_REQUIRED,-MyCheckedException
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED,readOnly
事务代理会实现目标对象的接口:这里是属性名是target的引用。id是transactionServiceControl。(• 使用CGLIB也可以实现具体类的代理。只要设置proxyTargetClass属性为true即可。如果目标对象没有实现任何接口,这将自动设置该属性为true。通常,我们希望面向接口编程。)• 使用proxyInterfaces属性来限定事务代理来代理指定接口也是可以。• 也可以通过从org.springframework.aop.framework.ProxyConfig继承或所有AOP代理工厂共享的属性来定制TransactionProxyFactoryBean行为。
然后,说说属性名是transactionAttributes意义:
这里的transactionAttributes属性是定义在org.springframework.transaction.interceptor.NameMathTransactionAttributeSource中的属性格式设置。这个包括通配符的方法名称映射是很直观的,如”insert*”。注意insert*的映射的值包括回滚规则。”-MyCheckException”指定如果方法抛出MyCheckException或它的子类,事务会自动回滚。可以用逗号分隔多个回滚规则。“-”前缀强制回滚,“+”前缀指定提交(这允许即使抛出unchecked异常时也可以提交事务)。“PROPAGATION_REQUIRED”指定事务传播范围。
TransactionProxyFactoryBean允许你通过“preInterceptors”和“postInterceptors”属性设置前或后的拦截操作。可以设置任意数量的前和后通过,它们的类型可以是Advistor(切入点),MethodInterceptor或被当前Spring配置支持的通知类型。例如:ThrowAdvice,AfterReturningAdvice或BeforeAdvice。这些通知必须支持实例共享模式。如果你需要高级AOP特性操作事务,通过org.springframework.aop.framework.ProxyFactoryBean,而不是TransactionProxyFactory实用代理创建者。
② 另一种声明方式:BeanNameAutoProxyCreator
使用TransactionProxyFactoryBean当事务代理包装对象,你可以完全控制代理。如果需要用一致方式包装大量bean。使用一个BeanFactoryPostProcessor的一个实现,BeanNameAutoProxyCreator,可以提供另外一种方法。(Spring中,一旦ApplicationContext读完它的初始化信息,它将初始化所有实现BeanPostProcessor接口的bean,并且让它们后处理ApplicationContext中所有其他的bean。所以使用这种机制,正确配置的BeanNameAutoProxyCreator可以用来后处理所有ApplicationContext中所有其他的bean),并且把它们用事务代理包装起来。真正生成的事务代理和使用TransactionProxyFactoryBean生成的基本一致。
最后,总结一下Spring的优点:
Spring是java平台上的一个开源应用框架。 Spring框架本身并没有强制实行任何特别的编程模式。在Java社区里,Spring作为EJB模型之外的另外一个选择甚至是替代品而广为流行。从设计上看,Spring给予了Java程序员许多的自由度,但同时对业界常见的问题也提供了良好的文档和易于使用的方法。 Spring框架的核心功能在任何Java应用中都是适用的。在基于Java企业平台上的web应用中,大量的拓展和改进得以形成。为此,Spring获得了广泛的欢迎,并被许多公司认可为具有战略意义的重要框架。
Spring工作原理(title1)
内部最核心的就是IOC了,
动态注入,让一个对象的创建不用new了,可以自动的生产,这其实就是利用java里的反射
反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,跟xml Spring的配置
文件来动态的创建对象,和调用对象里的方法的 。
Spring还有一个核心就是AOP这个就是面向切面编程,可以为某一类对象 进行监督和控制(也就是
在调用这类对象的具体方法的前后去调用你指定的 模块)从而达到对一个模块扩充的功能。这些都是通过
配置类达到的。
Spring目的:就是让对象与对象(模块与模块)之间的关系没有通过代码来关联,都是通过配置类说明
管理的(Spring根据这些配置 内部通过反射去动态的组装对象)
要记住:Spring是一个容器,凡是在容器里的对象才会有Spring所提供的这些服务和功能。
Spring里用的最经典的一个设计模式就是:模板方法模式。(这里我都不介绍了,是一个很常用的设计模式)
Spring里的配置是很多的,很难都记住,但是Spring里的精华也无非就是以上的两点,把以上两点跟理解了 也就基本上掌握了Spring.
Spring AOP与IOC
一、 IoC(Inversion of control): 控制反转
1、IoC:
概念:控制权由对象本身转向容器;由容器根据配置文件去创建实例并创建各个实例之间的依赖关系
核心:bean工厂;在Spring中,bean工厂创建的各个实例称作bean
二、AOP(Aspect-Oriented Programming): 面向方面编程
1、 代理的两种方式:
静态代理:
针对每个具体类分别编写代理类;
针对一个接口编写一个代理类;
动态代理:
针对一个方面编写一个InvocationHandler,然后借用JDK反射包中的Proxy类为各种接口动态生成相应的代理类
Spring 优缺点
它是一个开源的项目,而且目前非常活跃;它基于IoC(Inversion of Control,反向控制)和AOP的构架多层j2ee系统的框架,但它不强迫你必须在每一层 中必须使用Spring,因为它模块化的很好,允许你根据自己的需要选择使用它的某一个模块;它实现了很优雅的MVC,对不同的数据访问技术提供了统一的 接口,采用IoC使得可以很容易的实现bean的装配,提供了简洁的AOP并据此实现Transcation Managment,等等
优点
a. Spring能有效地组织你的中间层对象,不管你是否选择使用了EJB。如果你仅仅使用了Struts或其他为J2EE的 API特制的framework,Spring致力于解决剩下的问题。
b. Spring能消除在许多工程中常见的对Singleton的过多使用。根据我的经验,这是一个很大的问题,它降低了系统的可测试性和面向对象的程度。
c. 通过一种在不同应用程序和项目间一致的方法来处理配置文件,Spring能消除各种各样自定义格式的属性文件的需要。曾经对某个类要寻找的是哪个魔法般的属性项或系统属性感到不解,为此不得不去读Javadoc甚至源编码?有了Spring,你仅仅需要看看类的JavaBean属性。Inversion of Control的使用(在下面讨论)帮助完成了这种简化。
d. 通过把对接口编程而不是对类编程的代价几乎减少到没有,Spring能够促进养成好的编程习惯。
e. Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。
f. 使用Spring构建的应用程序易于单元测试。
g. Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码。
h. Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适用于许多web应用。例如,Spring能使用AOP提供声明性事务管理而不通过EJB容器,如果你仅仅需要与单个数据库打交道,甚至不需要一个JTA实现。
i. Spring为数据存取提供了一个一致的框架,不论是使用的是JDBC还是O/R mapping产品(如Hibernate)。
Spring确实使你能通过最简单可行的解决办法来解决你的问题。而这是有有很大价值的。
缺点:jsp中要写很多代码、控制器过于灵活,缺少一个公用控制器
1、spring原理(title2)
spring的最大作用ioc/di,将类与类的依赖关系写在配置文件中,程序在运行时根据配置文件动态加载依赖的类,降低的类与类之间的藕合度。它的原理是在applicationContext.xml加入bean标记,在bean标记中通过class属性说明具体类名、通过property标签说明该类的属性名、通过constructor-args说明构造子的参数。其一切都是反射,当通过applicationContext.getBean(“id名称”)得到一个类实例时,就是以bean标签的类名、属性名、构造子的参数为准,通过反射实例对象,唤起对象的set方法设置属性值、通过构造子的newInstance实例化得到对象。正因为spring一切都是反射,反射比直接调用的处理速度慢,所以这也是spring的一个问题。
spring第二大作用就是aop,其机理来自于代理模式,代理模式有三个角色分别是通用接口、代理、真实对象。代理、真实对象实现的是同一接口,将真实对象作为代理的一个属性,向客户端公开的是代理,当客户端调用代理的方法时,代理找到真实对象,调用真实对象方法,在调用之前之后提供相关的服务,如事务、安全、日志。其名词分别是代理、真实对象、装备、关切点、连接点。
2、动态代理:不用写代理类,虚拟机根据真实对象实现的接口产生一个类,通过类实例化一个动态代理,在实例化动态代理时将真实对象及装备注入到动态代理中,向客户端公开的是动态代理,当客户端调用动态代理方法时,动态代理根据类的反射得到真实对象的Method,调用装备的invoke方法,将动态代理、Method、方法参数传与装备的invoke方法,invoke方法在唤起method方法前或后做一些处理。
1、产生动态代理的类:
java.lang.refect.Proxy
2、装备必须实现InvocationHandler接口实现invoke方法
3、反射
通过类说明可以得到类的父类、实现的接口、内部类、构造函数、方法、属性并可以根据构造器实例化一个对象,唤起一个方法,取属性值,改属性值。
如何得到一个类说明?
Class cls=类.class;
Class cls=对象.getClass();
Class.forName(“类路径”);
如何得到一个方法并唤起它?
Class cls=类.class;
Constructor cons=cls.getConstructor(new Class[]{String.class});
Object obj=cons.newInstance(new Object[]{“aaa”});
Method method=cls.getMethod(“方法名”,new Class[]{String.class,Integer.class});
method.invoke(obj,new Object[]{“aa”,new Integer(1)});
4、spring的三种注入方式是什么?
setter
interface
constructor
5、spring的核心接口及核类配置文件是什么?
FactoryBean:工厂bean主要实现ioc/di
ApplicationContext ac=new FileXmlApplicationContext(“applicationContext.xml”);
Object obj=ac.getBean(“id值”);
applicationContext.xml
spring设计理念与模式
Spring的骨骼架构
Spring总共有十几个组件,但是真正核心的组件只有几个,下面是Spring框架的总体架构图:
从上图中可以看出Spring框架中的核心组件只有三个:Core、Context和Beans。它们构建起了整个Spring的骨骼架构。没有它们就不可能有AOP、Web等上层的特性功能。下面也将主要从这三个组件入手分析Spring。
Spring的设计理念
前面介绍了Spring的三个核心组件,如果再在它们三个中选出核心的话,那就非Beans组件莫属了,为何这样说,其实Spring就是面向Bean的编程(BOP,Bean Oriented Programming),Bean在Spring 中才是真正的主角。
Bean在Spring中作用就像Object对OOP的意义一样,没有对象的概念就像没有面向对象编程,Spring中没有Bean也就没有Spring存在的意义。就像一次演出舞台都准备好了但是却没有演员一样。为什 么要Bean这种角色Bean或者为何在Spring如此重要,这由Spring框架的设计目标决定,Spring为何如此流行,我们用Spring的原因是什么,想想你会发现原来Spring解决了一个非常关键的问题他可以让 你把对象之间的依赖关系转而用配置文件来管理,也就是他的依赖注入机制。而这个注入关系在一个叫Ioc容器中管理,那Ioc容器中有又是什么就是被Bean包裹的对象。Spring正是通过把对象包装在 Bean中而达到对这些对象管理以及一些列额外操作的目的。
它这种设计策略完全类似于Java实现OOP的设计理念,当然了Java本身的设计要比Spring复杂太多太多,但是都是构建一个数据结构,然后根据这个数据结构设计他的生存环境,并让它在这个环境中 按照一定的规律在不停的运动,在它们的不停运动中设计一系列与环境或者与其他个体完成信息交换。这样想来回过头想想我们用到的其他框架都是大慨类似的设计理念。
核心组件如何协同工作
前面说Bean是Spring中关键因素,那Context和Core又有何作用呢?前面吧Bean比作一场演出中的演员的话,那Context就是这场演出的舞台背景,而Core应该就是演出的道具了。只有他们在一起才能 具备能演出一场好戏的最基本的条件。当然有最基本的条件还不能使这场演出脱颖而出,还要他表演的节目足够的精彩,这些节目就是Spring能提供的特色功能了。
我们知道Bean包装的是Object,而Object必然有数据,如何给这些数据提供生存环境就是Context要解决的问题,对Context来说他就是要发现每个Bean之间的关系,为它们建立这种关系并且要维护好 这种关系。所以Context就是一个Bean关系的集合,这个关系集合又叫Ioc容器,一旦建立起这个Ioc容器后Spring就可以为你工作了。那Core组件又有什么用武之地呢?其实Core就是发现、建立和维护每 个Bean之间的关系所需要的一些列的工具,从这个角度看来,Core这个组件叫Util更能让你理解。
它们之间可以用下图来表示:
核心组件详解
这里将详细介绍每个组件内部类的层次关系,以及它们在运行时的时序顺序。我们在使用Spring是应该注意的地方。
Bean组件
前面已经说明了Bean组件对Spring的重要性,下面看看Bean这个组件式怎么设计的。Bean组件在Spring的org.springframework.beans包下。这个包下的所有类主要解决了三件事:Bean的定义、Bean 的创建以及对Bean的解析。对Spring的使用者来说唯一需要关心的就是Bean的创建,其他两个由Spring在内部帮你完成了,对你来说是透明的。
SpringBean的创建时典型的工厂模式,他的顶级接口是BeanFactory,下图是这个工厂的继承层次关系:
BeanFactory有三个子类:ListableBeanFactory、HierarchicalBeanFactory和Autowire Capable Bean Factory。但是从上图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,他实 现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有他使用的场合,它主要是为了区分在Spring内部在操作过程中对象的传递和转化过程中,对对象的 数据访问所做的限制。例如ListableBeanFactory接口表示这些Bean是可列表的,而HierarchicalBeanFactory表示的是这些Bean是有继承关系的,也就是每个Bean有可能有父Bean。 AutowireCapableBeanFactory接口定义Bean的自动装配规则。这四个接口共同定义了Bean的集合、Bean之间的关系、以及Bean行为。
Bean的定义主要有BeanDefinition描述,如下图说明了这些类的层次关系:
Bean的定义就是完整的描述了在Spring的配置文件中你定义的节点中所有的信息,包括各种子节点。当Spring成功解析你定义的一个节点后,在Spring的内部他就被转化 成BeanDefinition对象。以后所有的操作都是对这个对象完成的。
Bean的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过 下图中的类完成:
当然还有具体对tag的解析这里并没有列出。
Context组件
Context在Spring的org.springframework.context包下,前面已经讲解了Context组件在Spring中的作用,他实际上就是给Spring提供一个运行时的环境,用以保存各个对象的状态。下面看一下这个 环境是如何构建的。
ApplicationContext是Context的顶级父类,他除了能标识一个应用环境的基本信息外,他还继承了五个接口,这五个接口主要是扩展了Context的功能。下面是Context的类结构图:
从上图中可以看出ApplicationContext继承了BeanFactory,这也说明了Spring容器中运行的主体对象是Bean,另外ApplicationContext继承了ResourceLoader接口,使得ApplicationContext可以访 问到任何外部资源,这将在Core中详细说明。
ApplicationContext的子类主要包含两个方面:
ConfigurableApplicationContext表示该Context是可修改的,也就是在构建Context中用户可以动态添加或修改已有的配置信息,它下面又有多个子类,其中最经常使用的是可更新的Context,即 AbstractRefreshableApplicationContext类。
WebApplicationContext顾名思义,就是为web准备的Context他可以直接访问到ServletContext,通常情况下,这个接口使用的少。
再往下分就是按照构建Context的文件类型,接着就是访问Context的方式。这样一级一级构成了完整的Context等级层次。
总体来说ApplicationContext必须要完成以下几件事:
◆标识一个应用环境
◆利用BeanFactory创建Bean对象
◆保存对象关系表
◆能够捕获各种事件
Context作为Spring的Ioc容器,基本上整合了Spring的大部分功能,或者说是大部分功能的基础。
Core组件
Core组件作为Spring的核心组件,他其中包含了很多的关键类,其中一个重要组成部分就是定义了资源的访问方式。这种把所有资源都抽象成一个接口的方式很值得在以后的设计中拿来学习。下面就 重要看一下这个部分在Spring的作用。
下图是Resource相关的类结构图:
从上图可以看出Resource接口封装了各种可能的资源类型,也就是对使用者来说屏蔽了文件类型的不同。对资源的提供者来说,如何把资源包装起来交给其他人用这也是一个问题,我们看到Resource 接口继承了InputStreamSource接口,这个接口中有个getInputStream方法,返回的是InputStream类。这样所有的资源都被可以通过InputStream这个类来获取,所以也屏蔽了资源的提供者。另外还有一 个问题就是加载资源的问题,也就是资源的加载者要统一,从上图中可以看出这个任务是由ResourceLoader接口完成,他屏蔽了所有的资源加载者的差异,只需要实现这个接口就可以加载所有的资源, 他的默认实现是DefaultResourceLoader。
下面看一下Context和Resource是如何建立关系的?首先看一下他们的类关系图:
从上图可以看出,Context是把资源的加载、解析和描述工作委托给了ResourcePatternResolver类来完成,他相当于一个接头人,他把资源的加载、解析和资源的定义整合在一起便于其他组件使用。 Core组件中还有很多类似的方式。
Ioc容器如何工作
前面介绍了Core组件、Bean组件和Context组件的结构与相互关系,下面这里从使用者角度看一下他们是如何运行的,以及我们如何让Spring完成各种功能,Spring到底能有那些功能,这些功能是如 何得来的,下面介绍。
如何创建BeanFactory工厂
正如图2描述的那样,Ioc容器实际上就是Context组件结合其他两个组件共同构建了一个Bean关系网,如何构建这个关系网?构建的入口就在AbstractApplicationContext类的refresh方法中。这个方 法的代码如下:
清单1.AbstractApplicationContext.refresh
- public void refresh() throws BeansException, IllegalStateException {
- synchronized (this.startupShutdownMonitor) {
- // Prepare this context for refreshing.
- prepareRefresh();
- // Tell the subclass to refresh the internal bean factory.
- ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
- // Prepare the bean factory for use in this context.
- prepareBeanFactory(beanFactory);
- try {
- // Allows post- processing of the bean factory in context subclasses.
- postProcessBeanFactory(beanFactory);
- // Invoke factory processors registered as beans in& nbsp;the context.
- invokeBeanFactoryPostProcessors(beanFactory);
- // Register bean processors that intercept bean crea tion.
- registerBeanPostProcessors (beanFactory);
- // Initialize message source for this context.
- initMessageSource();
- // Initialize event multicaster for this context.
- initApplicationEventMulticaster();
- // Initialize other special beans in specific contex t subclasses.
- onRefresh();
- // Check for listener beans and register them.
- registerListeners();
- // Instantiate all remaining (non-lazy-init) singletons.
- finishBeanFactoryInitialization (beanFactory);
- // Last step: publish corresponding event.
- finishRefresh();
- }
- catch (BeansException ex) {
- // Destroy already created singletons to avoid dangl ing resources.
- destroyBeans();
- // Reset 'active' flag.
- cancelRefresh(ex);
- // Propagate exception to caller.
- throw ex;
- }
- }
- }
这个方法就是构建整个Ioc容器过程的完整的代码,了解了里面的每一行代码基本上就了解大部分Spring的原理和功能了。
这段代码主要包含这样几个步骤:
◆构建BeanFactory,以便于产生所需的“演员”
◆注册可能感兴趣的事件
◆创建Bean实例对象
◆触发被监听的事件
下面就结合代码分析这几个过程。
第二三句就是在创建和配置BeanFactory。这里是refresh也就是刷新配置,前面介绍了Context有可更新的子类,这里正是实现这个功能,当BeanFactory已存在是就更新,如果没有就新创建。下面是 更新BeanFactory的方法代码:
清单2. AbstractRefreshableApplicationContext. refreshBeanFactory
- protected final void refreshBeanFactory() throws BeansException {
- if (hasBeanFactory()) {
- destroyBeans();
- closeBeanFactory();
- }
- try {
- DefaultListableBeanFactory beanFactory = createBeanFactory();
- beanFactory.setSerializationId(getId());
- customizeBeanFactory(beanFactory);
- loadBeanDefinitions(beanFactory);
- synchronized (this.beanFactoryMonitor) {
- this.beanFactory = beanFactory;
- }
- }
- catch (IOException ex) {
- throw new ApplicationContextException(
- "I/O error& nbsp;parsing bean definition source for "
- + getDisplayName (), ex);
- }
- }
这个方法实现了AbstractApplicationContext的抽象方法refreshBeanFactory,这段代码清楚的说明了BeanFactory的创建过程。注意BeanFactory对象的类型的变化,前 面介绍了他有很多子类,在什么情况下使用不同的子类这非常关键。BeanFactory的原始对象是DefaultListableBeanFactory,这个非常关键,因为他设计到后面对这个对象的多种操作,下面看一下这个 类的继承层次类图:
图10.DefaultListableBeanFactory类继承关系图
从这个图中发现除了BeanFactory相关的类外,还发现了与Bean的register相关。这在refreshBeanFactory方法中有一行loadBeanDefinitions(beanFactory)将找到答案,这个方法将开始加载、解析 Bean的定义,也就是把用户定义的数据结构转化为Ioc容器中的特定数据结构。
这个过程可以用下面时序图解释:
Bean的解析和登记流程时序图如下:
创建好BeanFactory后,接下去添加一些Spring本身需要的一些工具类,这个操作在AbstractApplicationContext的prepareBeanFactory方法完成。
AbstractApplicationContext中接下来的三行代码对Spring的功能扩展性起了至关重要的作用。前两行主要是让你现在可以对已经构建的BeanFactory的配置做修改,后面一行就是让你可以对以后再 创建Bean的实例对象时添加一些自定义的操作。所以他们都是扩展了Spring的功能,所以我们要学习使用Spring必须对这一部分搞清楚。
其中在invokeBeanFactoryPostProcessors方法中主要是获取实现BeanFactoryPostProcessor接口的子类。并执行它的postProcessBeanFactory方法,这个方法的声明如下:
清单3.BeanFactoryPostProcessor.postProcessBeanFactory
- void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
- throws BeansException;
它的参数是beanFactory,说明可以对beanFactory做修改,这里注意这个beanFactory是ConfigurableListableBeanFactory类型的,这也印证了前面介绍的不同BeanFactory所使用的场合不同,这里 只能是可配置的BeanFactory,防止一些数据被用户随意修改。
registerBeanPostProcessors方法也是可以获取用户定义的实现了BeanPostProcessor接口的子类,并执行把它们注册到BeanFactory对象中的beanPostProcessors变量中。BeanPostProcessor中声明 了两个方法:postProcessBeforeInitialization、postProcessAfterInitialization分别用于在Bean对象初始化时执行。可以执行用户自定义的操作。
后面的几行代码是初始化监听事件和对系统的其他监听者的注册,监听者必须是ApplicationListener的子类。
如何创建Bean实例并构建Bean的关系网
下面就是Bean的实例化代码,是从finishBeanFactoryInitialization方法开始的。
清单4.AbstractApplicationContext.finishBeanFactoryInitialization
- protected void finishBeanFactoryInitialization(
- ConfigurableListableBeanFactory beanFactory) {
- // Stop using the temporary ClassLoader for type matching.
- beanFactory.setTempClassLoader(null);
- // Allow for caching all bean definition metadata, not expecting further changes .
- beanFactory.freezeConfiguration();
- // Instantiate all remaining (non-lazy-init) singletons.
- beanFactory.preInstantiateSingletons();
- }
从上面代码中可以发现Bean的实例化是在BeanFactory中发生的。preInstantiateSingletons方法的代码如下:
清单5.DefaultListableBeanFactory.preInstantiateSingletons
- public void preInstantiateSingletons() throws BeansException {
- if (this.logger.isInfoEnabled()) {
- this.logger.info("Pre- instantiating singletons in " + this);
- }
- synchronized (this.beanDefinitionMap) {
- for (String beanName : this.beanDefinitionNames) {
- RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
- if (!bd.isAbstract() && bd.isSingleton()
- && !bd.isLazyInit()) {
- if (isFactoryBean(beanName)) {
- final FactoryBean factory =
- (FactoryBean) getBean(FACTORY_BEAN_PREFIX+ beanName);
- boolean isEagerInit;
- if (System.getSecurityManager() != null
- && ;factory instanceof SmartFactoryBean) {
- isEagerInit = AccessController.doPrivileged(
- &nb sp; new PrivilegedAction<Boolean>() {
- &nb sp; public Boolean run() {
- return ((SmartFactoryBean) factory).isEagerInit();
- &nb sp; }
- }, getAcce ssControlContext());
- }
- else {
- isEagerInit = factory instanceof SmartFactoryBean
- &nb sp; && ((SmartFactoryBean) factory).isEagerInit();
- }
- if (isEagerInit) {
- getBean (beanName);
- }
- }
- else {
- getBean(beanName);
- }
- }
- }
- }
- }
这里出现了一个非常重要的Bean——FactoryBean,可以说Spring一大半的扩展的功能都与这个Bean有关,这是个特殊的Bean他是个工厂Bean,可以产生Bean的Bean,这里的产生Bean是指 Bean的实例,如果一个类继承FactoryBean用户可以自己定义产生实例对象的方法只要实现他的getObject方法。然而在Spring内部这个Bean的实例对象是FactoryBean,通过调用这个对象的getObject方 法就能获取用户自定义产生的对象,从而为Spring提供了很好的扩展性。Spring获取FactoryBean本身的对象是在前面加上&来完成的。
如何创建Bean的实例对象以及如何构建Bean实例对象之间的关联关系式Spring中的一个核心关键,下面是这个过程的流程图。
如果是普通的Bean就直接创建他的实例,是通过调用getBean方法。下面是创建Bean实例的时序图:
还有一个非常重要的部分就是建立Bean对象实例之间的关系,这也是Spring框架的核心竞争力,何时、如何建立他们之间的关系请看下面的时序图:
Ioc容器的扩展点
现在还有一个问题就是如何让这些Bean对象有一定的扩展性,就是可以加入用户的一些操作。那么有哪些扩展点呢?Spring又是如何调用到这些扩展点的?
对Spring的Ioc容器来说,主要有这么几个。BeanFactoryPostProcessor,BeanPostProcessor。他们分别是在构建BeanFactory和构建Bean对象时调用。还有就是InitializingBean和DisposableBean 他们分别是在Bean实例创建和销毁时被调用。用户可以实现这些接口中定义的方法,Spring就会在适当的时候调用他们。还有一个是FactoryBean他是个特殊的Bean,这个Bean可以被用户更多的控制。
这些扩展点通常也是我们使用Spring来完成我们特定任务的地方,如何精通Spring就看你有没有掌握好Spring有哪些扩展点,并且如何使用他们,要知道如何使用他们就必须了解他们内在的机理。可 以用下面一个比喻来解释。
我们把Ioc容器比作一个箱子,这个箱子里有若干个球的模子,可以用这些模子来造很多种不同的球,还有一个造这些球模的机器,这个机器可以产生球模。那么他们的对应关系就是BeanFactory就是 那个造球模的机器,球模就是Bean,而球模造出来的球就是Bean的实例。那前面所说的几个扩展点又在什么地方呢?BeanFactoryPostProcessor对应到当造球模被造出来时,你将有机会可以对其做出设 当的修正,也就是他可以帮你修改球模。而InitializingBean和DisposableBean是在球模造球的开始和结束阶段,你可以完成一些预备和扫尾工作。BeanPostProcessor就可以让你对球模造出来的球做出 适当的修正。最后还有一个FactoryBean,它可是一个神奇的球模。这个球模不是预先就定型了,而是由你来给他确定它的形状,既然你可以确定这个球模型的形状,当然他造出来的球肯定就是你想要的 球了,这样在这个箱子里尼可以发现所有你想要的球
Ioc容器如何为我所用
前面的介绍了Spring容器的构建过程,那Spring能为我们做什么,Spring的Ioc容器又能做什么呢?我们使用Spring必须要首先构建Ioc容器,没有它Spring无法工作,ApplicatonContext.xml就是Ioc 容器的默认配置文件,Spring的所有特性功能都是基于这个Ioc容器工作的,比如后面要介绍的AOP。
Ioc它实际上就是为你构建了一个魔方,Spring为你搭好了骨骼架构,这个魔方到底能变出什么好的东西出来,这必须要有你的参与。那我们怎么参与?这就是前面说的要了解Spring中那有些扩展点 ,我们通过实现那些扩展点来改变Spring的通用行为。至于如何实现扩展点来得到我们想要的个性结果,Spring中有很多例子,其中AOP的实现就是Spring本身实现了其扩展点来达到了它想要的特性功能 ,可以拿来参考。
Spring中AOP特性详解
动态代理的实现原理
要了解Spring的AOP就必须先了解的动态代理的原理,因为AOP就是基于动态代理实现的。动态代理还要从JDK本身说起。
在Jdk的java.lang.reflect包下有个Proxy类,它正是构造代理类的入口。这个类的结构入下:
从上图发现最后面四个是公有方法。而最后一个方法newProxyInstance就是创建代理对象的方法。这个方法的源码如下:
清单6.Proxy.newProxyInstance
- public static Object newProxyInstance(ClassLoader loader,
- Class> [] interfaces,
- InvocationHandler h)
- throws IllegalArgumentException {
- if (h == null) {
- throw new NullPointerException();
- }
- Class cl = getProxyClass (loader, interfaces);
- try {
- Constructor cons = cl.getConstructor(constructorParams);
- return (Object) cons.newInstance(new Object[] { h });
- } catch (NoSuchMethodException e) {
- throw new InternalError(e.toString());
- } catch (IllegalAccessException e) {
- throw new InternalError(e.toString());
- } catch (InstantiationException e) {
- throw new InternalError(e.toString());
- } catch (InvocationTargetException e) {
- throw new InternalError(e.toString());
- }
- }
这个方法需要三个参数:ClassLoader,用于加载代理类的Loader类,通常这个Loader和被代理的类是同一个Loader类。Interfaces,是要被代理的那些那些接口。InvocationHandler,就是用于执行 除了被代理接口中方法之外的用户自定义的操作,他也是用户需要代理的最终目的。用户调用目标方法都被代理到InvocationHandler类中定义的唯一方法invoke中。这在后面再详解。
下面还是看看Proxy如何产生代理类的过程,他构造出来的代理类到底是什么样子?下面揭晓啦。
其实从上图中可以发现正在构造代理类的是在ProxyGenerator的generateProxyClass的方法中。ProxyGenerator类在sun.misc包下,感兴趣的话可以看看他的源码。
假如有这样一个接口,如下:
清单7.SimpleProxy类
- public interface SimpleProxy {
- public void simpleMethod1();
- public void simpleMethod2();
- }
代理来生成的类结构如下:
清单 8.$Proxy2类
- public class $Proxy2 extends java.lang.reflect.Proxy implements SimpleProxy{
- java.lang.reflect.Method m0;
- java.lang.reflect.Method m1;
- java.lang.reflect.Method m2;
- java.lang.reflect.Method m3;
- java.lang.reflect.Method m4;
- int hashCode();
- boolean equals(java.lang.Object);
- java.lang.String toString();
- void simpleMethod1();
- void simpleMethod2();
- }
这个类中的方法里面将会是调用InvocationHandler的invoke方法,而每个方法也将对应一个属性变量,这个属性变量m也将传给invoke方法中的Method参数。整个代理就是这样实现的。
SpringAOP如何实现
从前面代理的原理我们知道,代理的目的是调用目标方法时我们可以转而执行InvocationHandler类的invoke方法,所以如何在InvocationHandler上做文章就是Spring实现Aop的关键所在。
Spring的Aop实现是遵守Aop联盟的约定。同时Spring又扩展了它,增加了如Pointcut、Advisor等一些接口使得更加灵活。
下面是Jdk动态代理的类图:
上图清楚的显示了Spring引用了Aop Alliance定义的接口。姑且不讨论Spring如何扩展Aop Alliance,先看看Spring如何实现代理类的,要实现代理类在Spring的配置文件中通常是这样定一个Bean的 ,如下:
清单9.配置代理类Bean
- <bean id="testBeanSingleton"
- class=value>
- property>
- <property name="target"><"singleton"><value>truevalue>property>
- <property name=<span class="" attribute-"="" style="margin: 0px; padding: 0px; border: none;">"interceptorNames">
- <list>
- <value>testInterceptorvalue>
- <value>testInterceptor2value>
- list>
- property>
- bean>
配置上看到要设置被代理的接口,和接口的实现类也就是目标类,以及拦截器也就在执行目标方法之前被调用,这里Spring中定义的各种各样的拦截器,可以选择使用。
下面看看Spring如何完成了代理以及是如何调用拦截器的。
前面提到Spring Aop也是实现其自身的扩展点来完成这个特性的,从这个代理类可以看出它正是继承了Factory Bean的ProxyFactoryBean,FactoryBean之所以特别就在它可以让你自定义对象的创建 方法。当然代理对象要通过Proxy类来动态生成。
下面是Spring创建的代理对象的时序图:
Spring创建了代理对象后,当你调用目标对象上的方法时,将都会被代理到InvocationHandler类的invoke方法中执行,这在前面已经解释。在这里JdkDynamicAopProxy类实现了InvocationHandler接 口。
下面再看看Spring是如何调用拦截器的,下面是这个过程的时序图:
以上所说的都是Jdk动态代理,Spring还支持一种CGLIB类代理,感兴趣自己看吧。
Spring中设计模式分析
Spring中使用的设计模式也很多,比如工厂模式、单例模式、模版模式等,在《Webx框架的系统架构与设计模式》、《Tomcat的系统架构与模式设计分析》已经有介绍,这里就不赘述了。这里主要介 绍代理模式和策略模式。
代理模式
代理模式原理
代理模式就是给某一个对象创建一个代理对象,而由这个代理对象控制对原对象的引用,而创建这个代理对象就是可以在调用原对象是可以增加一些额外的操作。下面是代理模式的结构:
Subject:抽象主题,它是代理对象的真实对象要实现的接口,当然这可以是多个接口组成。
ProxySubject:代理类除了实现抽象主题定义的接口外,还必须持有所代理对象的引用
RealSubject:被代理的类,是目标对象。
Spring中如何实现代理模式
Spring Aop中Jdk动态代理就是利用代理模式技术实现的。在Spring中除了实现被代理对象的接口外,还会有org.springframework.aop.SpringProxy和org.springframework.aop.framework.Advised 两个接口。Spring中使用代理模式的结构图如下:
$Proxy就是创建的代理对象,而Subject是抽象主题,代理对象是通过InvocationHandler来持有对目标对象的引用的。
Spring中一个真实的代理对象结构如下:
清单10代理对象$Proxy4
- public class $Proxy4 extends java.lang.reflect.Proxy implements
- org.springframework.aop.framework.PrototypeTargetTests$TestBean
- org.springframework.aop.SpringProxy
- org.springframework.aop.framework.Advised
- {
- java.lang.reflect.Method m16;
- java.lang.reflect.Method m9;
- java.lang.reflect.Method m25;
- java.lang.reflect.Method m5;
- java.lang.reflect.Method m2;
- java.lang.reflect.Method m23;
- java.lang.reflect.Method m18;
- java.lang.reflect.Method m26;
- java.lang.reflect.Method m6;
- java.lang.reflect.Method m28;
- java.lang.reflect.Method m14;
- java.lang.reflect.Method m12;
- java.lang.reflect.Method m27;
- java.lang.reflect.Method m11;
- java.lang.reflect.Method m22;
- java.lang.reflect.Method m3;
- java.lang.reflect.Method m8;
- java.lang.reflect.Method m4;
- java.lang.reflect.Method m19;
- java.lang.reflect.Method m7;
- java.lang.reflect.Method m15;
- java.lang.reflect.Method m20;
- java.lang.reflect.Method m10;
- java.lang.reflect.Method m1;
- java.lang.reflect.Method m17;
- java.lang.reflect.Method m21;
- java.lang.reflect.Method m0;
- java.lang.reflect.Method m13;
- java.lang.reflect.Method m24;
- int hashCode();
- int indexOf(org.springframework.aop.Advisor);
- int indexOf(org.aopalliance.aop.Advice);
- boolean equals(java.lang.Object);
- java.lang.String toString();
- void sayhello();
- void doSomething();
- void doSomething2();
- java.lang.Class getProxiedInterfaces();
- java.lang.Class getTargetClass();
- boolean isProxyTargetClass();
- org.springframework.aop.Advisor; getAdvisors();
- void addAdvisor(int, org.springframework.aop.Advisor)
- throws org.springframework.aop.framework.AopConfigException;
- void addAdvisor(org.springframework.aop.Advisor)
- throws org.springframework.aop.framework.AopConfigException;
- void setTargetSource(org.springframework.aop.TargetSource);
- org.springframework.aop.TargetSource getTargetSource();
- void setPreFiltered(boolean);
- boolean isPreFiltered();
- boolean isInterfaceProxied(java.lang.Class);
- boolean removeAdvisor(org.springframework.aop.Advisor);
- void removeAdvisor(int)throws org.springframework.aop.framework.AopConfigException;
- boolean replaceAdvisor(org.springframework.aop.Advisor,
- org.springframework.aop.Advisor)
- throws org.springframework.aop.framework.AopConfigException;
- void addAdvice(org.aopalliance.aop.Advice)
- throws org.springframework.aop.framework.AopConfigException;
- void addAdvice(int, org.aopalliance.aop.Advice)
- throws org.springframework.aop.framework.AopConfigException;
- boolean removeAdvice(org.aopalliance.aop.Advice);
- java.lang.String toProxyConfigString();
- boolean isFrozen();
- void setExposeProxy(boolean);
- boolean isExposeProxy();
- }
策略模式
策略模式原理
策略模式顾名思义就是做某事的策略,这在编程上通常是指完成某个操作可能有多种方法,这些方法各有千秋,可能有不同的适应的场合,然而这些操作方法都有可能用到。各一个操作方法都当作一 个实现策略,使用者可能根据需要选择合适的策略。
下面是策略模式的结构:
Context:使用不同策略的环境,它可以根据自身的条件选择不同的策略实现类来完成所要的操作。它持有一个策略实例的引用。创建具体策略对象的方法也可以由他完成。
◆Strategy:抽象策略,定义每个策略都要实现的策略方法
◆ConcreteStrategy:具体策略实现类,实现抽象策略中定义的策略方法
◆Spring中策略模式的实现
◆Spring中策略模式使用有多个地方,如Bean定义对象的创建以及代理对象的创建等。这里主要看一下代理对象创建的策略模式的实现。
前面已经了解Spring的代理方式有两个Jdk动态代理和CGLIB代理。这两个代理方式的使用正是使用了策略模式。它的结构图如下所示:
在上面结构图中与标准的策略模式结构稍微有点不同,这里抽象策略是AopProxy接口,Cglib2AopProxy和JdkDynamicAopProxy分别代表两种策略的实现方式,ProxyFactoryBean就是代表Context角色 ,它根据条件选择使用Jdk代理方式还是CGLIB方式,而另外三个类主要是来负责创建具体策略对象,ProxyFactoryBean是通过依赖的方法来关联具体策略对象的,它是通过调用策略对象的getProxy (ClassLoaderclassLoader)方法来完成操作。
总结
本文通过从Spring的几个核心组件入手,试图找出构建Spring框架的骨骼架构,进而分析Spring在设计的一些设计理念,是否从中找出一些好的设计思想,对我们以后程序设计能提供一些思路。接着 再详细分析了Spring中是如何实现这些理念的,以及在设计模式上是如何使用的。
通过分析Spring给我一个很大的启示就是其这套设计理念其实对我们有很强的借鉴意义,它通过抽象复杂多变的对象,进一步做规范,然后根据它定义的这套规范设计出一个容器,容器中构建它们的 复杂关系,其实现在有很多情况都可以用这种类似的处理方法。
虽然我很想把我对Spring的想法完全阐述清楚,但是所谓“书不尽言,言不尽意。”,有什么不对或者不清楚的地方大家还是看看其源码吧。
Spring MVC 框架搭建及详解
现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了。不过要想灵活运用Spring MVC来应对大多数的Web开发,就必须要掌握它的配置及原理。
一、Spring MVC环境搭建:(Spring 2.5.6 + Hibernate 3.2.0)
1. jar包引入
Spring 2.5.6:spring.jar、spring-webmvc.jar、commons-logging.jar、cglib-nodep-2.1_3.jar
Hibernate 3.6.8:hibernate3.jar、hibernate-jpa-2.0-api-1.0.1.Final.jar、antlr-2.7.6.jar、commons-collections-3.1、dom4j-1.6.1.jar、javassist-3.12.0.GA.jar、jta-1.1.jar、slf4j-api-1.6.1.jar、slf4j-nop-1.6.4.jar、相应数据库的驱动jar包
2. web.xml配置(部分)
<!-- Spring MVC配置 -->
<!-- ====================================== -->
<
servlet
>
<
servlet-name
>spring</
servlet-name
>
<
servlet-class
>org.springframework.web.servlet.DispatcherServlet</
servlet-class
>
<!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.
xml,
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value> 默认
</init-param>
-->
<
load-on-startup
>1</
load-on-startup
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>spring</
servlet-name
>
<
url-pattern
>*.do</
url-pattern
>
</
servlet-mapping
>
<!-- Spring配置 -->
<!-- ====================================== -->
<
listener
>
<
listener-class
>org.springframework.web.context.ContextLoaderListener</
listener-class
>
</
listener
>
<!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 -->
<
context-param
>
<
param-name
>contextConfigLocation</
param-name
>
<
param-value
>classpath:config/applicationContext.xml</
param-value
>
</
context-param
>
3. spring-servlet.xml配置
spring-servlet这个名字是因为上面web.xml中<servlet-name>标签配的值为spring(<servlet-name>spring</servlet-name>),再加上“-servlet”后缀而形成的spring-servlet.xml文件名,如果改为springMVC,对应的文件名则为springMVC-servlet.xml。
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:p
=
"http://www.springframework.org/schema/p"
xmlns:context
=
"http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context <a
href
=
"http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
http://www.springframework.org/schema/context/spring-context-3.0.xsd</
a
>">
<!-- 启用spring mvc 注解 -->
<
context:annotation-config
/>
<!-- 设置使用注解的类所在的jar包 -->
<
context:component-scan
base-package
=
"controller"
></
context:component-scan
>
<!-- 完成请求和注解POJO的映射 -->
<
bean
class
=
"org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
/>
<!-- 对转向页面的路径解析。prefix:前缀, suffix:后缀 -->
<
bean
class
=
"org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix
=
"/jsp/"
p:suffix
=
".jsp"
/>
</
beans
>
4. applicationContext.xml配置
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop
=
"http://www.springframework.org/schema/aop"
xmlns:tx
=
"http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- 采用hibernate.cfg.xml方式配置数据源 -->
<
bean
id
=
"sessionFactory"
class
=
"org.springframework.orm.hibernate3.LocalSessionFactoryBean"
>
<
property
name
=
"configLocation"
>
<
value
>classpath:config/hibernate.cfg.xml</
value
>
</
property
>
</
bean
>
<!-- 将事务与Hibernate关联 -->
<
bean
id
=
"transactionManager"
class
=
"org.springframework.orm.hibernate3.HibernateTransactionManager"
>
<
property
name
=
"sessionFactory"
>
<
ref
local
=
"sessionFactory"
/>
</
property
>
</
bean
>
<!-- 事务(注解 )-->
<
tx:annotation-driven
transaction-manager
=
"transactionManager"
proxy-target-class
=
"true"
/>
<!-- 测试Service -->
<
bean
id
=
"loginService"
class
=
"service.LoginService"
></
bean
>
<!-- 测试Dao -->
<
bean
id
=
"hibernateDao"
class
=
"dao.HibernateDao"
>
<
property
name
=
"sessionFactory"
ref
=
"sessionFactory"
></
property
>
</
bean
>
</
beans
>
二、详解
Spring MVC与Struts从原理上很相似(都是基于MVC架构),都有一个控制页面请求的Servlet,处理完后跳转页面。看如下代码(注解):
package
controller;
import
javax.servlet.http.HttpServletRequest;
import
org.springframework.stereotype.Controller;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestParam;
import
entity.User;
@Controller
//类似Struts的Action
public
class
TestController {
@RequestMapping
(
"test/login.do"
)
// 请求url地址映射,类似Struts的action-mapping
public
String testLogin(
@RequestParam
(value=
"username"
)String username, String password,
HttpServletRequest request) {
// @RequestParam是指请求url地址映射中必须含有的参数(除非属性required=false)
// @RequestParam可简写为:@RequestParam("username")
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password)) {
return
"loginError"
;
// 跳转页面路径(默认为转发),该路径不需要包含spring-servlet配置文件中配置的前缀和后缀
}
return
"loginSuccess"
;
}
@RequestMapping
(
"/test/login2.do"
)
public
ModelAndView testLogin2(String username, String password,
int
age){
// request和response不必非要出现在方法中,如果用不上的话可以去掉
// 参数的名称是与页面控件的name相匹配,参数类型会自动被转换
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password) || age <
5
) {
return
new
ModelAndView(
"loginError"
);
// 手动实例化ModelAndView完成跳转页面(转发),效果等同于上面的方法返回字符串
}
return
new
ModelAndView(
new
RedirectView(
"../index.jsp"
));
// 采用重定向方式跳转页面
// 重定向还有一种简单写法
// return new ModelAndView("redirect:../index.jsp");
}
@RequestMapping
(
"/test/login3.do"
)
public
ModelAndView testLogin3(User user) {
// 同样支持参数为表单对象,类似于Struts的ActionForm,User不需要任何配置,直接写即可
String username = user.getUsername();
String password = user.getPassword();
int
age = user.getAge();
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password) || age <
5
) {
return
new
ModelAndView(
"loginError"
);
}
return
new
ModelAndView(
"loginSuccess"
);
}
@Resource
(name =
"loginService"
)
// 获取applicationContext.xml中bean的id为loginService的,并注入
private
LoginService loginService;
//等价于spring传统注入方式写get和set方法,这样的好处是简洁工整,省去了不必要得代码
@RequestMapping
(
"/test/login4.do"
)
public
String testLogin4(User user) {
if
(loginService.login(user) ==
false
) {
return
"loginError"
;
}
return
"loginSuccess"
;
}
}
package
controller;
import
org.springframework.stereotype.Controller;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping
(
"/test2/login.do"
)
// 指定唯一一个*.do请求关联到该Controller
public
class
TestController2 {
@RequestMapping
public
String testLogin(String username, String password,
int
age) {
// 如果不加任何参数,则在请求/test2/login.do时,便默认执行该方法
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password) || age <
5
) {
return
"loginError"
;
}
return
"loginSuccess"
;
}
@RequestMapping
(params =
"method=1"
, method=RequestMethod.POST)
public
String testLogin2(String username, String password) {
// 依据params的参数method的值来区分不同的调用方法
// 可以指定页面请求方式的类型,默认为get请求
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password)) {
return
"loginError"
;
}
return
"loginSuccess"
;
}
@RequestMapping
(params =
"method=2"
)
public
String testLogin3(String username, String password,
int
age) {
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password) || age <
5
) {
return
"loginError"
;
}
return
"loginSuccess"
;
}
}
package
controller;
import
org.springframework.stereotype.Controller;
import
org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping
(
"/test3/*"
)
// 父request请求url
public
class
TestController3 {
@RequestMapping
(
"login.do"
)
// 子request请求url,拼接后等价于/test3/login.do
public
String testLogin(String username, String password,
int
age) {
if
(!
"admin"
.equals(username) || !
"admin"
.equals(password) || age <
5
) {
return
"loginError"
;
}
return
"loginSuccess"
;
}
}
三、结束语
掌握以上这些Spring MVC就已经有了很好的基础了,几乎可应对与任何开发,在熟练掌握这些后,便可更深层次的灵活运用的技术,如多种视图技术,例如 Jsp、Velocity、Tiles、iText 和 POI。Spring MVC框架并不知道使用的视图,所以不会强迫您只使用 JSP 技术。
spring mvc原理与应用
springMVC工作原理(括号内为相关接口)
SpringMVC主要由调度器(DispatcherServlet)、处理器映射(HanderMapping)、处理器(HandlerAdapter)、拦截器(HandlerInterceptor )、控制器(Controller)、视图解析器(ViewResolver)、视图(View)这几部分构成。下面根据springMVC的工作流程依次介绍以上接口。
一个请求到达服务器,首先经过的是DispacherServlet,它是springMVC的入口,也是核心所在,主要职责是:
1,截获相应请求(具体将在下面配置中讲到).
2,初始化其WebApplicationContext上下文.
3,初始springMVC各个组件,并装配到DispacherServlet.
DispacherServlet拿到请求后,根据配置的处理器映射,将去寻找HanderMapping.HanderMapping有两个实现:
1,SimpleUrlHandlerMapping 通过配置文件,把一个URL映射到Controller
2,DefaultAnnotationHandler
接下下来是根据注解或者配置找到对应的Controller,执行其业务逻辑(若有拦截器,当然先走拦截器,springMVC拦截器类型不同,此处暂不提,下面有讲到)。Controller里面的方法返回值类型有String,ModelAndView,View,json等等。它的返回值包含了响应所需数据和url等信息。
接下来将到达ViewResolver(这个过程是DispacherServlet定位到ViewResolver还是框架既定的步骤?求解),根据配置的ViewResolver将找到对应的View,然后构造response呈递给浏览器。
大致步骤就是这样子。
入门小例子
1,新建一个web项目,这里我们取名为demo,打开web.xml,配置DispacherServlet,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
</web-app>
自定义springmvc配置文件名字:通过为servlet指定init-param实现,如下:
此处指定的文件是web根目录下的叫做springMVC.xml
拦截地址:
好了,接下来就在WEB-INF目录下新建springmvc的配置文件。名字根据以上配置取。我使用的默认配置,所以我的配置文件名字就是:spring-servlet.xml,内容如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
</beans>
来解释下:
- <!--
默认的注解映射的支持 --> -
<mvc:annotation-driven />
-
下面是指定物理目录用的, springmvc将根据controller里面方法的返回值查找物理路径,规则是这样的:
项目路径下/[prefix的value]+[controller的返回值]+[suffix指定的后缀]
比如我的controller里面某个方法返回index,那么,springmvc所找的页面就是:demo/jsp/index.jsp
好了接下来写一个Controller,代码如下:
package com.my.test;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController
{
}
简单得很,能说明大致工作原理就成了。
顺便说几句吧:
这句方法名可以任意,后面的参数,Model是spring封装的一个request对象,把他当成request用没一点问题;后米娜的HttpServletRequest都清楚,不必多说了~其实还可以在后面参数中加入从表单取值的东西,比如这样写:
这句上面有提到,springMVC通过这个返回值来确定需要返回的视图的物理路径,按照我们spring-servlet.xml的配置,框架去找的物理路径就是:/demo/jsp/index.jsp
好吧,物理路径是这个,那么就在这个路径下建一个jsp页面,内容够简洁:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Insert title here</title>
</head>
<body>
</body>
</html>
好了,部署到tomcat,在浏览器中输入:http://localhost:8080/demo/test/dotest/就可以看到以下结果:
配置拓展
关于springMVC以上所说只是一些简单的应用,不能满足实际需求。有一句话叫做什么来着,不重复发明轮子,那么,这里就贴出个链接,个人感觉还可以,有空可以去看看:
http://elf8848.iteye.com/blog/875830;
另外,如果想深入了解,还是去看看源码吧~~虽然比较耗时,不过绝对值得~
spring常用注解
本文汇总了Spring的常用注解,以方便大家查询和使用,具体如下:
使用注解之前要开启自动扫描功能
其中base-package为需要扫描的包(含子包)。
1
|
<context:component-scan base-package="cn.test"/>
|
@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
@Scope注解 作用域
@Lazy(true) 表示延迟初始化
@Service用于标注业务层组件、
@Controller用于标注控制层组件(如struts中的action)
@Repository用于标注数据访问组件,即DAO组件。
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Scope用于指定scope作用域的(用在类上)
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)
@DependsOn:定义Bean初始化及销毁时的顺序
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
@Autowired @Qualifier("personDaoBean") 存在多个实例配合使用
@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
@PostConstruct 初始化注解
@PreDestroy 摧毁注解 默认 单例 启动就加载
@Async异步方法调用,需要添加以下代码:
<bean id=
"taskExecutor"
class
=
"org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
>
<property name=
"corePoolSize"
value=
"10"
/>
<property name=
"maxPoolSize"
value=
"300"
/>
</bean>
Spring Annotation 的简单介绍
1.使用 @Repository、 @Service、 @Controller 和 @Component将类标识为 Bean:Spring 自 2.0 版本开始,陆续引入了一些注解用于简化 Spring 的开发。 @Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。具体只需将该注解标注在 DAO 类上即可。同时,为了让 Spring 能够扫描类路径中的类并识别出 @Repository 注解,需要在 XML 配置文件中启用 Bean 的自动扫描功能,这可以通过<context:component-scan/>
<context:component-scan base-package=”bookstore.dao” />
<context:annotation-config/>
该配置隐式注册了多个对注解进行解析的处理器,如下列AutowiredAnnotationBeanPostProcessor CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor RequiredAnnotationBeanPostProcessor
其实,注解本身做不了任何事情,和XML一样,只起到配置的作用,主要在于背后强大的处理器
如此,我们就不再需要在 XML 中显式使用 <bean/> 进行 Bean 的配置。Spring 在容器初始化时将自动扫描 base-package 指定的包及其子包下的所有 class 文件,所有标注了 @Repository 的类都将被注册为 Spring Bean。
* @Component、@Service、@Constroller,它们分别用于软件系统的不同层次:
* @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。
* @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
* @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。
通过在类上使用 @Repository、 @Component、 @Service 和 @Constroller 注解,Spring 会自动创建相应的 BeanDefinition 对象,并注册到 ApplicationContext 中。这些类就成了 Spring 受管组件。这三个注解除了作用于不同软件层次的类,其使用方式与 @Repository 是完全相同的。
Spring使用注解的机制
当一个 Bean 被自动检测到时,会根据那个扫描器的 BeanNameGenerator 策略生成它的 bean 名称。默认情况下,对于包含 name 属性的 @Component、 @Repository、 @Service 和 @Controller,会把name 取值作为 Bean 的名字。
如果这个注解不包含 name 值或是其他被自定义过滤器发现的组件,默认 Bean 名称会是小写开头的类的简单名称。
与通过 XML 配置的 Spring Bean 一样,通过上述注解标识的 Bean,其默认作用域是"singleton",为了配合这四个注解,在标注 Bean 的同时能够指定 Bean 的作用域,Spring 2.5 引入了 @Scope 注解。使用该注解时只需提供作用域的名称就行了
@Scope("prototype")
@Repository
public class Demo { … }
使用 @PostConstruct 和 @PreDestroy 指定生命周期回调方法:
Spring Bean 是受 Spring IoC 容器管理,由容器进行初始化和销毁的(prototype 类型由容器初始化之后便不受容器管理),通常我们不需要关注容器对 Bean 的初始化和销毁操作,由 Spring 经过构造函数或者工厂方法创建的 Bean 就是已经初始化完成并立即可用的。然而在某些情况下,可能需要我们手工做一些额外的初始化或者销毁操作,这通常是针对一些资源的获取和释放操作
第一种方式:
是实现 Spring 提供的两个接口:InitializingBean 和 DisposableBean。如果希望在 Bean 初始化完成之后执行一些自定义操作,则可以让 Bean 实现 InitializingBean 接口,该接口包含一个 afterPropertiesSet() 方法,容器在为该 Bean 设置了属性之后,将自动调用该方法;如果 Bean 实现了 DisposableBean 接口,则容器在销毁该 Bean 之前,将调用该接口的 destroy() 方法。这种方式的缺点是,让 Bean 类实现 Spring 提供的接口,增加了代码与 Spring 框架的耦合度,因此不推荐使用。
第二种方式是:
在 XML 文件中使用 <bean> 的 init-method 和 destroy-method 属性指定初始化之后和销毁之前的回调方法,代码无需实现任何接口。这两个属性的取值是相应 Bean 类中的初始化和销毁方法,方法名任意,但是方法不能有参数。
<bean id=”userService” class=”bookstore.service.UserService”
init-method=”init” destroy-method=”destroy”>
…
</bean>
第三种方式:
Spring 2.5 在保留以上两种方式的基础上,提供了对 JSR-250 的支持。JSR-250 规范定义了两个用于指定声明周期方法的注解: @PostConstruct 和 @PreDestroy。这两个注解使用非常简单,只需分别将他们标注于初始化之后执行的回调方法或者销毁之前执行的回调方法上。
由于使用了注解,因此需要配置相应的 Bean 后处理器,亦即在 XML 中增加如下一行:
<context:annotation-config />
使用 @Required 进行 Bean 的依赖检查:
依赖检查的作用是,判断给定 Bean 的相应 Setter 方法是否都在实例化的时候被调用了。而不是判断字段是否已经存在值了。Spring 进行依赖检查时,只会判断属性是否使用了 Setter 注入。如果某个属性没有使用 Setter 注入,即使是通过构造函数已经为该属性注入了值,Spring 仍然认为它没有执行注入,从而抛出异常。另外,Spring 只管是否通过 Setter 执行了注入,而对注入的值却没有任何要求,即使注入的 <null/>,Spring 也认为是执行了依赖注入。
@Required 注解只能标注在 Setter 方法之上。因为依赖注入的本质是检查 Setter 方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非 setXxxx() 类型的方法则被忽略。
为了让 Spring 能够处理该注解,需要激活相应的 Bean 后处理器。要激活该后处理器,只需在 XML 中增加如下一行即可:<context:annotation-config/>
当某个被标注了 @Required 的 Setter 方法没有被调用,则 Spring 在解析的时候会抛出异常,以提醒开发者对相应属性进行设置。
使用 @Resource、 @Autowired 和 @Qualifier 指定 Bean 的自动装配策略
自动装配是指,Spring 在装配 Bean 的时候,根据指定的自动装配规则,将某个Bean所需要引用类型的Bean注入进来。
<bean> 元素提供了一个指定自动装配类型的 autowire 属性,该属性有如下选项:
* no -- 显式指定不使用自动装配。
* byName -- 如果存在一个和当前属性名字一致的 Bean,则使用该 Bean 进行注入。如果名称匹配但是类型不匹配,则抛出异常。如果没有匹配的类型,则什么也不做。
* byType -- 如果存在一个和当前属性类型一致的 Bean ( 相同类型或者子类型 ),则使用该 Bean 进行注入。byType 能够识别工厂方法,即能够识别 factory-method 的返回类型。如果存在多个类型一致的 Bean,则抛出异常。如果没有匹配的类型,则什么也不做。
* constructor -- 与 byType 类似,只不过它是针对构造函数注入而言的。如果当前没有与构造函数的参数类型匹配的 Bean,则抛出异常。使用该种装配模式时,优先匹配参数最多的构造函数。
* autodetect -- 根据 Bean 的自省机制决定采用 byType 还是 constructor 进行自动装配。如果 Bean 提供了默认的构造函数,则采用 byType;否则采用 constructor 进行自动装配。
使用 @Resource 和 @Qualifier 注解
如果希望根据 name 执行自动装配,那么应该使用 JSR-250 提供的 @Resource 注解
@Resource 使用 byName 的方式执行自动封装。 @Resource 标注可以作用于带一个参数的 Setter 方法、字段,以及带一个参数的普通方法上。 @Resource 注解有一个 name 属性,用于指定Bean在配置文件中对应的名字。如果没有指定 name 属性,那么默认值就是字段或者属性的名字。
如果 @Resource 没有指定 name 属性,那么使用 byName 匹配失败后,会退而使用 byType 继续匹配,如果再失败,则抛出异常。在没有为 @Resource 注解显式指定 name 属性的前提下,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。此时 name 属性不需要指定 ( 或者指定为""),否则注入失败
Spring常用的接口和类
一、ApplicationContextAware接口
当一个类需要获取ApplicationContext实例时,可以让该类实现ApplicationContextAware接口。代码展示如下:
Java代码
public class Animal implementsApplicationContextAware, BeanNameAware{
private String beanName;
private ApplicationContext applicationContext;
public void setBeanName(String name) {
this.beanName = name;
}
/**
* @param applicationContext 该参数将由Spring容器自动赋值
*/
public void setApplicationContext(ApplicationContextapplicationContext)throws BeansException {
this.applicationContext = applicationContext;
}
public void run(){
System.out.println(beanName);
//发布自定义事件
AnimalEvent event = new AnimalEvent(this, "老虎");
applicationContext.publishEvent(event);
}
}
public class Animal implementsApplicationContextAware, BeanNameAware{
privateString beanName;
privateApplicationContext applicationContext;
publicvoid setBeanName(String name) {
this.beanName= name;
}
/**
* @param applicationContext 该参数将由Spring容器自动赋值
*/
publicvoid setApplicationContext(ApplicationContext applicationContext)throwsBeansException {
this.applicationContext= applicationContext;
}
publicvoid run(){
System.out.println(beanName);
//发布自定义事件
AnimalEventevent = new AnimalEvent(this, "老虎");
applicationContext.publishEvent(event);
}
}
通过@Autowired注解可以自动装配一些常用对象实例:
Java代码
@Autowired
private MessageSource messageSource;
@Autowired
private ResourceLoader resourceLoader;
@Autowired
private ApplicationContextapplicationContext;
@Autowired
private MessageSource messageSource;
@Autowired
private ResourceLoader resourceLoader;
@Autowired
private ApplicationContextapplicationContext;
二、ApplicationEvent抽象类
当需要创建自定义事件时,可以新建一个继承自ApplicationEvent抽象类的类。代码展示如下:
Java代码
/**
* 自定义事件
*/
public class AnimalEvent extendsApplicationEvent {
private String name;
public String getName() {
return name;
}
/**
* @param source 事件源对象
*/
public AnimalEvent(Object source){
super(source);
}
public AnimalEvent(Object source, String name){
super(source);
this.name = name;
}
}
/**
* 自定义事件
*/
public class AnimalEvent extendsApplicationEvent {
privateString name;
publicString getName() {
returnname;
}
/**
* @param source 事件源对象
*/
publicAnimalEvent(Object source){
super(source);
}
publicAnimalEvent(Object source, String name){
super(source);
this.name= name;
}
}
三、ApplicationListener接口
当需要监听自定义事件时,可以新建一个实现ApplicationListener接口的类,并将该类配置到Spring容器中。代码展示如下:
Java代码
/**
* 自定义事件监听器
*/
public class CustomEventListener implementsApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof AnimalEvent){
AnimalEvent animalEvent = (AnimalEvent)event;
System.out.println("触发自定义事件:Animal name is " + animalEvent.getName());
}
}
}
/**
* 自定义事件监听器
*/
public class CustomEventListener implementsApplicationListener {
publicvoid onApplicationEvent(ApplicationEvent event) {
if(eventinstanceof AnimalEvent){
AnimalEventanimalEvent = (AnimalEvent)event;
System.out.println("触发自定义事件:Animal name is " +animalEvent.getName());
}
}
}
Java代码
<!-- 自定义事件监听器:Spring容器自动注册它-->
<bean id="customEventListener"class="com.cjm.spring.CustomEventListener"/>
<!-- 自定义事件监听器:Spring容器自动注册它 -->
<bean id="customEventListener"class="com.cjm.spring.CustomEventListener"/>
要发布自定义事件,需要调用ApplicationContext的publishEvent方法,具体用法请看Animal类的源码。
四、BeanNameAware接口
当bean需要获取自身在容器中的id/name时,可以实现BeanNameAware接口。
五、InitializingBean接口
当需要在bean的全部属性设置成功后做些特殊的处理,可以让该bean实现InitializingBean接口。
效果等同于bean的init-method属性的使用或者@PostContsuct注解的使用。
三种方式的执行顺序:先注解,然后执行InitializingBean接口中定义的方法,最后执行init-method属性指定的方法。
六、DisposableBean接口
当需要在bean销毁之前做些特殊的处理,可以让该bean实现DisposableBean接口。
效果等同于bean的destroy-method属性的使用或者@PreDestory注解的使用。
三种方式的执行顺序:先注解,然后执行DisposableBean接口中定义的方法,最后执行destroy-method属性指定的方法。
Spring常用的接口和类(二)
七、BeanPostProcessor接口
当需要对受管bean进行预处理时,可以新建一个实现BeanPostProcessor接口的类,并将该类配置到Spring容器中。
实现BeanPostProcessor接口时,需要实现以下两个方法:
postProcessBeforeInitialization 在受管bean的初始化动作之前调用
postProcessAfterInitialization 在受管bean的初始化动作之后调用
,容器中的每个Bean在创建时都会恰当地调用它们。代码展示如下:
Java代码
public class CustomBeanPostProcessorimplements BeanPostProcessor {
/**
* 初始化之前的回调方法
*/
public Object postProcessBeforeInitialization(Object bean, StringbeanName)throws BeansException {
System.out.println("postProcessBeforeInitialization: " + beanName);
return bean;
}
/**
* 初始化之后的回调方法
*/
public Object postProcessAfterInitialization(Object bean, StringbeanName)throws BeansException {
System.out.println("postProcessAfterInitialization: " +beanName);
return bean;
}
}
public class CustomBeanPostProcessorimplements BeanPostProcessor {
/**
* 初始化之前的回调方法
*/
publicObject postProcessBeforeInitialization(Object bean, String beanName)throwsBeansException {
System.out.println("postProcessBeforeInitialization:" + beanName);
returnbean;
}
/**
* 初始化之后的回调方法
*/
publicObject postProcessAfterInitialization(Object bean, String beanName)throwsBeansException {
System.out.println("postProcessAfterInitialization:" + beanName);
returnbean;
}
}
Xml代码
<!-- 自定义受管Bean的预处理器:Spring容器自动注册它 -->
<beanid="customBeanPostProcessor"class="com.cjm.spring.CustomBeanPostProcessor"/>
<!-- 自定义受管Bean的预处理器:Spring容器自动注册它 -->
<beanid="customBeanPostProcessor" class="com.cjm.spring.CustomBeanPostProcessor"/>
八、BeanFactoryPostProcessor接口
当需要对Bean工厂进行预处理时,可以新建一个实现BeanFactoryPostProcessor接口的类,并将该类配置到Spring容器中。代码展示如下:
Java代码
public class CustomBeanFactoryPostProcessorimplements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory) throws BeansException {
System.out.println(beanFactory.getClass().getSimpleName());
}
}
public class CustomBeanFactoryPostProcessorimplements BeanFactoryPostProcessor {
publicvoid postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throwsBeansException {
System.out.println(beanFactory.getClass().getSimpleName());
}
}
Xml代码
<!-- 自定义Bean工厂的预处理器:Spring容器自动注册它 -->
<beanid="customBeanFactoryPostProcessor"class="com.cjm.spring.CustomBeanFactoryPostProcessor"/>
<!-- 自定义Bean工厂的预处理器:Spring容器自动注册它 -->
<beanid="customBeanFactoryPostProcessor"class="com.cjm.spring.CustomBeanFactoryPostProcessor"/>
Spring内置的实现类:
1、PropertyPlaceholderConfigurer类
用于读取Java属性文件中的属性,然后插入到BeanFactory的定义中。
Xml代码
<beanid="propertyPlaceholderConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<propertyname="driverClassName"><value>${jdbc.driverClassName}</value></property>
<propertyname="url"><value>${jdbc.url}</value></property>
<propertyname="username"><value>${jdbc.username}</value></property>
<property name="password"><value>${jdbc.password}</value></property>
</bean>
<beanid="propertyPlaceholderConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<propertyname="locations">
<list>
<value>jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<propertyname="driverClassName"><value>${jdbc.driverClassName}</value></property>
<propertyname="url"><value>${jdbc.url}</value></property>
<propertyname="username"><value>${jdbc.username}</value></property>
<propertyname="password"><value>${jdbc.password}</value></property>
</bean>
PropertyPlaceholderConfigurer的另一种精简配置方式(context命名空间):
Xml代码
<context:property-placeholderlocation="classpath:jdbc.properties,classpath:mails.properties"/>
<context:property-placeholderlocation="classpath:jdbc.properties, classpath:mails.properties"/>
Java属性文件内容:
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:orcl
jdbc.username=qycd
jdbc.password=qycd
除了可以读取Java属性文件中的属性外,还可以读取系统属性和系统环境变量的值。
读取系统环境变量的值:${JAVA_HOME}
读取系统属性的值:${user.dir}
2、PropertyOverrideConfigurer类
用于读取Java属性文件中的属性,并覆盖XML配置文件中的定义,即PropertyOverrideConfigurer允许XML配置文件中有默认的配置信息。
Java属性文件的格式:
beanName.property=value
beanName是属性占位符企图覆盖的bean名,property是企图覆盖的数姓名。
Xml代码
<beanid="propertyOverrideConfigurer"class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<property name="driverClassName"value="11"/>
<property name="url" value="22"/>
<property name="username" value="33"/>
<property name="password" value="44"/>
</bean>
<beanid="propertyOverrideConfigurer"class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<propertyname="locations">
<list>
<value>jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
<propertyname="driverClassName" value="11"/>
<propertyname="url" value="22"/>
<propertyname="username" value="33"/>
<propertyname="password" value="44"/>
</bean>
Java属性文件内容:
dataSource.driverClassName=oracle.jdbc.driver.OracleDriver
dataSource.url=jdbc:oracle:thin:@localhost:1521:orcl
dataSource.username=qycd
dataSource.password=qycd
九、ResourceBundleMessageSource类
提供国际化支持,bean的名字必须为messageSource。此处,必须存在一个名为jdbc的属性文件。
Xml代码
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>jdbc</value>
</list>
</property>
</bean>
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource">
<propertyname="basenames">
<list>
<value>jdbc</value>
</list>
</property>
</bean>
jdbc.properties属性文件的内容:
Xml代码
welcome={0}, welcome to guangzhou!
welcome={0}, welcome to guangzhou!
Java代码
AbstractApplicationContext ctx = new FileSystemXmlApplicationContext("applicationContext.xml");
ctx.getMessage("welcome", newString[]{"张三"},"", Locale.CHINA);
AbstractApplicationContext ctx = newFileSystemXmlApplicationContext("applicationContext.xml");
ctx.getMessage("welcome", newString[]{"张三"},"", Locale.CHINA);
十、FactoryBean接口
用于创建特定的对象,对象的类型由getObject方法的返回值决定。
Java代码
public class MappingFactoryBean implementsFactoryBean {
/**
* 获取mapping配置对象
* @return mapping配置
*/
public Object getObject() throws Exception {
List<String> configs =ApplicationContext.getContext().getApplication().getMappingConfigs();
return configs.toArray(new String[configs.size()]);
}
/**
* 返回Bean的类型
* @return Bean的类型
*/
public Class<?> getObjectType() {
return String[].class;
}
/**
* 返回Bean是否是单例的
* @return true表示是单例的
*/
public boolean isSingleton() {
return true;
}
}
public class MappingFactoryBean implementsFactoryBean {
/**
* 获取mapping配置对象
* @return mapping配置
*/
public Object getObject() throws Exception {
List<String> configs =ApplicationContext.getContext().getApplication().getMappingConfigs();
return configs.toArray(new String[configs.size()]);
}
/**
* 返回Bean的类型
* @return Bean的类型
*/
public Class<?> getObjectType() {
return String[].class;
}
/**
* 返回Bean是否是单例的
* @return true表示是单例的
*/
public boolean isSingleton() {
return true;
}
}
Java代码
public class MappingAutowiring implementsBeanPostProcessor {
/**
* 映射配置
*/
private String[] mappingResources;
/**
* 获取映射配置信息
* @return 映射配置
*/
public String[] getMappingResources() {
return mappingResources;
}
/**
* 设置映射配置信息
* @param mappingResources 映射配置
*/
public void setMappingResources(String[] mappingResources) {
this.mappingResources = mappingResources;
}
/**
* 自动装配
* @param bean Spring容器托管的bean
* @param beanName Bean名称
* @return 装配了映射文件后的对象
*/
public Object postProcessBeforeInitialization(Object bean, StringbeanName) throws BeansException {
if (bean instanceof LocalSessionFactoryBean) {
((LocalSessionFactoryBean)bean).setMappingResources(mappingResources);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, StringbeanName)
throws BeansException {
return bean;
}
}
public class MappingAutowiring implementsBeanPostProcessor {
/**
* 映射配置
*/
private String[] mappingResources;
/**
* 获取映射配置信息
* @return 映射配置
*/
public String[] getMappingResources() {
return mappingResources;
}
/**
* 设置映射配置信息
* @param mappingResources 映射配置
*/
public void setMappingResources(String[] mappingResources) {
this.mappingResources = mappingResources;
}
/**
* 自动装配
* @param bean Spring容器托管的bean
* @param beanName Bean名称
* @return 装配了映射文件后的对象
*/
public Object postProcessBeforeInitialization(Object bean, StringbeanName) throws BeansException {
if (bean instanceof LocalSessionFactoryBean) {
((LocalSessionFactoryBean) bean).setMappingResources(mappingResources);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, StringbeanName)
throws BeansException {
return bean;
}
}
Xml代码
<bean id="mappingAutowiring"class="com.achievo.framework.server.core.deploy.MappingAutowiring">
<property name="mappingResources"ref="mappingResources" />
</bean>
<bean id="mappingResources"class="com.achievo.framework.server.core.deploy.MappingFactoryBean"/>
二,springMVC工作原理(括号内为相关接口)
SpringMVC主要由调度器(DispatcherServlet)、处理器映射(HanderMapping)、处理器(HandlerAdapter)、拦截器(HandlerInterceptor )、控制器(Controller)、视图解析器(ViewResolver)、视图(View)这几部分构成。下面根据springMVC的工作流程依次介绍以上接口。
一个请求到达服务器,首先经过的是DispacherServlet,它是springMVC的入口,也是核心所在,主要职责是:
1,截获相应请求(具体将在下面配置中讲到).
2,初始化其WebApplicationContext上下文.
3,初始springMVC各个组件,并装配到DispacherServlet.
DispacherServlet拿到请求后,根据配置的处理器映射,将去寻找HanderMapping.HanderMapping有两个实现:
1,SimpleUrlHandlerMapping 通过配置文件,把一个URL映射到Controller
2,DefaultAnnotationHandler
接下下来是根据注解或者配置找到对应的Controller,执行其业务逻辑(若有拦截器,当然先走拦截器,springMVC拦截器类型不同,此处暂不提,下面有讲到)。Controller里面的方法返回值类型有String,ModelAndView,View,json等等。它的返回值包含了响应所需数据和url等信息。
接下来将到达ViewResolver(这个过程是DispacherServlet定位到ViewResolver还是框架既定的步骤?求解),根据配置的ViewResolver将找到对应的View,然后构造response呈递给浏览器。
大致步骤就是这样子。
三,入门小例子
下面我们来做个springMVC的小东西.
首先到spring官网下载jar包,附上地址:
http://www.springsource.org/download/community
(家里网速很不给力啊,- -!)
环境:eclipse4.2,tomcat;
1,新建一个web项目,这里我们取名为demo,打开web.xml,配置DispacherServlet,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
</web-app>
自定义springmvc配置文件名字:通过为servlet指定init-param实现,如下:
此处指定的文件是web根目录下的叫做springMVC.xml
拦截地址:
好了,接下来就在WEB-INF目录下新建springmvc的配置文件。名字根据以上配置取。我使用的默认配置,所以我的配置文件名字就是:spring-servlet.xml,内容如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
</beans>
来解释下:
- <!--
默认的注解映射的支持 --> -
<mvc:annotation-driven />
-
不知道是版本还是其他原因,这种方式我用不了, o(╯□╰)o
下面是指定物理目录用的, springmvc将根据controller里面方法的返回值查找物理路径,规则是这样的:
项目路径下/[prefix的value]+[controller的返回值]+[suffix指定的后缀]
比如我的controller里面某个方法返回index,那么,springmvc所找的页面就是:demo/jsp/index.jsp
好了接下来写一个Controller,代码如下:
package com.my.test;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController
{
}
简单得很,能说明大致工作原理就成了。
顺便说几句吧:
这句方法名可以任意,后面的参数,Model是spring封装的一个request对象,把他当成request用没一点问题;后米娜的HttpServletRequest都清楚,不必多说了~其实还可以在后面参数中加入从表单取值的东西,比如这样写:
这句上面有提到,springMVC通过这个返回值来确定需要返回的视图的物理路径,按照我们spring-servlet.xml的配置,框架去找的物理路径就是:/demo/jsp/index.jsp
好吧,物理路径是这个,那么就在这个路径下建一个jsp页面,内容够简洁:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Insert title here</title>
</head>
<body>
</body>
</html>
好了,部署到tomcat,在浏览器中输入:http://localhost:8080/demo/test/dotest/就可以看到以下结果:
以上代码在eclipse4.2+tomcat6.0.26的环境中可用。
四,配置拓展
关于springMVC以上所说只是一些简单的应用,不能满足实际需求。有一句话叫做什么来着,不重复发明轮子,那么,这里就贴出个链接,个人感觉还可以,有空可以去看看:
http://elf8848.iteye.com/blog/875830;
另外,如果想深入了解,还是去看看源码吧~~虽然比较耗时,不过绝对值得~