概述 什么是Spring框架
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发 的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可 以在 Java SE/EE 中使用的轻量级开源框架
Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度 。就是让对象和对象(模 块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模 块)的关系。
Spring的优点 Spring 是一个框架,是一个半成品的软件。有 20 个模块组成。它是一个容器管理对象, 容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。
(1) 轻量 Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需 的 jar 总共在 3M 左右。 Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar
(2) 针对接口编程,解耦合 Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的 对象创建方式,现在由容器完成。对象之间的依赖解耦合。
(3) AOP 编程的支持 通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现 的功能可以通过 AOP 轻松应付 在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地 进行事务的管理,提高开发效率和质量。
(4) 方便集成各种优秀框架 Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Struts,Hibernate、MyBatis)等的直接支持。简化框架的使用。 Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把 这个插头放入插线板。不需要可以轻易的移除。
Spring 体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理(Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。
IoC控制翻转 控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理 。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。
IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖 注入。应用广泛。
依赖 :classA
类中含有 classB
的实例,在 classA
中调用 classB
的方法完成功能,即 classA
对 classB
有依赖。
IoC的是实现:
依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建 被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。 Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系 的管理。
Spring 框架使用依赖注入(DI)实现 IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称 为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
Spring 第一个程序 1. 创建 Maven
项目 2. 引入 Maven
依赖 pom.xml
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.5.RELEASE</version > </dependency >
3. 定义接口和实体类 1 2 3 public interface SomeService { void doSome () ; }
1 2 3 4 5 6 7 8 9 public class SomeServiceImpl implements SomeService { public SomeServiceImpl () { System.out.println("SomeServiceImpl.SomeServiceImpl" ); } @Override public void doSome () { System.out.println("SomeServiceImpl.doSome" ); } }
4. 创建Spring配置文件 在 src/main/resources/
目录现创建一个 xml
文件,文件名可以随意,但 Spring 建议的名 applicationContext.xml
。
spring 配置中需要加入约束文件才能正常使用,约束文件是 xsd
扩展名。
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="someService" class ="com.bjpowernode.service.impl.SomeServiceImpl" /> <bean id ="someService1" class ="com.bjpowernode.service.impl.SomeServiceImpl" /> <bean id ="mydate" class ="java.util.Date" /> </beans >
<bean />
:用于定义一个实例对象。一个实例对应一个 bean 元素。
id
:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依 赖关系也是通过 id 属性关联的。
class
:指定该 Bean 所属的类,注意这里只能是类,不能是接口。
5. 定义测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void test02 () { String config = "beans.xml" ; ApplicationContext ac = new ClassPathXmlApplicationContext(config); SomeService service = (SomeService) ac.getBean("someService" ); service.doSome(); }
6. 使用 spring 创建非自定义类对象 spring 配置文件加入 java.util.Date
定义:
1 <bean id ="myDate" class ="java.util.Date" />
MyTest
测试类中: 调用 getBean(“myDate”);
获取日期类对象。
7. 容器接口和实现类 ApplicationContext 接口(容器) ApplicationContext 用于加载 Spring 的配置文件,在程序中充当“容器”的角色。其实现类有两个。
配置文件在类路径下 若 Spring 配置文件存放在项目的类路径下,则使用 ClassPathXmlApplicationContext 实现 类进行加载
ApplicationContext 容器中对象的装配时机 ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。 以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
基于 XML 的 DI DI: 依赖注入
DI的实现有两种:
在spring的配置文件中,使用标签和属性完成,叫做基于XML的di实现
使用spring中的注解,完成属性赋值,叫做基于注解的di实现
di
的语法分类:
set注入(设置注入) : spring调用类的set方法,在set方法中实现属性的赋值 80%左右都是使用set注入
构造注入,spring调用类的有参数构造方法,创建对象。在构造方法中完成赋值
注入分类 bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化 是由容器自动完成的,称为注入。 根据注入方式的不同,常用的有两类:set 注入、构造注入。
set注入 简单类型 set 注入也叫设值注入是指,通过 setter
方法传入被调用者的实例。这种注入方式简单、 直观,因而在 Spring 的依赖注入中大量使用
1 2 3 4 5 6 7 8 <bean id ="myStudent" class ="com.bjpowernode.ba01.Student" > <property name ="name" value ="李四" /> <property name ="age" value ="20" /> <property name ="email" value = "99799@qq.com" /> </bean > <bean id ="mydate" class ="java.util.Date" > <property name ="time" value ="23423114234223" /> </bean >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Student { private String name; private int age; @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } public void setName (String name) { this .name = name; } public void setAge (Integer age) { this .age = age; } public void setEmail (String email) { System.out.println("email = " + email); } }
引用类型 当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。
1 2 3 4 5 6 7 8 9 <bean id ="myStudent" class ="com.bjpowernode.ba02.Student" > <property name ="name" value ="李四" /> <property name ="age" value ="20" /> <property name ="school" ref ="mySchool" /> </bean > <bean id ="mySchool" class ="com.bjpowernode.ba02.School" > <property name ="name" value ="北京大学" /> <property name ="address" value ="北京海淀区" /> </bean >
构造注入 构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。
1 2 3 4 5 6 public Student (String myname, int myage, School mySchool) { System.out.println("===========Studnet有参数构造方法=========" ); this .name = myname; this .age = myage; this .school = mySchool; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean id ="myStudent" class ="com.bjpowernode.ba03.Student" > <constructor-arg name ="myname" value ="张三" /> <constructor-arg name ="myage" value ="20" /> <constructor-arg name ="mySchool" ref ="myXuexiao" /> </bean > <bean id ="myXuexiao" class ="com.bjpowernode.ba03.School" > <property name ="name" value ="北京大学" /> <property name ="address" value ="北京海淀区" /> </bean > <bean id ="myStudent2" class ="com.bjpowernode.ba03.Student" > <constructor-arg index ="0" value ="李四" /> <constructor-arg index ="1" value ="26" /> <constructor-arg index ="2" ref ="myXuexiao" /> </bean >
构造注入 : spring调用类的有参数构造方法,在创建对象的同时,在构造方法中给属性赋值 使用 <constructor-arg> 标签 <constructor-arg> 标签: 一个<constructor-arg>表示构造方法中一个参数 <constructor-arg> 标签属性: name: 表示构造方法的形参名 index: 表示构造方法参数的位置,参数从左往右是0, 1, 2的顺序 value: 构造方法的形参类型是简单类型的,使用value ref: 构造方法的形参类型是引用类型,使用ref进行赋值
引用类型属性自动注入 对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性 )。根据自动注入判断标准的不同,可以分为两种:
byName:根据名称自动注入
byType: 根据类型自动注入、
byName方式自动注入 当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的set方法后缀名 相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean 类的set方法后缀名 与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
1 2 3 4 5 6 7 8 9 <bean id ="myStudent" class ="com.bjpowernode.ba04.Student" autowire ="byName" > <property name ="name" value ="李四" /> <property name ="age" value ="20" /> </bean > <bean id ="school" class ="com.bjpowernode.ba04.School" > <property name ="name" value ="清华大学" /> <property name ="address" value ="北京海淀区" /> </bean >
1 2 3 4 5 6 7 8 9 10 11 public class Student { private String name; private int age; private School schooll; public void setSchool (School schooll) { System.out.println("setSchool" + schooll); this .schooll = schooll; } ....... }
byType 方式自动注入 使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
1 2 3 4 5 6 7 8 <bean id ="myStudent" class ="com.bjpowernode.ba05.Student" autowire ="byType" > <property name ="name" value ="张萨" /> <property name ="age" value ="26" /> </bean > <bean id ="primarySchool" class ="com.bjpowernode.ba05.PrimarySchool" > <property name ="name" value ="北京小学" /> <property name ="address" value ="北京大兴区" /> </bean >
为应用指定多个 Spring 配置文件 1 2 3 4 5 6 7 8 9 <import resource ="classpath:ba06/spring-*.xml" />
基于注解的DI 使用注解的步骤:
加入Maven依赖 spring-context , 在你加入spring-context的同时, 已经间接加入spring-aop依赖,使用注解必须使用spring-aop依赖
在类中加入 spring 的注解(多个不同功能的注解)
在 spring 配置文件中,加入一个组件扫描器的标签,说明注解在项目中的位置
使用注解创建对象,创建容器ApplicationContext
举例:
1 2 3 4 5 6 7 8 9 10 11 @Component(value = "myStudent") public class Student {.......... }
声明组件扫描器(component-scan), 组件就是java对象 base-package: 指定注解在你的项目中的包名 component-scan工作方式:spring会扫描遍历base-package指定的包, 扫描包中和子包中所有类,找到类中的注解,按照注解功能创建对象,或给组件赋值
1 <context:component-scan base-package ="com.bjpowernode.ba01" />
定义 Bean 的注解@Component 需要在类上使用注解@Component,该注解的 value 属性用于指定该 bean 的 id 值。
1 2 3 @Component("myStudent")
另外,Spring 还提供了 3 个创建对象的注解:
@Repository 用于对 DAO 实现类进行注解(持久层类)
@Service 用于对Service实现类进行注解(业务层类)
@Controller 用于对 Controller实现类进行注解 (用在控制器上)
这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处 理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对 象。即持久层对象,业务层对象,控制层对象。
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
简单类型属性注入@Value 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Component("myStudent") public class Student { @Value("张飞") private String name; private Integer age; public void setName (String name) { System.out.println("HHH" ); this .name = name; } @Value("30") public void setAge (Integer age) { System.out.println("setAge:" + age); this .age = age; } }
byType 自动注入@Autowired 需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。 使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。
byName 自动注入@Autowired 与@Qualifier 需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
1 2 3 @Autowired @Qualifier("mySchool") private School school;
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运 行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
1 2 3 @Autowired(required = false) @Qualifier("mySchool1") private School school;
JDK 注解@Resource 自动注入 Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。默认是按名称注入 , 如何byName赋值失败,则使用byType, 使用该注解,要求 JDK 必须是 6 及以上版本。 @Resource 可在属性上,也可在 set 方法上
如果是jdk11以上版本,下pom.xml文件中添加
1 2 3 4 5 <dependency > <groupId > javax.annotation</groupId > <artifactId > javax.annotation-api</artifactId > <version > 1.2</version > </dependency >
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
1 2 @Resource(name="mySchool") private School school;
注解和XML的对比 注解的优点是:
弊端:以硬编码的方式写入Java代码中,修改是需要重新编译代码的
XML方式的优点:
配置和代码是分离的
在 xml 中做修改,无需编译代码,只需重启服务器将新的配置加载
xml的缺点:编写麻烦,效率低, 大型项目过于复杂
AOP面向切面编程 动态代理 复习JDK动态代理的实现步骤:
创建目标类,SomeServiceImpl目标类,给它的doSome, doOther增加 输出时间,事务
1 2 3 4 5 6 7 8 9 10 11 public class SomeServiceImpl implements SomeService { @Override public void doSome () { System.out.println("执行业务方法doSome()" ); } @Override public void doOther () { System.out.println("执行业务方法doOther()" ); } }
1 2 3 4 5 6 7 8 public class ServiceTools { public static void doLog () { System.out.println("非业务方法:方法的执行时间" + new Date()); } public static void doTrans () { System.out.println("非业务方法: 方法结束,提交事务" ); } }
创建InvocationHandler接口的实现类,在这个类实现给目标方法增加功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.bjpowernode.handler;import com.bjpowernode.util.ServiceTools;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("执行了MyInvocationHandler中的invoke()方法" ); System.out.println("method名称: " + method.getName()); String methodName = method.getName(); Object res = null ; if ("doSome" .equals(methodName)) { ServiceTools.doLog(); res = method.invoke(target, args); ServiceTools.doTrans(); } else { res = method.invoke(target, args); } return res; } }
使用jdk中 类Proxy, 创建代理对象。 实现创建对象的能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.bjpowernode;import com.bjpowernode.handler.MyInvocationHandler;import com.bjpowernode.service.SomeService;import com.bjpowernode.service.impl.SomeServiceImpl;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;public class MyApp { public static void main (String[] args) { SomeService target = new SomeServiceImpl(); InvocationHandler handler = new MyInvocationHandler(target); SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); proxy.doSome(); System.out.println("========================================" ); proxy.doOther(); } }
AOP概述 AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理 ,与 CGLIB 的动态代理 。
实现方式:
jdk动态代理:使用jdk中的Proxy, Method, InvocationHandler创建代理对象。
cglib动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类,子类就是代理对象。要求目标类不能是final的,方法也不能是final的
动态代理的作用:
在目标类源代码不改变的情况下,增加功能
减少代码的重复
专注业务逻辑代码
解耦合,让你的业务功能和日志,事务非业务功能分离
AOP: 面向切面编程,基于动态代理的,使用jdk, cglib两种方式。 AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了。让开发人员用一种统一的方式,使用动态代理。
Aspect: 切面,给你的目标类增加的功能,就是切面。像上面用的日志,事务都是切面。
切面的特点:一般都是非业务方法,独立使用的。
怎么理解面向切面编程 ?
AOP 编程术语 切面(Aspect) 表示增强的功能,就是一堆代码,完成某一个功能,非业务功能,常见的切面功能有日志,事务,统计信息,参数检查,权限验证。
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
连接点(JoinPoint) 连接业务方法和切面的位置,就是某类中的业务方法
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
切入点(Pointcut) 指多个连接点方法的集合。多个方法
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不 能被增强的。
目标对象(Target) 去给哪个类的方法增加功能,这个类就是目标对象
通知(Advice) 表示切面功能执行的时间
切入点定义切入的位置,通知定义切入的时间。
切面有三个关键的要素:
AspectJ 对 AOP 的实现
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框 架中。 在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
AspectJ框架实现aop有两种方式:
使用xml的的配置文件:配置全局事务
使用注解,一般在项目中使用注解实现aop功能,aspectJ有5个注解
AspectJ的通知类型 切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强)
在 aspectJ框架中使用注解表示的。也可以使用xml配置文件中的标签
前置通知 @Before
后置通知 @AfterReturning
环绕通知 @Around
异常通知 @AfterThrowing
最终通知 @After
AspectJ 的切入点表达式 表示切面执行的位置,使用的是切入点表达式。
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
1 2 3 execution(modifiers-pattern ? ret-type-pattern declaring-type-pattern ?name-pattern (param-pattern ) throws-pattern ?)
modifiers-pattern : 访问权限类型
ret-type-pattern :返回值类型
declaring-type-pattern :包名类名
name-pattern(param-pattern) :方法名(参数类型和参数个数)
throws-pattern : 抛出异常类型
? 表示可选的部分
以上表达式共 4 个部分。 execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可 以使用以下符号:
举例 execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.*.*(..))
指定切入点为:定义在service包里任意类的任意方法
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。
“..”出现在类名中,后面必须跟 “*” ,表示包,子包下的所有类
execution(* *..service.*.*(..))
指定所有包下的 service 子包下所有类(接口)中所有方法为切入点
AspectJ 的开发环境 使用aspectj实现aop的基本步骤:
新建Maven项目
加入依赖
spring依赖
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.5.RELEASE</version > </dependency >
aspectj依赖
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.2.5.RELEASE</version > </dependency >
junit单元测试
1 2 3 4 5 6 <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.11</version > <scope > test</scope > </dependency >
创建目标类,接口和它的实现类,要做的是给类中的方法增加功能
创建切面类 :普通类
在类的上面加入 @Aspect
在类中定义方法,方法就是切面要执行的功能代码,
在方法的上面加入aspectj的通知注解,例如@Before
还需要指定切入点表达式execution()
创建spring的配置文件:声明对象,把对象交给容器统一管理
声明目标对象
声明切面类对象
声明aspectj框架中的自动代理生成器标签
创建测试类,从spring容器中获取目标对象(实际上就是代理对象)
通过代理执行方法,实现aop的功能增强
Aspect基于注解的AOP实现 实现步骤 定义业务接口与实现类 1 2 3 public interface SomeService { void doSome (String name, Integer age) ; }
1 2 3 4 5 6 7 public class SomeServiceImpl implements SomeService { @Override public void doSome (String name, Integer age) { System.out.println("============目标方法doSome()========" ); } }
定义切面类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Aspect public class MyAspect { @Before(value="execution(public void com.bjpowernode.ba01.SomeServiceImpl.doSome(String, Integer))") public void MyBefore () { System.out.println("前置通知,切面功能: 在目标方法之前输出执行时间: " + new Date()); } }
声明目标对象,切面类对象 1 2 3 4 <bean id ="someService" class ="com.bjpowernode.ba01.SomeServiceImpl" /> <bean id ="myAspect" class ="com.bjpowernode.ba01.MyAspect" />
注册 AspectJ 的自动代理 1 <aop:aspectj-autoproxy />
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理 对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的 自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并 生成代理。
声明自动代理生成器:使用aspectj框架内部功能,创建目标对象的代理对象。 创建代理对象是在内存中实现的,修改目标对象的内存中的结构 ,创建为一个代理对象。 所以目标对象就是被修改后的代理对象aspectj-autoproxy
: 会把spring容器中的所有的目标对象,一次性都生成代理对象
<aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。 从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。 其工作原理是,通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
测试类中使用目标对象的 id 1 2 3 4 5 6 7 8 9 @Test public void test01 () { String config = "applicationContext.xml" ; ApplicationContext ctx = new ClassPathXmlApplicationContext(config); SomeService proxy = (SomeService) ctx.getBean("someService" ); proxy.doSome("lisi" , 20 ); }
@Before 前置通知-方法有 JoinPoint 参数 在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。 不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该 参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Before(value="execution(void *..SomeServiceImpl.doSome(String, Integer))") public void MyBefore (JoinPoint jp) { System.out.println("方法的签名(定义) = " + jp.getSignature()); System.out.println("方法的名称 = " + jp.getSignature().getName()); Object[] args = jp.getArgs(); for (Object arg : args) { System.out.println("参数 = " + arg); } System.out.println("2======前置通知,切面功能: 在目标方法之前输出执行时间: " + new Date()); }
@AfterReturning 后置通知-注解有 returning 属性 在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。
该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
接口增加的方法 1 2 3 4 5 public interface SomeService { void doSome (String name, Integer age) ; String doOther (String name, Integer age) ; Student doOther2 (String name, Integer age) ; }
实现方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class SomeServiceImpl implements SomeService { @Override public void doSome (String name, Integer age) { System.out.println("============目标方法doSome()========" ); } @Override public String doOther (String name, Integer age) { System.out.println("=========== 目标方法doOther() =============" ); return "abcd" ; } @Override public Student doOther2 (String name, Integer age) { Student student = new Student(); student.setName("lisi" ); student.setAge(20 ); return student; } }
定义切面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Aspect public class MyAspect { @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))", returning = "res") public void myAfterReturning (Object res) { System.out.println("后置通知:在目标方法之后执行的,获取的返回值是 : " + res); if (res.equals("abcd" )) { } else { } if (res != null ) { res = "Hello Aspectj" ; } } @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther2(..))", returning = "res") public void myAfterReturning2 (Object res) { System.out.println("后置通知:在目标方法之后执行的,获取的返回值是 : " + res); Student student = (Student)res; student.setName("牛可乐" ); student.setAge(3 ); } }
@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数 在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法 的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法: 1 2 3 public interface SomeService { String doFirst (String name, Integer age) ; }
接口方法的实现: 1 2 3 4 5 6 7 public class SomeServiceImpl implements SomeService { @Override public String doFirst (String name, Integer age) { System.out.println("--------- 业务方法doFirst ------------" ); return "doFirst" ; } }
定义切面: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Aspect public class MyAspect { @Around(value = "execution(* * ..SomeServiceImpl.doFirst(..))") public Object myAround (ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); String name = "" ; if (args != null && args.length >= 1 ) { Object arg = args[0 ]; name = (String)arg; } Object result = null ; System.out.println("环绕通知:在目标方法之前,输出时间:" + new Date()); if ("zhangsan" .equals(name)) { result = pjp.proceed(); } System.out.println("环绕通知:在目标方法之后,提交事务!" ); if (result != null ) { result = "Hello AspectJ AOP" ; } return result; } }
@AfterThrowing 异常通知-注解中有 throwing 属性 在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的 名称,表示发生的异常对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Aspect public class MyAspect { @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))", throwing = "ex") public void myAfterThrowing (Exception ex) { System.out.println("异常通知:方法发生异常时,执行: " + ex.getMessage()); } }
@After 最终通知 无论目标方法是否抛出异常,该增强均会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Aspect public class MyAspect { @After(value = "execution(* *..SomeServiceImpl.doThird(..))") public void myAfter () { System.out.println("执行最终通知,总是会执行" ); } }
@Pointcut 定义切入点 当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解 的方法一般使用 private 的标识方法,即没有实际作用的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Aspect public class MyAspect { @After(value = "mypt()") public void myAfter () { System.out.println("执行最终通知,总是会执行" ); } @Before(value = "mypt()") public void myBefore () { System.out.println("前置通知,目标方法之前执行" ); } @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))") private void mypt () { } }
如果目标类没有接口,使用cglib动态代理,Spring框架会自动应用cglib
1 2 3 4 5 <aop:aspectj-autoproxy proxy-target-class ="true" />
Spring 集成 MyBatis 将mybatis框架和spring集成到一起,像一个框架一样使用
用到的技术:ioc
为什么ioc:能够把mybatis和spring集成到一起,像一个框架,是因为ioc能创建对象。可以把mybatis中的对象交给spring统一创建,开发人员从spring中获取对象。开发人员就不用同时面对两个或多个框架了,就面对一个spring
mybatis使用步骤,对象
定义dao接口,StudentDao
定义mapper文件,StudentDao.xml
定义mybatis 的主配置文件
创建dao代理对象,StudentDao dao = SqlSession.getMapper(StudentDao.class)
List<Student> students = dao.selectStudents();
要使用dao对象,需要使用getMapper() 方法
怎么能使用getMapper()方法,需要哪些条件
获取 SqlSession对象, 使用SqlSessionFactory的 openSession() 方法
创建SqlSessionFactory对象。 通过读取mybatis主配置文件,能创建SqlSessionFactory对象
需要有SqlSessionFactory对象,使用Factory能获取SqlSession, 有了SqlSession就能有dao, 目的就是获取dao对象
Factory创建需要读取主配置文件
主配置文件:
数据库信息
mapper文件的位置
我们会使用独立的连接池类替换mybatis默认自己带的,把连接池类也交给spring创建
通过以上说明,我们需要spring创建以下对象
独立的连接池类的对象,使用阿里的druid连接池
SqlSessionFactory对象
创建dao对象
需要学习的就是上面三个对象的创建语法,使用xml文件的bean标签
将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring 来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。 实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理 Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插 上 mybatis,两个框架就是一个整体。
步骤:
新建Maven项目
加入maven依赖
spring 依赖
mybatis 依赖
mysql驱动
spring事务的依赖
mybatis和spring集成的依赖:mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory, dao对象的
创建实体类
创建dao接口mapper文件
创建mybatis主配置文件
创建Service接口实现类,属性是dao
创建spring配置文件:声明mybatis对象交给spring创建
数据源
SqlSessionFactory
Dao对象
声明自定义的service
创建测试类,获取Service对象,通过service调用dao完成数据库的访问
MySQL 创建数据库 springdb,新建表 Student maven 依赖 pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.bjpowernode</groupId > <artifactId > ch07-spring-mybatis</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.11</version > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.5.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-tx</artifactId > <version > 5.2.5.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.2.5.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.1</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.1</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.9</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.12</version > </dependency > </dependencies > <build > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > </resources > <plugins > <plugin > <artifactId > maven-compiler-plugin</artifactId > <version > 3.1</version > <configuration > <source > 1.8</source > <target > 1.8</target > </configuration > </plugin > </plugins > </build > </project >
定义实体类 Student 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Student { private Integer id; private String name; private String email; private Integer age; public Student () { } public Student (Integer id, String name, String email, Integer age) { this .id = id; this .name = name; this .email = email; this .age = age; } public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } @Override public String toString () { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}' ; } }
定义 StudentDao 接口 1 2 3 4 public interface StudentDao { int insertStudent (Student student) ; List<Student> selectStudents () ; }
定义映射文件 mapper 1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.bjpowernode.dao.StudentDao" > <insert id="insertStudent" > insert into student values (#{id}, #{name}, #{email}, #{age}) </insert> <select id ="selectStudents" resultType="com.bjpowernode.domain.Student" > select id, name, email, age from student order by id desc </select> </mapper>
定义 Service 接口和实现类 1 2 3 4 public interface StudentService { int addStudent (Student student) ; List<Student> queryStudents () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class StudentServiceImpl implements StudentService { private StudentDao studentDao; public void setStudentDao (StudentDao studentDao) { this .studentDao = studentDao; } @Override public int addStudent (Student student) { int nums = studentDao.insertStudent(student); return nums; } @Override public List<Student> queryStudents () { List<Student> students = studentDao.selectStudents(); return students; } }
定义 MyBatis 主配置文件 在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml。
这里有两点需要注意
(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器来管理了。
(2)这里对 mapper 映射文件的注册,使用<package/>标签,即只需给出 mapper 映射文件 所在的包即可。因为 mapper 的名称与 Dao 接口名相同,可以使用这种简单注册方式。这种 方式的好处是,若有多个映射文件,这里的配置也是不用改变的。当然,也可使用原来的<resource/>标签方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings > <typeAliases > <package name ="com.bjpowernode.domain" /> </typeAliases > <mappers > <package name ="com.bjpowernode.dao" /> </mappers > </configuration >
修改 Spring 配置文件 (1) 数据源的配置 使用 JDBC 模板,首先需要配置好数据源,数据源直接以 Bean 的形式配置在 Spring 配 置文件中。根据数据源的不同,其配置方式不同:
Druid 数据源 DruidDataSource
Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接池。Druid 能够提供强大的监控和扩展功能。Druid 与其他数据库连接池的最大区别是提供数据库的
官网:https://github.com/alibaba/druid
使用地址:https://github.com/alibaba/druid/wiki/
配置连接池:
(2) 从属性文件读取数据库连接信息 为了便于维护,可以将数据库连接信息写入到属性文件中,使 Spring 配置文件从中读取数据。
属性文件名称自定义,但一般都是放在 src 下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <util:properties id ="jdbc" local-override ="true" location ="classpath:jdbc.properties" /> <bean id ="myDataSource" class ="com.alibaba.druid.pool.DruidDataSource" init-method ="init" destroy-method ="close" > <property name ="url" value ="#{jdbc['jdbc.url']}" /> <property name ="username" value ="#{jdbc['jdbc.username']}" /> <property name ="password" value ="#{jdbc['jdbc.passwd']}" /> <property name ="maxActive" value ="#{jdbc['jdbc.max']}" /> </bean >
(3) 注册 SqlSessionFactoryBean 1 2 3 4 5 6 7 8 9 10 11 <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="myDataSource" /> <property name ="configLocation" value ="classpath:mybatis.xml" /> </bean >
(4) 配置DAO类 定义 Mapper 扫描配置器 MapperScannerConfigurer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" /> <property name ="basePackage" value ="com.bjpowernode.dao" /> </bean >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testDaoInsert () { String config = "applicationContext.xml" ; ApplicationContext ctx = new ClassPathXmlApplicationContext(config); StudentDao dao = (StudentDao) ctx.getBean("studentDao" ); Student student = new Student(); student.setId(1013 ); student.setName("周峰" ); student.setEmail("kkscsa@qq.com" ); student.setAge(20 ); int nums = dao.insertStudent(student); System.out.println("num = " + nums); }
向 Service 注入接口名 1 2 3 4 <bean id ="studentService" class ="com.bjpowernode.service.impl.StudentServiceImpl" > <property name ="studentDao" ref ="studentDao" /> </bean >
Spring 配置文件全部配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?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:context ="http://www.springframework.org/schema/util" xmlns:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd" > <util:properties id ="jdbc" local-override ="true" location ="classpath:jdbc.properties" /> <bean id ="myDataSource" class ="com.alibaba.druid.pool.DruidDataSource" init-method ="init" destroy-method ="close" > <property name ="url" value ="#{jdbc['jdbc.url']}" /> <property name ="username" value ="#{jdbc['jdbc.username']}" /> <property name ="password" value ="#{jdbc['jdbc.passwd']}" /> <property name ="maxActive" value ="#{jdbc['jdbc.max']}" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="myDataSource" /> <property name ="configLocation" value ="classpath:mybatis.xml" /> </bean > <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" /> <property name ="basePackage" value ="com.bjpowernode.dao" /> </bean > <bean id ="studentService" class ="com.bjpowernode.service.impl.StudentServiceImpl" > <property name ="studentDao" ref ="studentDao" /> </bean > </beans >
Spring 事务 Spring 的事务管理 事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层, 即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
(1)使用 Spring 的事务注解管理事务
(2)使用 AspectJ 的 AOP 配置管理事务
Spring 事务管理 API Spring 的事务管理,主要用到两个事务相关的接口。
(1) 事务管理器接口 事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
常用的两个实现类 PlatformTransactionManager 接口有两个常用的实现类:
DataSourceTransactionManager :使用 JDBC 或 MyBatis 进行数据库操作时使用。
HibernateTransactionManager :使用 Hibernate 进行持久化数据时使用。
Spring 的回滚方式 Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时 提交 。不过,对于受查异常,程序员也可以手工设置其回滚方式。
回顾错误与异常
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一) 的实例时,才能通过 Java 虚拟机或者 Java 的 throw 语句抛出。
Error 是程序在运行过程中出现的无法处理的错误,比如 OutOfMemoryError、 ThreadDeath、NoSuchMethodError 等。当这些错误发生时,程序是无法处理(捕获或抛出) 的,JVM 一般会终止线程。
程序在编译和运行时出现的另一类错误称之为异常,它是 JVM 通知程序员的一种方式。 通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。
异常分为运行时异常与受查异常。
运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于 运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代 码编写足够仔细,程序足够健壮,运行时异常是可以避免的。
受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。
RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的 Exception 的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的 为 RuntimeException 的子类,那么定义的就是受查异常。
(2) 事务定义接口 事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
定义了五个事务隔离级别常量 这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
DEFAULT :采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
READ_UNCOMMITTED:读未提交。未解决任何并发问题。
READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
SERIALIZABLE:串行化。不存在并发问题。
定义了七个事务传播行为常量 所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,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()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
b、PROPAGATION_SUPPORTS 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
c、 PROPAGATION_REQUIRES_NEW 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
定义了默认事务超时时限 常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。
注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。
程序举例环境搭建 Step0:创建数据库表 创建两个数据库表 sale , goods
sale 销售表
goods 商品表
Step1: maven 依赖 pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.bjpowernode</groupId > <artifactId > ch08-spring-trans</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.11</version > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.5.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-tx</artifactId > <version > 5.2.5.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.2.5.RELEASE</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.1</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.1</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.9</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.12</version > </dependency > </dependencies > <build > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > </resources > <plugins > <plugin > <artifactId > maven-compiler-plugin</artifactId > <version > 3.1</version > <configuration > <source > 1.8</source > <target > 1.8</target > </configuration > </plugin > </plugins > </build > </project >
Step2:创建实体类 创建实体类 Sale 与 Goods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class Sale { private Integer id; private Integer gid; private Integer nums; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public Integer getGid () { return gid; } public void setGid (Integer gid) { this .gid = gid; } public Integer getNums () { return nums; } public void setNums (Integer nums) { this .nums = nums; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Goods { private Integer id; private String name; private Integer amount; private Float price; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAmount () { return amount; } public void setAmount (Integer amount) { this .amount = amount; } public Float getPrice () { return price; } public void setPrice (Float price) { this .price = price; } }
Step3:定义 dao 接口 1 2 3 4 public interface SaleDao { int insertSale (Sale sale) ; }
1 2 3 4 5 6 7 8 public interface GoodsDao { int updateGoods (Goods goods) ; Goods selectGoods (Integer id) ; }
Step4:定义 dao 接口对应的 sql 映射文件 SaleDao.xml
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.bjpowernode.dao.SaleDao" > <insert id ="insertSale" > insert into sale(gid, nums) values(#{gid}, #{nums}) </insert > </mapper >
GoodsDao.xml
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.bjpowernode.dao.GoodsDao" > <select id ="selectGoods" resultType ="com.bjpowernode.domain.Goods" > select id, name, amount, price from goods where id = #{gid} </select > <update id ="updateGoods" > update set amount = amount - #{amount} where id = #{id} </update > </mapper >
Step5:定义异常类 定义 service 层可能会抛出的异常类 NotEnoughException
1 2 3 4 5 6 7 8 9 public class NotEnoughException extends RuntimeException { public NotEnoughException () { super (); } public NotEnoughException (String message) { super (message); } }
Step6:定义 Service 接口 1 2 3 4 public interface BuyGoodsService { void buy (Integer goodsId, Integer nums) ; }
Step7:定义 service 的实现类 定义 service 层接口的实现类 BuyGoodsServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class BuyGoodsServiceImpl implements BuyGoodsService { private SaleDao saleDao; private GoodsDao goodsDao; @Override public void buy (Integer goodsId, Integer nums) { System.out.println("=============== buy 方法的开始 ===================" ); Sale sale = new Sale(); sale.setGid(goodsId); sale.setNums(nums); saleDao.insertSale(sale); Goods goods = goodsDao.selectGoods(goodsId); if (goods == null ) { throw new NullPointerException("编号是:" + goodsId + " ,商品不存在" ); } else if (goods.getAmount() < nums) { throw new NotEnoughException("编号是: " + goodsId + " ,商品库存不足" ); } Goods buyGoods = new Goods(); buyGoods.setId(goodsId); buyGoods.setAmount(nums); goodsDao.updateGoods(buyGoods); System.out.println("============= buy方法的完成 ===============" ); } public void setSaleDao (SaleDao saleDao) { this .saleDao = saleDao; } public void setGoodsDao (GoodsDao goodsDao) { this .goodsDao = goodsDao; } }
Step8:修改 Spring 配置文件内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?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:context ="http://www.springframework.org/schema/util" xmlns:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd" > <util:properties id ="jdbc" local-override ="true" location ="classpath:jdbc.properties" /> <bean id ="myDataSource" class ="com.alibaba.druid.pool.DruidDataSource" init-method ="init" destroy-method ="close" > <property name ="url" value ="#{jdbc['jdbc.url']}" /> <property name ="username" value ="#{jdbc['jdbc.username']}" /> <property name ="password" value ="#{jdbc['jdbc.passwd']}" /> <property name ="maxActive" value ="#{jdbc['jdbc.max']}" /> </bean > <bean id ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="myDataSource" /> <property name ="configLocation" value ="classpath:mybatis.xml" /> </bean > <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="sqlSessionFactoryBeanName" value ="sqlSessionFactory" /> <property name ="basePackage" value ="com.bjpowernode.dao" /> </bean > <bean id ="buyService" class ="com.bjpowernode.service.impl.BuyGoodsServiceImpl" > <property name ="goodsDao" ref ="goodsDao" /> <property name ="saleDao" ref ="saleDao" /> </bean > </beans >
Step9:定义测试类 定义测试类 MyTest。现在就可以在无事务代理的情况下运行了。
1 2 3 4 5 6 7 8 9 10 public class MyTest { @Test public void test01 () { String config = "applicationContext.xml" ; ApplicationContext ctx = new ClassPathXmlApplicationContext(config); BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService" ); service.buy(1001 , 10 ); } }
使用 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 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤 1. 声明事务管理器对象 1 2 3 4 <bean id ="TransactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="myDataSource" /> </bean >
2. 开启事务注解驱动,告诉spring框架,使用注解的方式管理事务 spring 使用 aop 机制,创建@Transactional 所在的类代理对象,给方法增加事务功能
spring 给业务方法增加事务:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop环绕通知
1 2 3 4 <tx:annotation-driven transaction-manager ="TransactionManager" />
3. 在你的方法上面加入@Trancational 1 2 3 4 5 6 7 8 9 @Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = { NullPointerException.class, NotEnoughException.class } )
一般直接写@Transactional
rollbackFor: 表示发生指定的异常一定回滚
处理逻辑是:
1)Spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中,如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚
2)如果你抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,如果是一定回滚
使用 AspectJ 的 AOP 配置管理事务 适合大型项目,有很多类,方法,需要大量配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务。这种方式业务方法和事务配置完全分离
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类 较多,配置文件会变得非常臃肿。
实现步骤:都是在xml配置文件中实现
Step 1: maven 依赖 pom.xml 要使用的是aspectj框架,需要加入依赖
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.2.5.RELEASE</version > </dependency >
Step2: 在容器中添加事务管理器 声明事务管理器对象
1 2 3 <bean id ="TransactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="myDataSource" /> </bean >
Step3: 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】) 配置事务通知,为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <tx:advice id ="myAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="buy" propagation ="REQUIRED" isolation ="DEFAULT" rollback-for ="java.lang.NullPointerException, com.bjpowernode.execp.NotEnoughException" /> <tx:method name ="add*" propagation ="REQUIRES_NEW" /> <tx:method name ="modify*" /> <tx:method name ="remove*" /> <tx:method name ="*" propagation ="SUPPORTS" read-only ="true" /> </tx:attributes > </tx:advice >
Step4:配置aop: 指定哪些类要创建代理 配置增强器,指定将配置好的事务通知,织入给谁。
1 2 3 4 5 6 7 8 9 10 11 12 13 <aop:config > <aop:pointcut id ="servicePt" expression ="execution(* *..service..*.*(..))" /> <aop:advisor advice-ref ="myAdvice" pointcut-ref ="servicePt" /> </aop:config >
Step5:修改测试类 1 2 3 4 5 6 7 8 9 @Test public void test01 () { String config = "applicationContext.xml" ; ApplicationContext ctx = new ClassPathXmlApplicationContext(config); BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService" ); System.out.println("service是代理:" + service.getClass().getName()); service.buy(1001 , 1000 ); }
Spring 与 Web 使用 Spring 的监听器 ContextLoaderListener 对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机, 放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性。
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的 全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就 保证了 Spring 容器的全局性。
上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中: spring-web-5.2.5.RELEASE
Step1: maven依赖pom.xml 1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-web</artifactId > <version > 5.2.5.RELEASE</version > </dependency >
Step2:注册监听器 ContextLoaderListener 若要在 ServletContext 初始化时创建Spring 容器 , 就需要使用监听器接口ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。
1 2 3 <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener >
Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。
Step3:指定 Spring 配置文件的位置 ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认 的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。
1 2 3 4 5 6 7 8 9 10 <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring.xml</param-value > </context-param >
Step4:获取 Spring 容器对象 在 Servlet 中获取容器对象的常用方式有两种:
(1) 直接从 ServletContext 中获取 从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。
1 2 3 4 5 6 7 WebApplicationContext ctx = null ; String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; Object attr = getServletContext().getAttribute(key); if (attr != null ) { ctx = (WebApplicationContext) attr; } StudentService service = (StudentService)ctx.getBean("studentService" );
(2) 通过 WebApplicationContextUtils 获取 工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring 容器对象:getRequiredWebApplicationContext(ServletContext sc)
1 2 3 WebApplicationContext ctx = null ; ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); StudentService service = (StudentService)ctx.getBean("studentService" );
完整servlet代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class RegisterServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String strId = request.getParameter("id" ); String strName = request.getParameter("name" ); String strEmail = request.getParameter("email" ); String strAge = request.getParameter("age" ); WebApplicationContext ctx = null ; ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); System.out.println("容器对象的信息 ========== " + ctx); StudentService service = (StudentService)ctx.getBean("studentService" ); Student student = new Student(); student.setId(Integer.parseInt(strId)); student.setName(strName); student.setEmail(strEmail); student.setAge(Integer.valueOf(strAge)); service.addStudent(student); request.getRequestDispatcher("/result.jsp" ).forward(request, response); } }