Spring小结

目录

 

1、引言

2、spring

2.1、Spring是什么

2.2、Spring特点

2.3、Spring框架结构

2.4、Spring 优点

3、Spring核心组件详解

 

3.1、Bean组件

3.2、Context组件

3.3、Core组件

4、Spring内容概括

4.1、ApplicationContext 接口容器

4.2、BeanFactory 接口容器

4.3、两个接口容器的区别

4.4、容器中 Bean 的作用域

4.5、bean后处理器

4.6、定制bean的生命始末

4.7 Bean 的生命周期

5、主配置文件讲解

6、IOC的DI

6.1、基于 XML 的 DI

6.2、基于注解的DI

7、AOP

7.1、通知 Advice

7.2、顾问 Advisor

7.3、自动代理生成器

7.4、AspectJ 对 AOP 的实现

7.5、Spring事务管理

8、Spring 与 JDBC 模板

8.1、数据源的配置

8.2、配置 JDBC 模板

问题解决

.1、bean类由动态工厂管理

.2、bean类由静态工厂管理

3、为应用指定多个 Spring 配置文件


1、引言

传统的Java Web应用程序是采用JSP+Servlet+Javabean来实现的,这种模式实现了最基本的MVC分层,使的程序结构分为几层,有负责前台展示的JSP、负责流程逻辑控制的Servlet以及负责数据封装的Javabean。但是这种结构仍然存在问题:如JSP页面中需要使用符号嵌入很多的Java代码,造成页面结构混乱,Servlet和Javabean负责了大量的跳转和运算工作,耦合紧密,程序复用度低等等。

之前文章讲到的SpringMVC就是解决该类问题的,或者另一个框架Struts2.。它是一个完美的MVC实现,它有一个中央控制类(一个Servlet),针对不同的业务,我们需要一个Action类负责页面跳转和后台逻辑运算,一个或几个JSP页面负责数据的输入和输出显示,还有一个Form类负责传递Action和JSP中间的数据。JSP中可以使用Struts框架提供的一组标签,就像使用HTML标签一样简单,但是可以完成非常复杂的逻辑。从此JSP页面中不需要出现一行包围的Java代码了。

通常人们会把整个Web应用程序分为三层,显示层,控制层,持久层。如果需要开发一个功能就需要new一个业务类出来,然后使用;业务层需要调用持久层的类,也需要new一个持久层类出来用。通过这种new的方式互相调用就是软件开发中最糟糕设计的体现。简单的说,就是调用者依赖被调用者,它们之间形成了强耦合,如果我想在其他地方复用某个类,则这个类依赖的其他类也需要包含。程序就变得很混乱,每个类互相依赖互相调用,复用度极低。如果一个类做了修改,则依赖它的很多类都会受到牵连。 为此,出现Spring框架。

2、spring

2.1、Spring是什么

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

2.2、Spring特点

         ①、IOC(控制反转)或DI(依赖注入):明确定义组件的接口,独立开发各个组件,然后根据组件的依赖关系组装运行;即将创建及管理对象的权利交给Spring容器。Spring是一个轻型容器(light-weight Container),其核心是Bean工厂(Bean Factory),用以构造我们所需要的M(Model)。能够让相互协作的软件组件保持松散耦合。降低了业务对象替换的复杂性,提高了组件之间的解耦。

        ②、AOP(面向切面编程):通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。即系统级的服务从代码中解耦出来。例如:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来。允许你把遍布应用各处的功能分离出来形成可重用组件。

  ③、声明式事务的支持

  在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

  

2.3、Spring框架结构

  1、核心容器:核心容器提供 Spring 框架的基本功能(Spring Core)。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

  2、Spring 上下文:Spring 上下文是一个配置文件,向 Spring框架提供上下文信息。Spring 上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。

  3、Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。

  4、Spring DAO:JDBCDAO抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

  5、Spring ORM:Spring 框架插入了若干个ORM框架,从而提供了 ORM 的对象关系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

  6、Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  7、Spring MVC 框架:MVC框架是一个全功能的构建 Web应用程序的 MVC 实现。通过策略接口,MVC框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。模型由javabean构成,存放于Map;视图是一个接口,负责显示模型;控制器表示逻辑代码,是Controller的实现。Spring框架的功能可以用在任何J2EE服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同J2EE 环境(Web 或EJB)、独立应用程序、测试环境之间重用。

2.4、Spring 优点

(1)控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。

(2)面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。

(3)MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。

(4)低侵入式设计,代码污染极低,独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺。

(5)集成能力强:集成多种优秀的开源框架。(Hibernate、Struts、Hessian等)。

(6)异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

