控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。 IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖 注入。应用广泛。 依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA 对 classB 有依赖。 Ioc 的实现: ➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。 ➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行 完成。 依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建 被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。 Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系 的管理。 Spring 框架使用依赖注入(DI)实现 IoC。 Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称 为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
2.1 开发工具准备
开发工具:idea2017 以上 依赖管理:maven3 以上 jdk:1.8 以上
需要设置 maven 本机仓库:
6
2.2 Spring 的第一个程序
举例:01-primay
2.2.1 创建 maven 项目
Java 框架 Spring4
7
2.2.2 引入 maven 依赖 pom.xml
org.springframework spring-context 4.3.16.RELEASE
插件 maven-compiler-plugin 3.1 1.8 1.8
jar 文件列表
2.2.3 定义接口与实体类
public interface SomeService { void doSome(); }
public class SomeServiceImpl implements SomeService { public SomeServiceImpl() { super(); System.out.println(“SomeServiceImpl无参数构造方法”); } @Override public void doSome() {
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
8
System.out.println("==业务方法doSome()="); } }
2.2.4 创建 Spring 配置文件
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名 称为 applicationContext.xml。 spring 配置中需要加入约束文件才能正常使用,约束文件是 xsd 扩展名。 使用约束文件的语法在:xsd-configuration.html。这个 xsd-configuration.html 文件位置是 在%SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html。 SPRING_HOME:是从 spring 官网下载的资源 spring-framework-4.3.16.RELEASE-dist.zip 解 压后的目录。
注意,Spring 配置文件中使用的约束文件为 xsd 文件。若 Eclipse 中没有自动提示功能, 则需要将约束要查找的域名地址指向本地的 xsd 文件。相应的 xsd 文件在 Spring 框架解压目 录下的 schema 目录的相应子目录中。
Java 框架 Spring4
9
这里需要的是 spring-beans.xsd 约束文件,故需要在 beans 子目录中查找相应版本 的约束文件。
:用于定义一个实例对象。一个实例对应一个 bean 元素。 id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依 赖关系也是通过 id 属性关联的。 class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。
2.2.5 定义测试类
2.2.6 使用 spring 创建非自定义类对象
spring 配置文件加入 java.util.Date 定义:
MyTest 测试类中: 调用 getBean(“myDate”); 获取日期类对象。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
10
2.2.7 容器接口和实现类
ApplicationContext 接口(容器)
ApplicationContext 用于加载 Spring 的配置文件,在程序中充当“容器”的角色。其实现 类有两个。
A、 配置文件在类路径下
若 Spring 配置文件存放在项目的类路径下,则使用 ClassPathXmlApplicationContext 实现 类进行加载。
B、 配置文件在项目根路径下
FileSystemXmlApplicationContext 实现类用来加载存放在项目根路径下或磁盘路径中的 spring 配置文件。 下面是存放在项目根路径下的情况,该配置文件与 src 目录同级,而非在 src 中。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
11
C、 ApplicationContext 容器中对象的装配时机
ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。 以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
D、 使用 spring 容器创建的 java 对象
2.3 Bean 的装配
举例:beanAssemble 项目 Bean 的装配,即 Bean 对象的创建。容器根据代码要求创建 Bean 对象后再传递给代码 的过程,称为 Bean 的装配。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
12
2.3.1 默认装配方式
Spring 调用 Bean 类的无参构造器,创建空值的实例对象。 代码:
Spring 配置文件:
测试类:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
13
2.3.2 容器中 Bean 的作用域
当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持多种作用域。 (1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的, 叫这个名称的对象只有一个实例。默认为单例的。 (2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个 新的实例。 (3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。 (4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。 注意: (1)对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。 (2)对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了。 (3)对于 scope 为 prototype 的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行 装配的。
举例: 类定义:
Spring 配置文件:
测试单例:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
14
测试原型:
2.4 基于 XML 的 DI
举例:项目 di-xml
2.4.1 注入分类
bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化 是由容器自动完成的,称为注入。 根据注入方式的不同,常用的有两类:设值注入、构造注入。
(1) 设值注入(掌握)
设值注入是指,通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而 在 Spring 的依赖注入中大量使用。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
15
A、 简单类型
B、 引用类型
当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
16
① 对于其它 Bean 对象的引用,使用标签的 ref 属性
测试方法:
② 对于其它 Bean 对象的引用,除了标签的 ref 属性外,还可以使用标签。
测试方法:
③ 创建 java.util.Date 并设置初始的日期时间: Spring 配置文件:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
17
测试方法:
(2) 构造注入(理解)
构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设 置依赖关系。 举例 1:
标签中用于指定参数的属性有: ➢ name:指定参数名称。 ➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行, 但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
18
顺序一致。
举例 2: 使用构造注入创建一个系统类 File 对象
测试类:
2.4.2 引用类型属性自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据自动注入判断标准的不同,可以分为两种: byName:根据名称自动注入 byType: 根据类型自动注入
(1) byName 方式自动注入
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
举例:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
19
(2) byType 方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配 哪一个了。 举例:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
20
2.4.3 为应用指定多个 Spring 配置文件
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。 包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过引入。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。 举例: 代码:
Spring 配置文件:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
21
也可使用通配符*。但,此时要求父配置文件名不能满足所能匹配的格式,否则将出现 循环递归包含。就本例而言,父配置文件不能匹配 spring-.xml 的格式,即不能起名为 spring-total.xml。
测试代码:
2.5 基于注解的 DI
举例:di-annotation 项目 对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。Spring 中使用注解, 需要在原有 Spring 运行环境基础上再做一些改变,完成以下三个步骤。 (1)pom.xml 加入 AOP 依赖。 org.springframework spring-aop 4.3.16.RELEASE (2)需要更换配置文件头,加入 spring-context.xsd 约束。 约束在%SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html 文件中。
(3)需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
22
指定多个包的三种方式: 1)使用多个 context:component-scan 指定不同的包路径
2)指定 base-package 的值使用分隔符 分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。
逗号分隔:
分号分隔:
3)base-package 是指定到父包名
base-package 的值表是基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到 子包下级的子包。所以 base-package 可以指定一个父包就可以。
或者最顶级的父包
但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。指定到目标包和合 适的。也就是注解所在包全路径。例如注解的类在 com.bjpowernode.beans 包中
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
23
2.5.1 定义 Bean 的注解@Component(掌握)
需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。 举例:di01
另外,Spring 还提供了 3 个创建对象的注解: ➢ @Repository 用于对 DAO 实现类进行注解 ➢ @Service 用于对 Service 实现类进行注解 ➢ @Controller 用于对 Controller 实现类进行注解 这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处 理器接收用户的请求。 @Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对 象。即持久层对象,业务层对象,控制层对象。
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
2.5.2 简单类型属性注入@Value(掌握)
需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。 使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。 举例:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
24
2.5.3 byType 自动注入@Autowired(掌握)
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。 举例:
2.5.4 byName 自动注入@Autowired 与@Qualifier(掌握)
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。 举例:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
25
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运 行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
2.5.5 JDK 注解@Resource 自动注入(掌握)
Spring提供了对jdk中@Resource注解的支持。@Resource注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。 @Resource 可在属性上,也可在 set 方法上。
(1) byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。 举例:
(2) byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
26
举例:
2.5.6 注解与 XML 的对比
注解优点是: ⚫ 方便 ⚫ 直观 ⚫ 高效(代码少,没有配置文件的书写那么复杂)。
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
XML 方式优点是: ⚫ 配置和代码是分离的 ⚫ 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。 xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
27
第3章 AOP 面向切面编程
3.1 动态代理
动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理 对象只是由代理生成工具(不是真实定义的类)在程序运行时由 JVM 根据反射等机制动态 生成的。代理对象与目标对象的代理关系在程序运行时才确立。
3.1.1 JDK 动态代理
动态代理的实现方式常用的有两种:使用 JDK 的 Proxy,与通过 CGLIB 生成代理。 Jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。 从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method 和 InovcationHandler。
3.1.2 CGLIB 动态代理(了解)
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架 使用,例如 Spring AOP。 使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在 接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。 CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代 理对象。所以,使用 CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的 类。 CGLIB 经常被应用在框架中,例如 Spring ,Hibernate 等。cglib 的代理效率高于 Jdk。 项目中直接使用动态代理的地方不多。一般都使用框架提供的功能。
3.2 不使用 AOP 的开发方式(理解)
Step1:项目 aop_leadin1
先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非 业务方法。非业务方法也称为交叉业务逻辑: ➢ doTransaction():用于事务处理 ➢ doLog():用于日志处理 然后,再使接口方法调用它们。接口方法也称为主业务逻辑。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
28
接口:
Step2:项目 aop_leadin2
当然,也可以有另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类 中,由主业务逻辑调用。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
29
Step3:项目 aop_leadin3
以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑 较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑 的可读性,降低了代码的可维护性,同时也增加了开发难度。 所以,可以采用动态代理方式。在不修改主业务逻辑的前提下,扩展和增强其功能。 功能增强:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
30
3.3 AOP 概述
3.4 AOP 简介
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程 序运行过程。 AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态 代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程 序的可重用性,同时提高了开发的效率。
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占 比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大 大干扰了主业务逻辑—转账。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
31
3.5 面向切面编程对有什么好处?
1.减少重复; 2.专注业务; 注意:面向切面编程只是面向对象编程的一种补充。
使用 AOP 减少重复代码,专注业务实现:
3.6 AOP 编程术语(掌握)
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不 能被增强的。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
32
(4) 目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然, 不被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知 定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通 知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。
3.7 AspectJ 对 AOP 的实现(掌握)
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向 切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框 架中。 在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
AspectJ 简介
官网地址:http://www.eclipse.org/aspectj/ AspetJ 是 Eclipse 的开源项目,官网介绍如下:
a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台 的面向切面编程的语言) Java platform compatible(兼容 Java 平台,可以无缝扩展) easy to learn and use(易学易用)
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
33
3.7.1 AspectJ 的通知类型(理解)
AspectJ 中常用的通知有五种类型: (1)前置通知 (2)后置通知 (3)环绕通知 (4)异常通知 (5)最终通知
3.7.2 AspectJ 的切入点表达式(掌握)
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..(…)) 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点 execution( .service..(…)) 指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点 execution( .ISomeService.(…))
Java 框架 Spring4
34
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 execution(* …ISomeService.(…)) 指定所有包下的 ISomeSerivce 接口中所有方法为切入点 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,String s3)不是。 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(Object ob) 是,但,joke(String s)与 joke(User u)均不是。 execution(* joke(Object+))) 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。 不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。
3.7.3 AspectJ 的开发环境(掌握)
(1) maven 依赖
junit junit 4.12 test org.springframework spring-context 4.3.16.RELEASE
Java 框架 Spring4
35
org.springframework spring-aspects 4.3.16.RELEASE
插件 maven-compiler-plugin 3.1 1.8 1.8
(2) 引入 AOP 约束
在配置文件头部,要引入关于 aop 的约束。在 Spring 框架的解压目录中, \docs\spring-framework-reference\html 下的 xsd-configuration.html 文件中。
在前面 Spring 实现 AOP 时,并未引入 AOP 的约束,而在 AspectJ 实现 AOP 时,才提出 要引入 AOP 的约束。说明,配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的, 而非 Spring 框架本身在实现 AOP 时使用的。 AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。
3.7.4 AspectJ 基于注解的 AOP 实现(掌握)
AspectJ 提供了以注解方式对于 AOP 的实现。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
36
(1) 实现步骤
A、 Step1:定义业务接口与实现类
B、 Step2:定义切面类
类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。
C、 Step3:声明目标对象切面类对象
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
37
D、 Step4:注册 AspectJ 的自动代理
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理 对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的 自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并 生成代理。
aop:aspectj-autoproxy/的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。 从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。 其工作原理是,aop:aspectj-autoproxy/通过扫描找到@Aspect 定义的切面类,再由切 面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
E、 Step5:测试类中使用目标对象的 id
(2) @Before 前置通知-方法有 JoinPoint 参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。 不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该 参数。
Java 框架 Spring4
38
(3) @AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回 值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
接口增加方法:
实现方法:
定义切面:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
39
(4) @Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法 的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。 接口增加方法:
接口方法的实现:
定义切面:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
40
(5) @AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的 名称,表示发生的异常对象。 增加业务方法:
方法实现:
定义切面:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
41
(6) @After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。 增加方法:
方法实现:
定义切面:
(7) @Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解 的方法一般使用 private 的标识方法,即没有实际作用的方法。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
42
3.7.5 AspectJ 基于 XML 文件的 AOP
AspectJ 除了提供了基于注解的 AOP 的实现外,还提供了以 XML 方式的实现。 切面就是一个普通类,而用于增强的方法就是普通的方法。通过配置文件,将切面中的 功能增强织入到了目标类的目标方法中。
实现步骤
Step1:定义业务接口与实现类
接口:
实现类:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
43
Step2:定义切面类
定义列作为切面出现。其中定义了若干普通方法,将作为不同的通知方法。
Step3:注册目标对象与 POJO 切面类
Step4:在容器中定义 AOP 配置
配置文件中,除了要定义目标类与切面的 Bean 外,最主要的是在aop:config/中进行 aop 的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。 通过其子标签aop:pointcut/定义切入点,该标签有两个属性,id 与 expression。分别 用于指定该切入点的名称及切入点的值。expression 的值为 execution 表达式。
通过子标签aop:aspect/定义具体的织入规则:根据不同的通知类型,确定不同的织入 时间;将 method 指定的增强方法,按照指定织入时间,织入到切入点指定的目标方法中。
aop:aspect/的 ref 属性用于指定使用哪个切面。 aop:aspect/的子标签是各种不同的通知类型。不同的通知所包含的属性是不同的,但 也有共同的属性。 method:指定该通知使用的切面中的增强方法。 pointcut-ref:指定该通知要应用的切入点。 AspectJ 的 5 种通知的 XML 标签如下: ➢ aop:before/:前置通知 ➢ aop:after-returning/:后置通知 ➢ aop:around/:环绕通知 ➢ aop:after-throwing/:异常通知
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
44
➢ aop:after/:最终通知
Step5:测试类中使用目标对象的 id
3.8 Spring 实现 AOP
Spring 中 AOP 的可用接口: 前置通知:MethodBeforeAdvice 后置通知:AfterReturningAdvice 环绕通知:MethodInterceptor 异常通知:ThrowsAdvice
实现环绕通知:
Step1: 接口
Step2: 实现类
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
45
Step3: 环绕通知
Step4: Spring 配置文件
Step5: 测试类
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
46
第4章 Spring 集成 MyBatis
将 MyBatis与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring 来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注 册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。 实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理 Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插 上 mybatis,两个框架就是一个整体。
4.1.1 MySQL 创建数据库 springdb,新建表 Student
4.1.2 maven 依赖 pom.xml
junit junit 4.12 test org.springframework spring-context 4.3.16.RELEASE org.springframework spring-tx 4.3.16.RELEASE org.springframework
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
47
spring-jdbc 4.3.16.RELEASE
org.mybatis mybatis 3.4.5 org.mybatis mybatis-spring 1.3.1 mysql mysql-connector-java 5.1.9
com.alibaba druid 1.1.12
插件: src/main/java /*.properties /*.xml false
maven-compiler-plugin 3.1
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
48
1.8 1.8
4.1.3 定义实体类 Student
4.1.4 定义 StudentDao 接口
4.1.5 定义映射文件 mapper
在 Dao 接口的包中创建 MyBatis 的映射文件 mapper,命名与接口名相同,本例为 StudentDao.xml。mapper 中的 namespace 取值也为 Dao 接口的全限定性名。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
49
4.1.6 定义 Service 接口和实现类
接口定义:
实现类定义:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
50
4.1.7 定义 MyBatis 主配置文件
在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml。 这里有两点需要注意: (1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器来管理了。 (2)这里对 mapper 映射文件的注册,使用标签,即只需给出 mapper 映射文件 所在的包即可。因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。这种 方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的 标签方式。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
51
4.1.8 修改 Spring 配置文件
(1) 数据源的配置(掌握)
使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置在 Spring 配 置文件中。根据数据源的不同,其配置方式不同:
Druid 数据源 DruidDataSource
Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接池。Druid 能 够提供强大的监控和扩展功能。Druid 与其他数据库连接池的最大区别是提供数据库的
官网:https://github.com/alibaba/druid 使用地址:https://github.com/alibaba/druid/wiki/常见问题 配置连接池:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
52
Spring 配置文件:
(2) 从属性文件读取数据库连接信息
为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置文件从中读取 数据。 属性文件名称自定义,但一般都是放在 src 下。
Spring 配置文件从属性文件中读取数据时,需要在的 value 属性中使用${ }, 将在属性文件中定义的 key 括起来,以引用指定属性的值。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
53
该属性文件若要被 Spring 配置文件读取,其必须在配置文件中进行注册。使用 标签。 context:property-placeholder/方式(掌握) 该方式要求在 Spring 配置文件头部加入 spring-context.xsd 约束文件
context:property-placeholder/标签中有一个属性 location,用于指定属性文件的位置。
(3) 注册 SqlSessionFactoryBean
(4) 定义 Mapper 扫描配置器 MapperScannerConfigurer
Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本包中 mapper 的代 理对象。该 Bean 无需设置 id 属性。basePackage 使用分号或逗号设置多个包。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
54
4.1.9 向 Service 注入接口名
向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器 MapperScannerConfigurer 生成的 Mapper 代理对象没有名称,所以在向 Service 注入 Mapper 代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口 的对象。
4.1.10 Spring 配置文件全部配置
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
55
第5章 Spring 事务
5.1 Spring 的事务管理
事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层, 即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。 在 Spring 中通常可以通过以下两种方式来实现对事务的管理: (1)使用 Spring 的事务注解管理事务 (2)使用 AspectJ 的 AOP 配置管理事务
5.2 Spring 事务管理 API
Spring 的事务管理,主要用到两个事务相关的接口。
(1) 事务管理器接口(重点)
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回 滚,及获取事务的状态信息。
A、 常用的两个实现类
PlatformTransactionManager 接口有两个常用的实现类: ➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。 ➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
56
B、 Spring 的回滚方式(理解)
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时 提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
C、 回顾错误与异常(理解)
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一) 的实例时,才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。 Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、 ThreadDeath、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出) 的,JVM 一般会终止线程。 程序在编译和运行时出现的另一类错误称之为异常,它是JVM通知程序员的一种方式。 通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。 异常分为运行时异常与受查异常。 运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于 运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代 码编写足够仔细,程序足够健壮,运行时异常是可以避免的。 受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的 为 RuntimeException 的子类,那么定义的就是受查异常。
(2) 事务定义接口
事务定义接口TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
57
A、 定义了五个事务隔离级别常量(掌握)
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。 ➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。 ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。 ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。 ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 ➢ SERIALIZABLE:串行化。不存在并发问题。
B、 定义了七个事务传播行为常量(掌握)
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。 PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW PROPAGATION_SUPPORTS PROPAGATION_MANDATORY PROPAGATION_NESTED PROPAGATION_NEVER PROPAGATION_NOT_SUPPORTED
a、 PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
58
doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
b、 PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
c、 PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
59
C、 定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置 的 none 值。 注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值 一般就使用默认值即可。
5.3 程序举例环境搭建
举例:购买商品 trans_sale 项目 本例要实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存。
实现步骤:
Step0:创建数据库表
创建两个数据库表 sale , goods sale 销售表
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
60
goods 商品表
goods 表数据
Step1: maven 依赖 pom.xml
junit junit 4.12 test org.springframework spring-context 4.3.16.RELEASE
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
61
org.springframework spring-tx 4.3.16.RELEASE org.springframework spring-jdbc 4.3.16.RELEASE
org.mybatis mybatis 3.4.5 org.mybatis mybatis-spring 1.3.1 mysql mysql-connector-java 5.1.9
com.alibaba druid 1.1.12
插件 src/main/java /*.properties /*.xml false
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
62
maven-compiler-plugin 3.1 1.8
Step2:创建实体类
创建实体类 Sale 与 Goods
Step3:定义 dao 接口
定义两个 dao 的接口 SaleDao , GoodsDao
Step4:定义 dao 接口对应的 sql 映射文件
SaleDao.xml
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
63
GoodsDao.xml
Step5:定义异常类
定义 service 层可能会抛出的异常类 NotEnoughException
Step6:定义 Service 接口
定义 Service 接口 BuyGoodsService
Step7:定义 service 的实现类
定义 service 层接口的实现类 BuyGoodsServiceImpl
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
64
类定义
Dao 属性
Buy 方法
Step8:Spring 配置文件中添加最全约束
本例中将使用到 Spring 中 DI、AOP、事务等众多功能,所以将之前用过的所有约束进行 了综合。综合后的约束为:
声明业务层对象
Step10:定义测试类
定义测试类 MyTest。现在就可以在无事务代理的情况下运行了。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
66
5.4 使用 Spring 的事务注解管理事务(掌握)
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示: ➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。 ➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。 ➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。 ➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。 ➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。 ➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。 ➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。 ➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。 需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤: 复制 trans_sale 项目,新项目 trans_sale_annotation 1. 声明事务管理器
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
67
transaction-manager:事务管理器 bean 的 id
5.5 使用 AspectJ 的 AOP 配置管理事务(掌握)
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类 较多,配置文件会变得非常臃肿。 使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法 很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
68
Step1:复制项目
复制 trans_sale 项目,并重命名为 trans_sal_aspectj。在此基础上修改。
Step2:maven 依赖 pom.xml
新加入 aspectj 的依赖坐标 org.springframework spring-aspects 4.3.16.RELEASE
Step3:在容器中添加事务管理器
Step4:配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。 例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。
Step5:配置增强器
指定将配置好的事务通知,织入给谁。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
69
Step6:修改测试类
测试类中要从容器中获取的是目标对象。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
70
第6章 Spring 与 Web
在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring 容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。
6.1 Web 项目使用 Spring 的问题(了解)
举例:springWeb 项目(在 spring-mybatis 基础上修改)
Step1:新建一个 Maven Project
类型 maven-archetype-webapp
Step2: 复制代码,配置文件,jar
将 spring-mybatis 项目中以下内容复制到当前项目中: (1)Service 层、Dao 层全部代码 (2)配置文件 applicationContext.xml 及 jdbc.properties,mybatis.xml (3)pom.xml (4)加入 servlet ,jsp 依赖 javax.servlet javax.servlet-api 3.1.0 provided
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
71
Step3:定义 index 页面
Step4:定义 RegisterServlet(重点代码)
Step5:定义 success 页面
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
72
Step6:web.xml 注册 Servlet
Step7:运行结果分析
当表单提交,跳转到 success.jsp 后,多刷新几次页面,查看后台输出,发现每刷新一次 页面,就 new 出一个新的 Spring 容器。即,每提交一次请求,就会创建一个新的 Spring 容 器。对于一个应用来说,只需要一个 Spring 容器即可。所以,将 Spring 容器的创建语句放 在 Servlet 的 doGet()或 doPost()方法中是有问题的。
此时,可以考虑,将 Spring 容器的创建放在 Servlet 进行初始化时进行,即执行 init()方 法时执行。并且,Servlet 还是单例多线程的,即一个业务只有一个 Servlet 实例,所有执行 该业务的用户执行的都是这一个 Servlet 实例。这样,Spring 容器就具有了唯一性了。 但是,Servlet 是一个业务一个 Servlet 实例,即 LoginServlet 只有一个,但还会有 StudentServlet、TeacherServlet 等。每个业务都会有一个 Servlet,都会执行自己的 init()方法, 也就都会创建一个 Spring 容器了。这样一来,Spring 容器就又不唯一了。
6.2 使用 Spring 的监听器 ContextLoaderListener(掌握)
举例:springweb-2 项目(在 spring-web 项目基础上修改) 对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机, 放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性。 当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
73
访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的 全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就 保证了 Spring 容器的全局性。 上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中: spring-web-4.3.16.RELEASE
Step1:maven 依赖 pom.xml
org.springframework spring-web 4.3.16.RELEASE
Step2:注册监听器 ContextLoaderListener
若要在 ServletContext 初始化时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口 ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。
Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的 工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。 打开 ContextLoaderListener 的源码。看到一共四个方法,两个是构造方法,一个初始化 方法,一个销毁方法。
所以,在这四个方法中较重要的方法应该就是 contextInitialized(),context 初始化方法。
跟踪 initWebApplicationContext()方法,可以看到,在其中创建了容器对象。
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
74
并且,将创建好的容器对象放入到了 ServletContext 的空间中,key 为一个常量: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
Step3:指定 Spring 配置文件的位置
ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认 的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文 件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及 名称进行指定。
从监听器 ContextLoaderListener 的父类 ContextLoader 的源码中可以看到其要读取的配 置文件位置参数名称 contextConfigLocation。
Step4:获取 Spring 容器对象
在 Servlet 中获取容器对象的常用方式有两种:
Java 框架 Spring4
北京动力节点 www.bjpowernode.com 讲师王鹤
75
(1) 直接从 ServletContext 中获取
从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存 放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可 以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。
(2) 通过 WebApplicationContextUtils 获取
工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring 容器对象:getRequiredWebApplicationContext(ServletContext sc)
调用 Spring 提供的方法获取容器对象:
查其源码,看其调用关系,就可看到其是从 ServletContext 中读取的属性值,即 Spring 容器。
以上两种方式,无论使用哪种获取容器对象,刷新 success 页面后,可看到代码中使用 的 Spring 容器均为同一个对象。