(1)Spring是一个开源的轻量级的应用开发框架,其目的是用于简化企业级应用程序开发,降低开发者的开发难度;
(2)Spring提供的IoC和AOP应用,可以将组件的耦合度降至最低(即解耦),便于系统日后的维护和升级;
(3)Spring为系统提供了一个整体的解决方案,开发者可以利用它本身提供的功能外,也可以与第三方框架和技术整合应用,可以自由选择采用哪种技术进行开发。比如Spring整合SpringMVC、Spring整合MyBatis、Spring整合Struts2、Spring整合Hibernate、Spring整合Quartz(定时任务处理)。
(1)方便解耦,简化开发:通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。
(2)AOP编程的支持:通过Spring提供的AOP功能,方便进行面向切面的编程,如性能监测、事务管理、日志记录等。
(3)声明式事务的支持。
(4)方便集成各种优秀框架。
(5)降低Java EE API的使用难度:例如JDBC、JavaMail和远程调用等提供了简便封装。
Spring 最初的目标就是要整合一切优秀资源,然后对外提供一个统一的服务。
Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示:
核心容器 Spring Core | 核心容器,提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用**控制反转(IOC)模式 **,将应用程序的配置和依赖性规范与实际的应用程序代码分开。 |
---|---|
Spring Context | Spring上下文,是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。 |
Spring AOP | 通过配置管理特性,Spring AOP 模块直接将面向切面编程的功能集成到了 Spring 框架中。可以很容易地使 Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring 的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,就可以将声明性事务管理集成到应用程序中。 |
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。 |
控制反转,就是指将对象的创建,对象的存储(map),对象的管理(依赖查找,依赖注入)交给了Spring容器。
提示:Spring容器是spring中的一个核心模块,用于管理对象。
只需要将类提前配置在spring配置文件中,就可以将对象的创建交给spring容器,当需要对象时,不需要自己创建,而是直接通过spring获取即可。
这样就省去了实例化对象的步骤,可以降低代码之间的耦合性。
这时候就会有个问题:IOC如何实例化对象?
答案如下:
<bean id="user" class="com.tdeu.pojo.User">
Spring容器执行过程:
当tomcat服务器启动时会加载Spring容器的配置文件,当程序解析到Bean标签时,通过反射机制实例化对象,对象最终保存到了Spring容器自身维护的Map
知识点:反射机制调用对象的无参构造实例化对象。
控制反转,顾名思义就是控制(由IOC容器来控制对象的创建)和反转(由容器来帮忙创建及注入依赖对象)。
那问题来了:谁控制谁?控制什么?为何是反转?哪些方面反转了?
谁控制谁:当然是IOC容器控制了对象;
控制什么:主要控制了外部资源获取,不只是对象,也包括文件等;
为何是反转:因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
哪些方面反转了:依赖对象的获取被反转了。
用图例说明一下,传统程序设计如下图所示,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了。
IOC是一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难以测试;有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IOC对编程带来的最大改变不是从代码上,而是从思想上发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IOC/DI思想中,应用程序就需要被动的等待IOC容器来创建并注入它所需要的资源。
IOC很好的体现了面向对象设计法则之一 —— 好莱坞法则:“别找我们,我们找你”。即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁? 为什么需要依赖? 谁注入谁? 注入了什么?”,那就让我来深入分析一下:
(1)谁依赖于谁:当然是应用程序依赖于IoC容器;
(2)为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
(3)谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
(4)注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
理解了以上的内容,接下来就要理解IOC和DI存在什么样的关系?
其实它们是同一个概念的不同角度描述。由于控制反转概念比较含糊,可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系,所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IOC 而言,“依赖注入”明确描述了“被注入对象依赖IOC容器配置依赖对象”。
对于对象中的属性来说,注入属性的方式:在配置文件中使用property标签。
代码示例如下:
<property name="对象属性名称" >
<value>需要注入的内容</value>
</property>
问:那么如果在类中有对另一个对象的引用的情况,要如何注入呢?
答:其实类似于注入一个属性,只是需要使用ref标签进行注入,具体步骤如下:
首先我们需要通过bean配置两个类,我们分别给他们的id命名为:类1、类2;
这时假如我们的需求是类1中包含有对类2的引用,也就是说 类2 是 类1 的一个属性。
这时我们就需要将类2的对象注入到类1对象中。
配置文件中的写法如下:
<bean id= "类1" class="类1的全路径">
<!--这里的注入方法类似于上面的value注入,可以写在一行也可以多行-->
<property name="类2在类1中的属性名" ref="类2"></bean>
<!--这里的类2是对象名称,再注入的时候ref中的值必须和bean 后面的Id完全一致-->
<bean id="类2" class="类2的全路径"></bean>
以上的做法就是Spring管理各个对象和维护各个对象之间的关系。
其中具体的执行原理是:
1.当ClassPathXmlApplicationContext(“applicationContext.xml”)这句话执行的时候,spring容器对象就被创建;
2.创建的对象会读取applicationContext.xml中配置的bean就会被创建,并且放入内存。具体是怎么创建的,就要依赖于反射机制。
就拿以上代码来说:首先,类1被创建;创建后,假设这个对象在内存中的地址为:123对象中对于对象二的引用为(???)。此时的内存如下:
然后 类2 被创建,这时候 类2 在对象中的内存地址会赋值给 类1 中相应字段的引用。
具体来说:假设 类2 的地址为:456,这时123对象中的那个对于对象2的引用由(???)改为(456)完成类的依赖注入。
此时内存如下:
其实,这些对象都存在与applicationContext对象的一个类似于HashMap的引用中。
这也是我们为什么要将applicationContext对象设置为单例的原因。
那Spring是如何实现刚才的那些的调用?
首先,通过dom4j将我们的配置文件读取,这时我们就可以解析到所有相关的类的全路径了;
然后,它再利用反射机制通过如下代码完成类的实例化:类1=Class.forName(“类1的全路径”);
这时,我们就得到了类1。
这也是为啥当我们的类的全路径写错了会导致出现classNotfind的错误。
当我们得到了 类1 以后,通过调用 类1 的set方法,将属性给对象进行注入。而且,需要遵循首字母大写的set规范。例如:我们的类中有个字段的属性为name那么set方法必须写成setName
注意:Name 的首字母要大写,否则会报一个属性找不到的错误。
代码示例如下:
public void setName(String name){
this.name= name;
}
对象创建后,将对象id和对象物理地址一起存入类似于HashMap的容器中;
然后,通过getBean的方法,通过对象Id获得对象的物理地址,得到对象;
最后,调用对象的方法,完成对方法的调用。
AOP(面向切面编程)可以说是OOP(面向对象编程)的补充和完善。
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。
OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能,日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系,对于其他类型的代码也是如此,如安全性、异常处理和透明的持续性。
这种散布在各处的无关的代码被称为横切代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP则利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。
简单的说,所谓“方面”,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。
面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。
通知方法:
1.前置通知:在我们执行目标方法之前运行(@Before)
2.后置通知:在我们目标方法运行结束之后 ,不管有没有异常(@After)
3.返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
4.异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
5.环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知(@Around)
AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。
AOP 的本质:由 AOP 框架修改业务组件的多个方法的源代码,看到这其实应该明白了,AOP 其实就是前面一篇文章讲的代理模式的典型应用。
按照 AOP 框架修改源代码的时机,可以将其分为两类:
第一类:静态 AOP 实现: AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类,例如:AspectJ… …
注意:AOP 框架在编译阶段对程序源代码进行修改时所生成的 .class 文件已经被改掉了,需要使用特定的编译器。
第二类:动态 AOP 实现: AOP 框架在运行阶段对动态生成代理对象,例如:SpringAOP… …
注意:框架在运行阶段对动态生成代理对象是指:在内存中以 JDK 动态代理 或 CGlib 动态地生成 AOP 代理类。
下面给出常用 AOP 实现比较:
Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。