(7)容器:Spring 包含并管理应用中对象的生命周期和配置。

(8)轻量:Spring 是轻量的,基本的版本大约2MB。

3、Spring核心组件详解

Spring核心组件只有Core、Context、Beans三个。core包侧重于帮助类,操作工具,beans包更侧重于bean实例的描述。context更侧重全局控制,功能衍生。

3.1、Bean组件

组件作用

Bean组件在Spring中的Beans包下,为了解决三件事。Bean的创建,Bean的定义,Bean的解析。最关心的就是Bean的创建。

Bean的创建

1、工厂模式的实现,顶层接口是:BeanFactory 
虽然最终实现类是DefaultListableBeanFactory,但是其上层接口都是为了区分在Spring内部对象的传递和转换的过程,对对象的数据访问所做的限制。 
ListableBeanFactory:可列表的 
HierarchicalBeanFactory:可继承的 
AutowriteCapableBeanFactory:可自动装配的 
这四个接口,共同定义了Bean的集合,Bean之间的关系,Bean的行为。

Bean的定义

Bean的定义完整的描述在Spring配置文件中节点中,包括子节点等。 
在Spring内部它被转换成BeanDefinition对象,后续操作都是对这个对象操作。 
主要是BeanDefinition来描述

Bean的解析

BeanDefinitionReader 
Bean的解析就是对Spring配置文件以及对Tag的解析。

3.2、Context组件

组件作用

在Spring中的context包下,为Spring提供运行环境,用以保存各个对象状态。

Context作为Spring的IOC容器,整合了大部分功能或说大部分功能的基础,完成了以下几件事: 
  1、标识一个应用环境 
  2、利用BeanFactory创建Bean对象 
  3、保存对象关系表 
  4、能够捕获各种事件 
ApplicationContext是context的顶级父类,除了能标识一个应用的基本信息外,还继承了五个接口,扩展了Context的功能。并且继承了BeanFactory说明Spring容器中运行的主体对象是Bean,另外还继承了ResourceLoader,可以让ApplicationContext可以访问任何外部资源。

ApplicationContext子类

1、ConfigurableApplicationContext:表示Context是可以修改的,在构建Context,用户可以动态添加或者修改已有的配置。 
2、WebApplicationContext:为Web准备的Context,可以访问ServletContext。

3.3、Core组件

访问资源

1、它包含了很多关键类,一个重要的组成部分就是定义的资源的访问方式,这种把所有资源都抽象成了一个接口的方式很值得学习。 
2、Resource接口封装了各种可能的资源类型,继承了InputStreamSource接口。 
加载资源的问题,也就是资源加载者的统一,由ResourceLoader接口来完成。 
默认实现是:DefaultResourceLoader

Core资源的加载

Core组件将解析等工作委托给了ResourcePatternResolver来完成,作为一个接头人,把资源的加载,解析和定义整合在了一起便于其他组件使用。

4、Spring内容概括

讲一下自己的感悟吧,在我们用任何一个框架的时候,这个框架是怎么启动的呢?以之前学到的spring mvc和mybatis以及这篇文章的spring来做一下总结:

一种是直接将启动程序(泛指一切启动框架的顶级接口和对象)放入到web.xml中,web.xml是整个应用程序的初始化配置文件。。例如spring mvc就是将主配置文件以<init-param>形式放在web.xml中。

另外一种就是在类中通过对象来获取框架的启动对象(泛指框架入口或者顶级接口)例如mybatis则通过输入流imputStrum获取mybatis配置文件。

还有一种就是spring clound框架直接创建一个启动类。。

Spring框架的启动或者加载配置文件提供了两个接口容器:一个是ApplicationContext 接口容器,另一个BeanFactory 接口容器

4.1、ApplicationContext 接口容器

1)ClassPathXmlApplicationContext实现类

           A、配置文件在类路径下(src下):若 Spring 配置文件存放在项目的类路径下,则使用 ClassPathXmlApplicationContext 实现类进行加载。
 

2)FileSystemXmlApplicationContext实现类
          B、配置文件在本地目录中:若 Spring 配置文件存放在本地磁盘目录中,则使用 FileSystemXmlApplicationContext 实
现类进行加载。
         C、配置文件在项目根路径下: 若 Spring 配置文件存放在项目的根路径下,同样使用 FileSystemXmlApplicationContext
实现类进行加载。下面是存放在项目根路径下的情况,该配置文件与 src 目录同级,而非在 src 中。

4.2、BeanFactory 接口容器

BeanFactory 接口对象也可作为 Spring 容器出现。 BeanFactory 接口是 ApplicationContext接口的父类。

若要创建 BeanFactory 容器,需要使用其实现类 XmlBeanFactory,而 Spring 配置文件以资源 Resouce 的形式出现在 XmlBeanFactory 类的构造器参数中。Resouce 是一个接口,其具有两个实现类:
     ClassPathResource:指定类路径下的资源文件
     FileSystemResource:指定项目根路径或本地磁盘路径下的资源文件

在创建了 BeanFactory 容器后,便可使用其重载的 getBean()方法,从容器中获取指定的Bean 对象。获取无参构造来创建bean的实例

4.3、两个接口容器的区别

虽然这两个接口容器所要加载的 Spring 配置文件是同一个文件,但在代码中的这两个容器对象却不是同一个对象,即不是同一个容器:它们对于容器内对象的装配(创建)时机是不同的。

A、 ApplicationContext 容器中对象的装配时机
ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
 

B、 BeanFactory 容器中对象的装配时机
BeanFactory 容器,对容器中对象的装配与加载采用延迟加载策略,即在第一次调用getBean()时,才真正装配该对象。

4.4、容器中 Bean 的作用域

当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过scope 属性,为 Bean 指定特定的作用域。 Spring 支持 5 种作用域。
(1)singleton: 单态模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的,
只有一个实例。 默认为单态的。
(2) prototype: 原型模式。即每次使用 getBean 方法获取的同一个<bean />的实例都是一个
新的实例。
(3) request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
(4) session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
(5) global session:每个全局的 HTTP session 对应一个 Bean 实例。典型情况下,仅在使用portlet 集群时有效,多个 Web 应用共享一个 session。一般应用中, global-session 与 session是等同的。
注意
(1)对于 scope 的值 request、 session 与 global session, 只有在 Web 应用中使用 Spring 时,该作用域才有效。
(2)对于 scope 为 singleton 的单例模式, 该 Bean 是在容器被创建时即被装配好了。
(3)对于 scope 为 prototype 的原型模式, Bean 实例是在代码中使用该 Bean 实例时才进行装配的。

4.5、bean后处理器

作用:可以对所有的bean的所有方法进行统一的处理, Bean 类与 Bean 类中的方法进行判断,就可实现对指定的 Bean 的指定方法进行功能扩展与增强。

Bean 后处理器是一种特殊的 Bean,容器中所有的 Bean 在初始化时,均会自动执行该类的两个方法。由于该 Bean 是由其它 Bean 自动调用执行,不是程序员手工调用,故此 Bean无须 id 属性。
需要做的是,在 Bean 后处理器类方法中,只要对 Bean 类与 Bean 类中的方法进行判断,就可实现对指定的 Bean 的指定方法进行功能扩展与增强。方法返回的 Bean 对象,即是增过的对象。代码中需要自定义 Bean 后处理器类。该类就是实现了接口 BeanPostProcessor 的类。该接口中包含两个方法,分别在目标 Bean 初始化完毕之前与之后执行。它们的返回值为:功能被扩展或增强后的 Bean 对象。
Bean 初始化完毕有一个标志:一个方法将被执行。即当该方法被执行时,表示该 Bean被初始化完毕。所以 Bean 后处理器中两个方法的执行,是在这个方法之前之后执行。 这个方法在后面将会讲到。

public Object postProcessBeforeInitialization(Object bean, String beanId)throws BeansException该方法会在目标 Bean 初始化完毕之前由容器自动调用。
public Object postProcessAfterInitialization(Object bean, String beanId) throws BeansException该方法会在目标 Bean 初始化完毕之后由容器自动调用。
它们的参数是:第一个参数是系统即将初始化的 Bean 实例,第二个参数是该 Bean 实例的 id 属性值。若 Bean 没有 id 就是 name 属性值。

4.6、定制bean的生命始末

在配置文件的<bean/>标签中增加如下属性:
init-method:指定初始化方法的方法名
destroy-method:指定销毁方法的方法名
注意,若要看到 Bean 的 destroy-method 的执行结果,需要满足两个条件:
(1) Bean 为 singleton,即单例
(2)要确保容器关闭。接口 ApplicationContext 没有 close()方法,但其实现类有。所以,可
以将 ApplicationContext 强转为其实现类对象,或直接创建的就是实现类对象。

4.7 Bean 的生命周期

Bean 实例从创建到最后销毁,需要经过很多过程,执行很多生命周期方法。
Step1:调用无参构造器,创建实例对象。
Step2:调用参数的 setter,为属性注入值。
Step3:若 Bean 实现了 BeanNameAware 接口,则会执行接口方法 setBeanName(String beanId),使 Bean 类可以获取其在容器中的 id 名称。
Step4:若 Bean 实现了 BeanFactoryAware 接口,则执行接口方法 setBeanFactory(BeanFactory
factory),使 Bean 类可以获取到 BeanFactory 对象。
Step5 : 若 定 义 并 注 册 了 Bean 后 处 理 器 BeanPostProcessor , 则 执 行 接 口 方 法
postProcessBeforeInitialization()。
Step6:若 Bean 实现了 InitializingBean 接口,则执行接口方法 afterPropertiesSet ()。 该方法在 Bean 的所有属性的 set 方法执行完毕后执行,是 Bean 初始化结束的标志,即 Bean 实例化结束。
Step7:若设置了 init-method 方法,则执行。
Step8 : 若 定 义 并 注 册 了 Bean 后 处 理 器 BeanPostProcessor , 则 执 行 接 口 方 法postProcessAfterInitialization()。
Step9:执行业务方法。
Step10:若 Bean 实现了 DisposableBean 接口,则执行接口方法 destroy()。
Step11:若设置了 destroy-method 方法,则执行。

 

5、主配置文件讲解

<bean/>标签的 id 属性与 name 属性
一般情况下,命名<bean/>使用 id 属性,而不使用 name 属性。在没有 id 属性的情况下,
name 属性与 id 属性作用是相同的。但,当<bean/>中含有一些特殊字符时,就需要使用 name
属性了。
id 的命名需要满足 XML 对 ID 属性命名规范:必须以字母开头,可以包含字母、数字、
下划线、连字符、句话、冒号。
name 属性值则可以包含各种字符
 

<bean />: 用于定义一个实例对象。 一个实例对应一个 bean 元素。
id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean, Bean 与 Bean 间的依
赖关系也是通过 id 属性关联的。
class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。

 

6、IOC的DI

Bean 实例在调用无参构造器创建了空值对象后,就要对 Bean 对象的属性进行初始化。初始化是由容器自动完成的, 称为注入。根据注入方式的不同, 常用的有两类: 设值注入构造注入。还有另外一种, 实现特定接口注入。 由于这种方式采用侵入式编程,污染了代码,所以几乎不用。
 

6.1、基于 XML 的 DI

(1)设值注入,bean类有setter方法
设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用

当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。 ref的值必须为某 bean 的 id 值。

还可以使用ref标签

(2)构造注入,bean类由构造方法

(3)命名空间注入PC(这个就不多做介绍了,感兴趣的自己百度一下)
 

bean类中不可能只有基本数据类型,还有集合类型,以及对象类型,还有域属性,下面逐个讲解。

(4)集合属性注入

            (1)为数组注入值

          (2) 为 List 注入值

         (3)为 Set 注入值

         (4)为 Map 注入值

           (5) 为 Properties 注入值

(5)对于域属性的自动注入
对于域属性的注入,也可不在配置文件中显示的注入。可以通过为<bean/>标签设置autowire 属性值,为域属性进行隐式自动注入。根据自动注入判断标准的不同,可以分为两种:
      byName:根据名称自动注入
      byType:根据类型自动注入
    (1) byName 方式自动注入
当配置文件中被调用者 Bean 的 id 值与代码中调用者 Bean 类的属性名相同时,可使用byName 方式,让容器自动将被调用者 Bean 注入给调用者 Bean。容器是通过调用者的 Bean类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的

      (2) byType 方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 Bean 类的某域属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。

(6)使用 SPEL 注入(了解)
SPEL, Spring Expression Language,即 Spring EL 表达式语言。即,在 Spring 配置文件中为 Bean 的属性注入值时,可直接使用 SPEL 表达式计算的结果。 SPEL 表达式以#开头,后跟一对大括号。用法: <bean id=“abc” value=“#{…}”/>。
(7)使用内部 Bean 注入
若不希望代码直接访问某个 bean,即,在代码中通过 getBean 方法获取该 Bean 实例,则可将该 Bean 的定义放入调用者 bean 定义的内部。

(8)使用同类抽象 Bean 注入
当若干 Bean实例同属于一个类,且这些实例的属性值又有相同值时,可以使用抽象 Bean,以简化配置文件。
抽象 Bean 是用于让其它 bean 继承的。这个 bean 在 Bean 类中是不能通过 getBean 方法获取的。设置 abstract 属性为 true 来指明该 bean 为抽象 bean, 默认值为 false。 不过,该bean 不为抽象 bean 时,也可被继承。 只不过,在应用中,用于被继承的 bean 一般为抽象bean。

6.2、基于注解的DI

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 Bean 实例。Spring 中使用注解,需要在原有 Spring 运行环境基础上再做一些改变,导入 AOP 的 Jar 包。因为注解的后台实现用到了 AOP 编程
(1)导包aop

(2)加入约束

(3)需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解

(4)定义 [email protected]
需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。另外, Spring 还提供了 3 个功能基本和@Component 等效的注解:

  @Repository 用于对 DAO 实现类进行注解
  @Service 用于对 Service 实现类进行注解
  @Controller 用于对 Controller 实现类进行注解

之所以创建这三个功能与@Component 等效的注解,是为了以后对其进行功能上的扩展,使它们不再等效

(5)Bean 的作用域@Scope

需要在类上使用注解@Scope,其 value 属性用于指定作用域。默认为 singleton。
(6)按类型注入域属性@Autowired
需要在域属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
(7)按名称注入域属性@Autowired 与@Qualifier
需要在域属性上联合使用注解@Autowired 与@Qualifier。 @Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。同样类中无需 setter,也可加到 setter 上

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
(8)域属性注解@Resource
Spring 提供了对 JSR-250 规范中定义@Resource 标准注解的支持。 @Resource 注解既可以按名称匹配 Bean,也可以按类型匹配 Bean。 使用该注解,要求 JDK 必须是 6 及以上版本。
           按类型注入域属性
@Resource 注解若不带任何参数,则会按照类型进行 Bean 的匹配注入。

           按名称注入域属性
@Resource 注解指定其 name 属性, 则 name 的值即为按照名称进行匹配的 Bean 的 id。

(9)Bean 的生命始末@PostConstruct 与@PreDestroy
在方法上使用@PostConstruct,与原来的 init-method 等效。在方法上使用@PreDestroy,与 destroy-method 等效。

7、AOP

7.1、通知 Advice

通知(Advice),切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。 常用通知有:前置通知、后置通知、环绕通知、异常处理通知。

(1) 前置通知 MethodBeforeAdvice
定义前置通知,需要实现 MethodBeforeAdvice 接口。该接口中有一个方法 before(),会
在目标方法执行之前执行。 前置通知的特点:
 在目标方法执行之前先执行。
 不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行。
 不改变目标方法执行的结果。
(2) 后置通知 AfterReturningAdvice
定义后置通知,需要实现接口 AfterReturningAdvice。该接口中有一个方法 afterReturning(),和前置特点一样。
(3) 环绕通知 MethodInterceptor
定义环绕通知,需要实现 MethodInterceptor 接口。环绕通知,也叫方法拦截器,可以
在目标方法调用之前及之后做处理,可以改变目标方法的返回值,也可以改变程序执行流程。

(4) 异常通知 ThrowsAdvice
定义异常通知,需要实现 ThrowsAdvice 接口。该接口的主要作用是,在目标方法抛出异常后,根据异常的不同做出相应的处理。当该接口处理完异常后,会简单地将异常再次抛出给目标方法。不过,这个接口较为特殊,从形式上看,该接口中没有必须要实现的方法。但,这个接口却确实有必须要实现的方法 afterThrowing()。这个方法重载了四种形式。由于使用时,一般只使用其中一种,若要都定义到接口中,则势必要使程序员在使用时必须要实现这四个方法。这是很麻烦的。所以就将该接口定义为了标识接口(没有方法的接口)。
 

常用的形式如下:
public void afterThrowing(自定义的异常类 e)
这里的参数 e 为,与具体业务相关的用户自定义的异常类对象。容器会根据异常类型的
不同,自动选择不同的该方法执行。这些方法的执行是在目标方法执行结束后执行的。

定义好前四种通知类后,在主配置文件中添加目标类(bean),通知类,代理

上面切入的通知都是单个的,那如何为一个目标类切入多个通知?

7.2、顾问 Advisor

通知(Advice)是 Spring 提供的一种切面(Aspect)。但其功能过于简单:只能将切面织入到目标类的所有目标方法中, 无法完成将切面织入到指定目标方法中。顾问(Advisor)是 Spring 提供的另一种切面。其可以完成更为复杂的切面织入功能。
PointcutAdvisor 是顾问的一种, 可以指定具体的切入点。 顾问将通知进行了包装,会根据不同的通知类型,在不同的时间点,将切面织入到不同的切入点。PointcutAdvisor 接口有两个较为常用的实现类:
            NameMatchMethodPointcutAdvisor 名称匹配方法切入点顾问
            RegexpMethodPointcutAdvisor 正则表达式匹配方法切入点顾问

(1) 名称匹配方法切入点顾问

NameMatchMethodPointcutAdvisor,即名称匹配方法切入点顾问。容器可根据配置文件中指定的方法名来设置切入点。代码不用修改,只在配置文件中注册一个顾问,然后使用通知属性 advice 与切入点的方法名 mappedName 对其进行配置。代理中的切面,使用这个顾问即可。

也可以使用通配符,do*:表示目标类中所有以do开头的方法。也可以使用list标签:

(2)正则表达式方法切入点顾问
RegexpMethodPointcutAdvisor,即正则表达式方法顾问。容器可根据正则表达式来设置切入点。注意,与正则表达式进行匹配的对象是接口中的方法名,而非目标类(接口的实现类)的方法名。

正则表达式回顾:

* 匹配前面的子表达式任意次,比如:ao* 能匹配 a ao aoo aoooo..
+ 匹配前面的字表达式一次或者多次,比如:ao+ 能匹配 ao aoo aooo.. 但是不能匹配 a
. 匹配任意一个字符,换行符除外(rn)
.* 匹配任意字符串,比如:.add. 能匹配包含 add 的字符串、

7.3、自动代理生成器

前面代码中所使用的代理对象,均是由 ProxyFactoryBean 代理工具类生成的。而该代理工具类存在着如下缺点:
     (1)一个代理对象只能代理一个 Bean,即如果有两个 Bean 同时都要织入同一个切面,这
时,不仅要配置这两个 Bean,即两个目标对象,同时还要配置两个代理对象。
     (2)在客户类中获取 Bean 时,使用的是代理类的 id,而非我们定义的目标对象 Bean 的 id。
我们真正想要执行的应该是目标对象。从形式上看,不符合正常的逻辑。Spring 提供了自动代理生成器,用于解决 ProxyFactoryBean 的问题。常用的自动代理生成器有两个:
              默认 advisor 自动代理生成器
              Bean 名称自动代理生成器
需要注意的是,自动代理生成器均继承自 Bean 后处理器 BeanPostProcessor。容器中所有 Bean 在初始化时均会自动执行 Bean 后处理器中的方法,故其无需 id 属性。所以自动代理生成器的 Bean 也没有 id 属性,客户类直接使用目标对象 bean 的 id。
         (1)默认 advisor 自动代理生成器
DefaultAdvisorAutoProxyCreator 代理的生成方式是,将所有的目标对象与 Advisor(顾问,通知为advice) 自动结合,生成代理对象。无需给生成器做任何的注入配置。注意,只能与 Advisor 配合使用。这种代理的配置很简单,如下:

(2)Bean 名称自动代理生成器
DefaultAdvisorAutoProxyCreator 会为每一个目标对象织入所有匹配的 Advisor,不具有选择性,且切面只能是顾问 Advisor。而 BeanNameAutoProxyCreator 的代理生成方式是,根据bean 的 id,来为符合相应名称的类生成相应代理对象,且切面既可以是顾问 Advisor 又可以是通知 Advice。

beanNames:指定要增强的目标类的 id
interceptorNames:指定切面。可以是顾问 Advisor,也可以是通知 Advice。

对于自动代理,直接获取目标对象id即可,当有多个目标对象时,可采用如下方式获取:

7.4、AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。 Spring 就是其中之一, 可以完成面向切面编程。 然而, AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以, Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

(1)什么是AspectJ?

AspectJ 是一个面向切面的框架,它扩展了 Java 语言。 AspectJ 定义了 AOP 语法,它有
一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。

(2)AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于 try..catch 中的finally 代码块。
AspectJ 除了提供了六种通知外,还定义了专门的表达式用于指定切入点。表达式的原
型是:
execution ( [modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定性类名
name-pattern(param-pattern) 方法名(参数名)
[throws-pattern] 抛出异常类型
)
切入点表达式要匹配的对象就是目标方法的方法名。所以, execution 表达式中明显就是方法的签名。 注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

举例:
execution(public * *(..))
指定切入点为:任意公共方法。
execution(* set *(..))
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* *.service.*.doSome())
指定只有一级包下的 serivce 子包下所有类中的 doSome()方法为切入点
execution(* *..service.*.doSome())
指定所有包下的 serivce 子包下所有类中的 doSome()方法为切入点
execution(* com.xyz.service.IAccountService.*(..))
指定切入点为: IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..))
指定切入点为: IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任
意方法;若为类,则为该类及其子类中的任意方法
execution(* joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参
数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用
全限定类名,如 joke( java.util.List, int)。
execution(* joke(String,*)))
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但 joke(String s1,double d2,Strings3)不是
execution(* joke(String,..)))
指定切入点为:所有的 joke()方法,该方法第 一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、 joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
execution(* joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。 joke(Objectob)是,但, joke(String s)与 joke(User u)均不是。
execution(* joke(Object+)))

指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是, joke(String s)和 joke(User u)也是。

7.5、Spring事务管理

对于 Spring 的事务管理, 是 AOP 的应用,将事务作为切面织入到了 Service 层的业务方法中。
事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。在 Spring 中通常可以通过以下三种方式来实现对事务的管理:
(1) 使用 Spring 的事务代理工厂管理事务
(2) 使用 Spring 的事务注解管理事务
(3)使用 AspectJ 的 AOP 配置管理事务

7.5.1、事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
       A、 常用的两个实现类
           PlatformTransactionManager 接口有两个常用的实现类:
               DataSourceTransactionManager:使用 JDBC 或 iBatis 进行持久化数据时使用。
               HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
       B、 Spring 的回滚方式
Spring 事务的默认回滚方式是: 发生运行时异常时回滚,发生受查异常时提交。 不过,对于受查异常,程序员也可以手工设置其回滚方式。
       C事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限, 及对它们的操作。

              1) 定义了五个事务隔离级别常量

这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。

  •  DEFAULT: 采用 DB 默认的事务隔离级别。 MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
  •  READ_UNCOMMITTED: 读未提交。 未解决任何并发问题。
  •  READ_COMMITTED: 读已提交。解决脏读,存在不可重复读与幻读。
  •  REPEATABLE_READ: 可重复读。解决脏读、不可重复读,存在幻读
  •  SERIALIZABLE: 串行化。不存在并发问题。

        2) 定义了七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如, A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
 REQUIRED: 指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;
若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。如该传播行为加在 doOther()方法上。若 doSome()方法在执行时就是在事务内的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

 SUPPORTS: 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
 MANDATORY: 指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。
 REQUIRES_NEW: 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
 NOT_SUPPORTED: 指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。
 NEVER: 指定的方法不能在事务环境下执行,若当前存在事务,就直接抛出异常。
 NESTED: 指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行;若当前没有事务,则创建一个新事务。
        3)定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的 none 值。

注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。
 

8、Spring 与 JDBC 模板

为了避免直接使用 JDBC 而带来的复杂且冗长的代码, Spring 提供了一个强有力的模板类---JdbcTemplate 来简化 JDBC 操作。并且,数据源 DataSource 对象与模板 JdbcTemplate 对象均可通过 Bean 的形式定义在配置文件中,充分发挥了依赖注入的威力。
对于 JDBC 模板的使用,是 IoC 的应用,是将 JDBC 模板对象注入给了 Dao 层的实现类。
 

8.1、数据源的配置

使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置在 Spring 配置文件中。根据数据源的不同,其配置方式不同。下面主要讲解三种常用数据源的配置方式:
(1) Spring 默认的数据源
(2) DBCP 数据源
(3) C3P0 数据源

        Spring 默认的数据源 DriverManagerDataSource
Spring 默认的数据源为 DriverManagerDataSource,其有一个属性 DriverClassName,用于接收 DB 驱动。

            DBCP 数据源 BasicDataSource
DBCP, DataBase Connection Pool,是 apache 下的项目, 使用该数据源,需要导入两个Jar 包。它们在 Spring 依赖库的解压目录的 org.apache.commons 目录中 dbcp 与 pool 子包中。

            C3P0 数据源 ComboPooledDataSource

从属性文件读取数据库连接信息
为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置文件从中读取
数据。
属性文件名称随意,但一般都是放在 src 下
Spring 配置文件从属性文件中读取数据时,需要在<property/>的 value 属性中使用${ },
将在属性文件中定义的 key 括起来,以引用指定属性的值。

该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。注册方式有两
种:

(1) <bean/>方式
(2) <context>方式

(1) <bean/>方式-使用 class 为 PropertyPlaceholderConfigurer
以 PropertyPlaceholderConfigurer 类的 bean 实例的方式进行注册。该类有一个属性
location,用于指定属性文件的位置。这种方式不常用。

(2) <context:property-placeholder/>方式
该方式要求在 Spring 配置文件头部加入 context 的约束,即修改配置文件头。<context:property-placeholder/>标签中有一个属性 location,用于指定属性文件的位置

配置 JDBC 模板
JDBC 模板类 JdbcTemplate 从其父类 JdbcAccessor 继承了一个属性 dataSource,用于接收
数据源。
 

8.2、配置 JDBC 模板

(1)JDBC 模板类 JdbcTemplate 从其父类 JdbcAccessor 继承了一个属性 dataSource,用于接收数据源。

其中myC3P0DataSource为上面数据源配置的ID

(2)Dao 实现类继承 JdbcDaoSupport 类

JdbcDaoSupport 类中有一个属性 JdbcTemplate,用于接收 JDBC 模板。所以 Dao 实现类继承了 JdbcDaoSupport 类后,也就具有了 JDBC 模板属性。在配置文件中,只要将模板对象注入即可。

注意:再仔细查看 JdbcDaoSupport 类,发现其有一个 dataSource 属性,查看 setDataSource()方法体可知,若 JDBC 模板为 null,则会自动创建一个模板对象。故,在 Spring 配置文件中,对于 JDBC 模板对象的配置完全可以省去,而是在 Dao 实现类中直接注入数据源对象。这样会让系统自动创建 JDBC 模板对象。

(3)对 DB 的增、删、改操作
JdbcTemplate 类中提供了对 DB 进行修改、查询的方法。 Dao 实现类使用继承自JdbcDaoSupport 的 getTemplate()方法,可以获取到 JDBC 模板对象。对 DB 的增、删、改都是通过 update()方法实现的。该方法常用的重载方法有两个:
      public int update ( String sql)
       public int update ( String sql, Object… args)
第 1 个参数为要执行的 sql 语句,第 2 个参数为要执行的 sql 语句中所包含的动态参数。其返回值为所影响记录的条数。一般不用。

      a)简单对象查询
常用的简单对象查询方法有:查询结果为单个对象的 queryForObject()与查询结果为 List的 queryForList()。
pubic T queryForObject (String sql, Class<T> type, Object... args)
pubic List<T> queryForList (String sql, Class<T> type, Object... args)

      b)自定义对象查询
常用的自定义对象查询方法有:查询结果为单个对象的 queryForObject()与查询结果为List 的 query()。
            pubic T queryForObject (String sql, RowMapper<T> m , Object... args)
            pubic List<T> query (String sql, RowMapper<T > m, Object... args)
注意, RowMapper 为记录映射接口,用于将查询结果集中每一条记录包装为指定对象。该接口中有一个方法需要实现:
public Object mapRow(ResultSet rs, int rowNum)参数 rowNum 表示总的结果集中当前行的行号,但参数 rs 并不表示总的结果集,而是表示 rowNum 所代表的当前行的记录所定义的结果集,仅仅是当前行的结果。一般,该方法体中就是实现将查询结果中当前行的数据包装为一个指定对象

 

问题解决

.1、bean类由动态工厂管理

问题:有些时候,项目中需要通过工厂类来创建 Bean 实例,而不能像前面例子中似的,直接
由 Spring 容器来装配 Bean 实例。使用工厂模式创建 Bean 实例,就会使工厂类与要创建的
Bean 类耦合到一起。

解决思路:将动态工厂 Bean 作为普通 Bean 来使用是指,在配置文件中注册过动态工厂 Bean 后,
测试类直接通过 getBean()获取到工厂对象,再由工厂对象调用其相应方法创建相应的目标
对象。配置文件中无需注册目标对象的 Bean。因为目标对象的创建不由 Spring 容器来管理。

缺点:这样做的缺点是,不仅工厂类与目标类耦合到了一起,测试类与工厂类也耦合到了一起。

优化:使用 Spring 的动态工厂 Bean

Spring 对于使用动态工厂来创建的 Bean,有专门的属性定义。 factory-bean 指定相应的工厂 Bean,由 factory-method 指定创建所用方法。此时配置文件中至少会有两个 Bean 的定义:工厂类的 Bean,与工厂类所要创建的目标类 Bean。而测试类中不再需要获取工厂 Bean对象了,可以直接获取目标 Bean 对象。实现测试类与工厂类间的解耦。

.2、bean类由静态工厂管理

使用工厂模式中的静态工厂来创建实例 Bean。此时需要注意,静态工厂无需工厂实例,所以不再需要定义静态工厂<bean/>。
而对于工厂所要创建的 Bean,其不是由自己的类创建的,所以无需指定自己的类。但其是由工厂类创建的,所以需要指定所用工厂类。故 class 属性指定的是工厂类而非自己的类。当然,还需要通过 factory-method 属性指定工厂方法。

3、为应用指定多个 Spring 配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。
(1)平等关系的配置文件
将配置文件分解为地位平等的多个配置文件,并将所有配置文件的路径定义为一个String 数组,将其作为容器初始化参数出现。 其将与可变参的容器构造器匹配。各配置文件间为并列关系,不分主次

(2)包含关系的配置文件
各配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。在 Java代码中只需要使用总配置文件对容器进行初始化即可。

也可使用通配符*。但,此时要求父配置文件名不能满足*所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为spring-total.xml。