Spring官网
Spring 是一款目前主流的 Java EE 轻量级开源框架 ,是 Java 世界最为成功的框架之一。Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。
Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。
Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。
这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。
项目名称 | 描述 |
---|---|
Spring Data | Spring 提供的数据访问模块,对 JDBC 和 ORM 提供了很好的支持。通过它,开发人员可以使用一种相对统一的方式,来访问位于不同类型数据库中的数据。 |
Spring Batch | 一款专门针对企业级系统中的日常批处理任务的轻量级框架,能够帮助开发人员方便的开发出健壮、高效的批处理应用程序。 |
Spring Security | 前身为 Acegi,是 Spring 中较成熟的子模块之一。它是一款可以定制化的身份验证和访问控制框架。 |
Spring Mobile | 是对 Spring MVC 的扩展,用来简化移动端 Web 应用的开发。 |
Spring Boot | 是 Spring 团队提供的全新框架,它为 Spring 以及第三方库一些开箱即用的配置,可以简化 Spring 应用的搭建及开发过程。 |
Spring Cloud | 一款基于 Spring Boot 实现的微服务框架。它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。 |
狭义的Spring特指Spring Framework,通过我们将它 称为Spring框架。Spring框架是一个分层的,面向切面的Java应用程序的一站轻量级框架。
Spring有以下几个特点:
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Portlet 组件,具体介绍如下。
Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。
在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:
Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。
IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。
IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。
实现类 | 描述 | 示例代码 |
---|---|---|
ClassPathXmlApplicationContext | 加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 | ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation); |
FileSystemXmlApplicationContext | 加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 | ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation); |
这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”。
IOC是利用Spring容器创建对象,而DI是对用IOC创建的对象中的对象进行赋值。
依赖注入本质上是 Spring Bean 属性注入的一种,只不过这个属性是一个对象属性而已。
Spring 主要通过以下 3种方式实现属性注入:
构造器注入
@RestController
@RequestMapping("/constructor")
public class ConstructorController {
private final DiService diService;
private final String result;
public ConstructorController(DiService diService) {
this.diService = diService;
this.result = diService.test001("constructor");
}
@GetMapping("/test001")
public String test001() {
return diService.test001(this.result);
}
}
//只需要在 标签下的 元素中,再次使用 元素对内部 Bean 进行定义
……
setter 注入(又称设值注入)
@RestController
@RequestMapping("/setter")
public class SetterController {
private DiService diService;
@Autowired
public void setDiService(DiService diService) {
this.diService = diService;
}
@GetMapping("/test001")
public String test001() {
return diService.test001("setter");
}
}
//set方法注入Bean
……
注解注入
@RestController
@RequestMapping("/annotation")
public class AnnotationController {
@Autowired
private DiService diService;
@GetMapping("/test001")
public String test001() {
return diService.test001("annotation");
}
/*
首先配置文件需要先导入相应的aop和context约束以及配置注解的支持
还需要导入 扫描指定包下的组件,导入这个配置之后,就已经包含导入 这个配置了
*/
在以前的开发中,我们主要使用四种注解注册bean,每种注解可以任意使用,只是语义上有所差异:
@Resource :java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Autowired :spring注解,默认是以byType的方式去匹配类型相同的bean,可以结合@Qualifier 注解根据byName方式匹配。
类:
//1、数组类型属性
private String[] courses;
//2、list集合类型属性
private List list;
//3、map集合类型属性
private Map maps;
//4、set集合类型属性
private Set sets;
//学生所学的多门课程
private List courseList;
public void setCourseList(List courseList) {
this.courseList = courseList;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List list) {
this.list = list;
}
public void setMaps(Map maps) {
this.maps = maps;
}
public void setSets(Set sets) {
this.sets = sets;
}
public void test(){
System.out.println(Arrays.toString(courses));
System.out.println(list);
System.out.println(maps);
System.out.println(sets);
System.out.println(courseList);
}
Bean.xml
Java
C++
Python
小明
小红
北京
上海
Text类
@Test
public void testCollection1(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu",xxx.class);
stu.test();
}
输出结果
[Java, C++, Python]
[小明, 小红]
{Java=java, C++=c++}
[北京, 上海]
[Course{cname='Spring5框架'}, Course{cname='MyBatis框架'}]
Process finished with exit code 0
由 Spring IoC 容器管理的对象称为 Bean。
Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。
属性名称 | 描述 |
---|---|
id | Bean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。 |
name | 该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。 |
class | 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。 |
scope | 表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。 |
constructor-arg | 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。 |
property | 元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。 |
ref | 和 等元素的子元索,用于指定对某个 Bean 实例的引用,即 元素中的 id 或 name 属性。 |
value | 和 等元素的子元素,用于直接指定一个常量值。 |
list | 用于封装 List 或数组类型的属性注入。 |
set | 用于封装 Set 类型的属性注入。 |
map | 用于封装 Map 类型的属性注入。 |
entry | 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。 |
init-method | 容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法 |
destroy-method | 容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效 |
lazy-init | 懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效 |
我们在通过构造函数或 setter 方法进行属性注入时,通常是在 元素中嵌套 和 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。
Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。
p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。
首先我们需要在配置文件的 元素中导入以下 XML 约束。
xmlns:p="http://www.springframework.org/schema/p"
在导入 XML 约束后,我们就能通过以下形式实现属性注入。
使用 p 命名空间注入依赖时,必须注意以下 3 点:
默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。
我们可以在 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。
Spring 5 共提供了 6 种 scope 作用域,如下表。
作用范围 | 描述 |
---|---|
singleton | 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例 |
prototype | 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。 |
request | 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。 |
session | 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。 |
application | 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。 |
websocket | websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。 |
public class Bean {
private String str;
public void setStr(String str) {
this.str = str;
}
}
Bean.xml
测试:
public static void main(String[] args) {
//获取 ApplicationContext 容器
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
//创建两个实例,如果输出相同,则表示为一个对象
Bean Bean = context.getBean("Bean", Bean.class);
Bean Bean2 = context.getBean("Bean", Bean.class);
System.out.println(Bean);
System.out.println(Bean2);
}
运行类中的 main() 方法,控制台输出如下。
net.biancheng.c.SingletonBean@65e2dbf3
net.biancheng.c.SingletonBean@65e2dbf3
从控制台的输出可以看出,两次获得的 Bean 实例的地址完全一样,这说明 IoC 容器只创建了一个 singletonBean 实例。由于 singleton 是 Spring IoC 容器的默认作用域,因此即使省略 scope 属性,控制台的输出结果也一样的。
Bean.xml:
执行main 函数,控制台输出如下。
net.biancheng.c.PrototypeBean@61f8bee4
net.biancheng.c.PrototypeBean@7b49cea0
从运行结果可以看出,两次输出的内容并不相同,这说明在 prototype 作用域下,Spring 容器创建了两个不同的 prototypeBean 实例。
Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:
我们可以在 Spring Bean 生命周期的某个特定时刻,指定一些生命周期回调方法完成一些自定义的操作,对 Bean 的生命周期进行管理。
Bean 的生命周期回调方法主要有两种:
我们可以通过以下 3 种方式自定义 Bean 的生命周期回调方法:
通过接口实现
我们可以在 Spring Bean 的 Java 类中,通过实现 InitializingBean 和 DisposableBean 接口,指定 Bean 的生命周期回调方法。
回调方式 | 接口 | 方法 | 说明 |
---|---|---|---|
初始化回调 | InitializingBean | afterPropertiesSet() | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
销毁回调 | DisposableBean | destroy() | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |
例:类:
public class XiaoHu implements InitializingBean, DisposableBean {
public String name;
public int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "XiaoHu{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/**
* 调用销毁
*
* @throws Exception
*/
@Override
public void destroy() throws Exception {
System.out.println("调用destroy()方法");
}
/**
* 调用初始化回调函数
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("调用afterPropertiesSet()方法");
}
}
Bean.xml:
测试:
public class MainApp {
public static void main(String[] args) {
//获取 ClassPathXmlApplicationContext 容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
XiaoHu xiaohu = context.getBean("xiaohu", XiaoHu.class);
System.out.println(xiaohu);
//手动销毁 Bean
context.close();
}
}
结果:
调用afterPropertiesSet()方法
XiaoHu{name='小胡', age=22}
调用destroy()方法
进程已结束,退出代码为 0
通过 XML 配置实现
我们还可以在 Spring 的 XML 配置中,通过 元素中的 init-method 和 destory-method 属性,指定 Bean 的生命周期回调方法。
XML 配置属性 | 描述 |
---|---|
init-method | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
destory-method | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |
例:类:
public class XiaoHu {
public String name;
public int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "XiaoHu{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void destroy() throws Exception {
System.out.println("调用destroy()方法");
}
public void init() throws Exception {
System.out.println("调用afterPropertiesSet()方法");
}
}
Bean.xml
测试/结果:同上
使用注解实现
我们还可以通过 JSR-250 的 @PostConstruct 和 @PreDestroy 注解,指定 Bean 的生命周期回调方法。
注解 | 描述 |
---|---|
@PostConstruct | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
@PreDestroy | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |
例:类:
public class XiaoHu {
public String name;
public int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "XiaoHu{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@PreDestroy
public void destroy() throws Exception {
System.out.println("调用destroy()方法");
}
@PostConstruct
public void init() throws Exception {
System.out.println("调用afterPropertiesSet()方法");
}
}
Bean.xml
如果一个 Bean 中有多种生命周期回调方法时,优先级顺序为:注解 > 接口 > XML 配置。
Bean的装配方式可以理解为依赖关系的注入,Bean的装配方式即Bean依赖注入的方式。Spring容器支持多种形式的Bean的装配,如基于XML的装配,基于注解(Annotation),自动装配。
在Spring实例化Bean的过程中,Spring会首先调用Bean的默认构造方法来实例化Bean对象,然后通过反射的方式调用setter方法注入属性值。因此,设值注入必须满足以下两点:
使用设值注入时,在Spring配置文件中,需要使用元素的子元素
再使用有参构造时,需要使用元素的子元素<constructor-arg>来为每个属性注入值
例:类:
public class XiaoHu {
public String name;
public int age;
/**
* 使用构造注入
* 提供相应的有参构造方法
* @param name
* @param age
*/
public XiaoHu(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 使用设值注入
* 提供相应的空参构造方法
* 为所有属性设置相应的setter方法
*/
public XiaoHu() { }
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {return "XiaoHu{" + "name='" + name + '\'' + ", age=" + age +
'}';
}
}
Bean.xml
测试:
public class MainApp {
public static void main(String[] args) {
//获取 ClassPathXmlApplicationContext 容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
XiaoHu xiaohu1 = context.getBean("xiaohu1", XiaoHu.class);
XiaoHu xiaohu2 = context.getBean("xiaohu2", XiaoHu.class);
System.out.println("使用有参构造"+xiaohu1);
System.out.println("使用无参构造"+xiaohu2);
}
}
结果:
使用有参构造XiaoHu{name='小胡', age=22}
使用无参构造XiaoHu{name='小胡', age=22}
进程已结束,退出代码为 0
Spring 通过注解实现自动装配的步骤如下:
引入依赖 使用注解的第一步,就是要在项目中引入以下 Jar 包。
开启组件扫描
因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。
使用注解定义 Bean
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
注解 | 说明 |
---|---|
@Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 |
@Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Controller | 该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
依赖注入
我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。
注解 | 说明 |
---|---|
@Autowired | 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 @Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用 |
@Resource | 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。 Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配;如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。 |
@Qualifier | 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。 |
例:类
1.创建Dao层,在其下创建UserDao类的接口
public interface UserDao {
public void save();
}
2.在Dao层下创建UserDaoimpl类实现Use人Dao接口
@Repository("userDao")
public class UserDaoimpl implements UserDao {
@Override
public void save() {
System.out.println("userdao......save....");
}
}
3.创建Service层,在其下创建UserServiceimpl类的接口
public interface UserService {
public void save();
}
4.在Service层下创建UserServiceimpl来实现User Service的接口
@Service("userService")
public class UserServiceimpl implements UserService {
@Resource(name="userDao")
private UserDao userDao;
@Override
public void save() {
this.userDao.save();
System.out.println("userService...save.....");
}
}
5.创建Controller层,
@Controller("userController")
public class UserController {
@Resource(name="userService")
private UserService userService;
public void save(){
this.userService.save();
System.out.println("userController.....save....");
}
}
6.创建Bean.xml
7.创建测试类
public class MainApp {
public static void main(String[] args) {
//获取 ClassPathXmlApplicationContext 容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
UserController userController=context.getBean("userController",UserController.class);
userController.save();
}
}
8.测试结果
userdao......save....
userService...save.....
userController.....save....
进程已结束,退出代码为 0
Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。
属性值 | 说明 |
---|---|
byName | 按名称自动装配。 Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。 |
byType | 按类型自动装配。 Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。 |
constructor | 与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。 其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。 |
default | 表示默认采用上一级元素 设置的自动装配规则(default-autowire)进行装配。 |
no | 默认值,表示不使用自动装配,Bean 的依赖关系必须通过 和 元素的 ref 属性来定义。 |
例:
1,2,3同上–>注解装配的步骤
4.更改Service层下的UserServiceimpl类为:(加入setter方法)
@Service("userService")
public class UserServiceimpl implements UserService {
//@Resource(name="userDao")
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
this.userDao.save();
System.out.println("userService...save.....");
}
}
5.更改Controller层下UserControllerller为:(加入setter方法)
@Controller("userController")
public class UserController {
//@Resource(name="userService")
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void save(){
this.userService.save();
System.out.println("userController.....save....");
}
}
6.更改Bean.xml
简介:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要功能:
日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图:
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 的接口也一样能够做到,我想这个观点是值得商榷的。AOP和定义良好的 OOP 的接口可以说都是用来解决并且实现需求中的横切问题的方法。但是对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法避免的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会这样,你只需要修改相应的 Aspect,再重新编织(weave)即可。 当然,AOP 也绝对不会代替 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,以此之长,补彼之短。
与大多数的技术一样,AOP 已经形成一套属于自己的概念和术语。
名称 | 说明 |
---|---|
Joinpoint(连接点) | AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象,通常也被称为被通知(advised)对象。 |
Weaving(织入) | 指把增强代码应用到目标对象上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切面是切入点(Pointcut)和通知(Advice)的结合。 |
Advice 直译为通知,也有人将其翻译为“增强处理”,共有 5 种类型,如下表所示。
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。
在 Spring 框架中使用 AOP 主要有以下优势。
目前最流行的 AOP 实现(框架)主要有两个,分别为 Spring AOP 和 AspectJ。
AOP 框架 | 说明 |
---|---|
Spring AOP | 是一款基于 AOP 编程的框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。 Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Spring AOP 支持 2 种代理方式,分别是基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。 |
AspectJ | 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。 AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。 |
AOP中的代理说是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用。Spring 中的AOP有两种,JDK动态代理,CGLIB代理
JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。
例:
1.创建UserDao接口:
public interface UserDao {
public void addUser();
public void deleteUser();
}
2.创建UserDao接口的实现类:
@Repository("userDao")
public class UserDaoimpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
3.创建切面类:
public class MyAspect {
public void Check_Permissions(){
System.out.println("模拟检查权限。。。。");
}
public void log(){
System.out.println("模拟记录日志。。。");
}
}
4.创建代理类:–>实现InvocationHandler接口,并编写代理方法
public class JdkProxy implements InvocationHandler {
//生命目标类接口
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao){
this.userDao=userDao;
//类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
//被代理类实现的所有接口
Class>[] clazz = userDao.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader,clazz,this);
}
/**
* 所有的动态代理都会调用invoke()方法
* @param proxy 被代理后的对象
* @param method 见还要被哦执行的方法(反射)
* @param args 执行方法是需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//生命切面
MyAspect myAspect = new MyAspect();
//前增强
myAspect.Check_Permissions();
//在目标类上调用方法
Object obj = method.invoke(userDao, args);
//后增强
myAspect.log();
return obj;
}
}
5.创建测试类方法
public class JdkTest {
public static void main(String[] args) {
//创建代理对象
JdkProxy jdkProxy = new JdkProxy();
//创建目标对象
UserDaoimpl userDao = new UserDaoimpl();
UserDao userDao1 = (UserDao)jdkProxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
6.测试结果:
模拟检查权限。。。。
添加用户
模拟记录日志。。。
模拟检查权限。。。。
删除用户
模拟记录日志。。。
进程已结束,退出代码为 0
通过前面的学习可知,JDK的动态代理用起来非常简单,但它是有局限性的,使用动态代理的对象必须实现一个或多个接口(如UserDaoImpl实现了UserDao接口)。 如果想代理没有实现接口的类,那么可以使用CGLIB代理.
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。
例:
1,2同上
3.创建代理类CglibProxy,–>该类需要实现MethodInterceptor接口,并在接口中实现interceptor方法
import com.Aspect.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//代理类
public class CglibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object target){
//创建一个动态类对象
Enhancer enhancer=new Enhancer();
//确定要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//添加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/**
* proxy CGlib根据父类生成的代理对象
* method 拦截的方法
* args 拦截方法的参数组
* methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//创建切面类对象
MyAspect myAspect=new MyAspect();
//前增强
myAspect.Check_Permissions();
//目标方法执行
Object obj=methodProxy.invokeSuper(proxy,args);
//后增强
myAspect.log();
return obj;
}
}
4.创建测试类CglibTest
public class CglibTest {
public static void main(String[] args){
//创建代理类对象
CglibProxy cglibProxy=new CglibProxy();
//创建目标对象
UserDao userDao=new UserDao();
//获取增强后的目标对象
UserDao userDao1=(UserDao) cglibProxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
5,测试结果
模拟检查权限。。。。
添加用户
模拟记录日志。。。
模拟检查权限。。。。
删除用户
模拟记录日志。。。
进程已结束,退出代码为 0
Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。
pring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口,如下表。
通知类型 | 接口 | 描述 |
---|---|---|
前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强。 |
后置通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行后实施增强。 |
后置返回通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行完成,并返回一个返回值后实施增强。 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强。 |
异常通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强。 |
引入通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性。 |
属性 | 描述 |
---|---|
target | 需要代理的目标对象(Bean) |
proxyInterfaces | 代理需要实现的接口,如果需要实现多个接口,可以通过 元素进行赋值。 |
proxyTargetClass | 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理 |
interceptorNames | 拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。 |
singleton | 返回的代理对象是否为单例模式,默认值为 true。 |
optimize | 是否对创建的代理进行优化(只适用于CGLIB)。 |
当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。
例:
下面我们就通过一个简单的实例演示下一般切面的 AOP 开发流程。
2.创建一个UserDao的接口
public interface UserDao {
public void add();
public void delete();
public void modify();
public void get();
}
3.创建 UserDao 的实现类 UserDaoImpl,
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("正在执行 UserDao 的 add() 方法……");
}
@Override
public void delete() {
System.out.println("正在执行 UserDao 的 delete() 方法……");
}
@Override
public void modify() {
System.out.println("正在执行 UserDao 的 modify() 方法……");
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的 get() 方法……");
}
}
4.UserDaoBeforeAdvice 的前置增强类—>实现MethodBeforeAdvice接口,并实现invoke方法
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("正在执行前置增强操作…………");
}
}
5.Beans.xml
6.创建一个名为 MainApp 的类
public class MainApp {
public static void main(String[] args) {
//获取 ApplicationContext 容器
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
//获取代理对象
UserDao userDao = context.getBean("userDaoProxy", UserDao.class);
//调用 UserDao 中的各个方法
userDao.add();
userDao.delete();
userDao.get();
userDao.modify();
}
}
7.测试结果
正在执行前置增强操作…………
正在执行 UserDao 的 add() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 delete() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 get() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 modify() 方法……
Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。
AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。
使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。
Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 aop:config 元素。
在 Spring 配置文件中,使用 aop:aspect 元素定义切面。该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 aop:aspect 之前需要先定义一个普通的 Spring Bean。
...
其中,id 用来定义该切面的唯一标识名称,ref 用于引用普通的 Spring Bean。
aop:pointcut 用来定义一个切入点,用来表示对哪个类中的那个方法进行增强。它既可以在 aop:pointcut 元素中使用,也可以在 aop:aspect 元素下使用。
其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
execution 的语法格式格式为:
execution([权限修饰符] [返回值类型] [类的完全限定名] 方法名称
其中:
*
表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。*
代表所有方法,set*
代表以 set 开头的所有方法。(..)
代表所有参数;(*)
代表只有一个参数,参数类型为任意类型;(*,String)
代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。举例 1:对 net.biancheng.c 包下 UserDao 类中的 add() 方法进行增强,配置如下。
execution(* net.biancheng.c.UserDao.add(..))
举例 2:对 net.biancheng.c 包下 UserDao 类中的所有方法进行增强,配置如下。
execution(* net.biancheng.c.UserDao.*(..))
举例 3:对 net.biancheng.c 包下所有类中的所有方法进行增强,配置如下。
execution(* net.biancheng.c.*.*(..))
AspectJ 支持 5 种类型的 advice,如下。
....
1.新建一个Java 项目,并将以下依赖 Jar 包导入到该项目中。
2.创建一个名为 OrderDao 的接口,代码如下。
public interface OrderDao {
public void add();
public void delete();
public Integer modify();
public void get();
}
3.创建 OrderDao 的实现类 OrderDaoImpl
public class OrderDaoImpl implements OrderDao {
@Override
public void add() {
System.out.println("正在执行 OrderDao 中的 add() 方法");
}
@Override
public void delete() {
System.out.println("正在执行 OrderDao 中的 delete() 方法");
}
@Override
public int modify() {
System.out.println("正在执行 OrderDao 中的 modify() 方法");
return 1;
}
@Override
public void get() {
//异常
int a = 10 / 0;
System.out.println("正在执行 OrderDao 中的 get() 方法");
}
}
4.创建一个名为 MyOrderAspect 的切面类
public class MyOrderAspect {
public void before() {
System.out.println("前置增强……");
}
public void after() {
System.out.println("最终增强……");
}
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕增强---前……");
proceedingJoinPoint.proceed();
System.out.println("环绕增强---后……");
}
public void afterThrow(Throwable exception) {
System.out.println("异常增强…… 异常信息为:" + exception.getMessage());
}
public void afterReturning(Object returnValue) {
System.out.println("后置返回增强…… 方法返回值为:" + returnValue);
}
}
5.配置文件 Beans2.xml
6.创建一个名 MainApp 的测试类
public class MainApp {
public static void main(String[] args) {
ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans2.xml");
OrderDao orderDao = context2.getBean("orderDao", OrderDao.class);
orderDao.add();
orderDao.delete();
orderDao.modify();
orderDao.get();
}
}
7.测试结果
前置增强……
环绕增强---前……
正在执行 OrderDao 中的 add() 方法
环绕增强---后……
最终增强……
正在执行 OrderDao 中的 delete() 方法
最终增强……
正在执行 OrderDao 中的 modify() 方法
最终增强……
后置返回增强…… 方法返回值为:1
最终增强……
异常增强…… 异常信息为:/ by zero
其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。
为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面。 |
@Pointcut | 用于定义一个切入点。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。
@Configuration
@ComponentScan(basePackages = "net.biancheng.c") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}
在 Spring 的 XML 配置文件中,添加以下内容启用 @AspectJ 注解支持。
我们可以通过 @Aspect 注解将一个 Bean 定义为切面。
在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。
我们可以在 XML 配置中通过一些配置将这个类定义为一个 Bean,如下。
...
在定义完 Bean 后,我们只需要在Bean 对应的 Java 类中使用一个 @Aspect 注解,将这个 Bean 定义为一个切面,代码如下。
@Aspect //定义为切面
public class MyAspect {
}
在 AspectJ 中,我们可以使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void,示例代码如下。
// 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
@Pointcut("execution(*net.biancheng..*.*(..))")private void myPointCut() {}
@Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。
AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。
注解 | 说明 |
---|---|
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
1.新建一个Java 项目,并将以下依赖 Jar 包导入到该项目中。
2.创建一个UserDao接口
public interface UserDao {
public void add();
public void delete();
public int modify();
public void get();
}
3.创建一个UserDaoImpl类实现UserDao接口
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("正在执行 UserDao 的 add 方法");
}
@Override
public void delete() {
System.out.println("正在执行 UserDao 的 delete 方法");
}
@Override
public int modify() {
System.out.println("正在执行 UserDao 的 modify 方法");
return 1;
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的 get 方法");
}
}
4.创建一个全注解的配置类
@Configuration//声明为配置类
@ComponentScan(basePackages = "net.biancheng.c") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}
5.创建一个名为 MyAspect 的切面类
@Component // 定义成 Bean
@Aspect //定义为切面
public class MyAspect {
@Before("execution(* net.biancheng.c.dao.UserDao.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置增强……" + joinPoint);
}
@After("execution(* net.biancheng.c.dao.UserDao.get(..))")
public void after(JoinPoint joinPoint) {
System.out.println("最终增强……" + joinPoint);
}
/**
* 将 net.biancheng.c.dao包下的 UserDao 类中的 get() 方法 定义为一个切点
*/
@Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.get(..))")
public void pointCut1() {
}
/**
* 将 net.biancheng.c.dao包下的 UserDao 类中的 delete() 方法 定义为一个切点
*/
@Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.delete(..))")
public void pointCut2() {
}
//使用切入点引用
@Around("MyAspect.pointCut2()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕增强……1");
proceedingJoinPoint.proceed();
System.out.println("环绕增强……2");
}
//使用切入点表达式
@AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.modify(..))", returning = "returnValue")
public void afterReturning(Object returnValue) {
System.out.println("后置返回增强……,方法返回值为:" + returnValue);
}
}
6.创建一个MainApp的测试类
public class MainApp {
public static void main(String[] args) {
ApplicationContext context2 = new AnnotationConfigApplicationContext(AppConfig.class);
UserDao userDao = context2.getBean("userDao", UserDao.class);
userDao.add();
userDao.modify();
userDao.delete();
userDao.get();
}
}
7.测试结果
前置增强……execution(void net.biancheng.c.dao.UserDao.add())
正在执行 UserDao 的 add 方法
正在执行 UserDao 的 modify 方法
后置返回增强……,方法返回值为:1
环绕增强……1
正在执行 UserDao 的 delete 方法
环绕增强……2
正在执行 UserDao 的 get 方法
最终增强……execution(void net.biancheng.c.dao.UserDao.get())
JdbcTemplate类的继承关系十分简单,他继承自抽象类JdbcAccessor,同时实现了JdbcOperations接口。
JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。
JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。
方法 | 说明 |
---|---|
public int update(String sql) | 用于执行新增、更新、删除等语句;sql:需要执行的 SQL 语句;args 表示需要传入到 SQL 语句中的参数。 |
public int update(String sql,Object… args) | |
public void execute(String sql) | 可以执行任意 SQL,一般用于执行 DDL 语句; sql:需要执行的 SQL 语句;action 表示执行完 SQL 语句后,要调用的函数。 |
public T execute(String sql, PreparedStatementCallback action) | |
public List query(String sql, RowMapper rowMapper, @Nullable Object… args) | |
用于执行查询语句;sql:需要执行的 SQL 语句;rowMapper:用于确定返回的集合(List)的类型;args:表示需要传入到 SQL 语句的参数。 | |
public T queryForObject(String sql, RowMapper rowMapper, @Nullable Object… args) | |
public int[] batchUpdate(String sql, List | 用于批量执行新增、更新、删除等语句; sql:需要执行的 SQL 语句;argTypes:需要注入的 SQL 参数的 JDBC 类型;batchArgs:表示需要传入到 SQL 语句的参数。 |
1.创建一个名为 my-spring-jdbc-demo 的项目,并在将以下依赖导入到工程中。
2.创建一个User的实体类
public class User {
private Integer userId;
private String userName;
private String status;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", status='" + status + '\'' +
'}';
}
}
3.创建一个UserDao接口
public interface UserDao {
/**
* 新增一条用户
*
* @param user
* @return
*/
int addUer(User user);
/**
* 更新指定的用户信息
*
* @param user
* @return
*/
int update(User user);
/**
* 删除指定的用户信息
*
* @param user
* @return
*/
int delete(User user);
/**
* 统计用户个数
*
* @param user
* @return
*/
int count(User user);
/**
* 查询用户列表
*
* @param user
* @return
*/
List getList(User user);
/**
* 查询单个用户信息
*
* @param user
* @return
*/
User getUser(User user);
/**
* 批量增加用户
*
* @param batchArgs
*/
void batchAddUser(List
4.创建一个UserDaoimpl实现UserDao
@Repository
public class UserDaoImpl implements UserDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public int addUer(User user) {
String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
int update = jdbcTemplate.update(sql, user.getUserName(), user.getStatus());
return update;
}
@Override
public int update(User user) {
String sql = "UPDATE `user` SET status=? WHERE user_name=?;";
return jdbcTemplate.update(sql, user.getStatus(), user.getUserName());
}
@Override
public int delete(User user) {
String sql = "DELETE FROM `user` where user_name=?;";
return jdbcTemplate.update(sql, user.getUserName());
}
@Override
public int count(User user) {
String sql = "SELECT COUNT(*) FROM `user` where `status`=?;";
return jdbcTemplate.queryForObject(sql, Integer.class, user.getStatus());
}
@Override
public List getList(User user) {
String sql = "SELECT * FROM `user` where `status`=?;";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), user.getStatus());
}
@Override
public User getUser(User user) {
String sql = "SELECT * FROM `user` where `user_id`=?;";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(User.class), user.getUserId());
}
@Override
public void batchAddUser(List
5.创建一个UserService层接口
public interface UserService {
/**
* 新增用户数据
*
* @param user
* @return
*/
public int addUser(User user);
/**
* 更新用户数据
*
* @param user
* @return
*/
public int updateUser(User user);
/**
* 删除用户数据
*
* @param user
* @return
*/
public int deleteUser(User user);
/**
* 统计用户数量
*
* @param user
* @return
*/
public int countUser(User user);
/**
* 查询用户数据
*
* @param user
* @return
*/
public List getUserList(User user);
/**
* 查询单个用户信息
*
* @param user
* @return
*/
public User getUser(User user);
/**
* 批量添加用户
*
* @param batchArgs
*/
public void batchAddUser(List
6.创建一个UserServiceimple类实现UserService接口
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public int addUser(User user) {
return userDao.addUer(user);
}
@Override
public int updateUser(User user) {
return userDao.update(user);
}
@Override
public int deleteUser(User user) {
return userDao.delete(user);
}
@Override
public int countUser(User user) {
return userDao.count(user);
}
@Override
public List getUserList(User user) {
return userDao.getList(user);
}
@Override
public User getUser(User user) {
return userDao.getUser(user);
}
@Override
public void batchAddUser(List
7.创建一个MainApp的类
public class MainApp {
public static void main(String[] args) {
ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
UserService userService = context2.getBean("userService", UserService.class);
User user = new User();
user.setUserName("小张");
user.setStatus("离线线");
//新增一个用户
int i = userService.addUser(user);
System.out.println("新增用户成功!");
User user1 = new User();
user1.setUserName("小张");
user1.setStatus("在线");
int u = userService.updateUser(user1);
System.out.println("修改用户成功");
List
注:前提先创建一个数据库
事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。
事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。
事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有的操作都执行成功,那自然万事大吉。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。
Spring 支持以下 2 种事务管理方式。
事务管理方式 | 说明 |
---|---|
编程式事务管理 | 编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。 |
声明式事务管理 | Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。 |
选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。
Spring 的声明式事务管理主要通过以下 2 种方式实现:
Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。
在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
该接口中各方法说明如下:
名称 | 说明 |
---|---|
TransactionStatus getTransaction(TransactionDefinition definition) | 用于获取事务的状态信息 |
void commit(TransactionStatus status) | 用于提交事务 |
void rollback(TransactionStatus status) | 用于回滚事务 |
Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。
实现类 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用 Spring JDBC 或 iBatis 进行持久化数据时使用。 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 使用 Hibernate 3.0 及以上版本进行持久化数据时使用。 |
org.springframework.orm.jpa.JpaTransactionManager | 使用 JPA 进行持久化时使用。 |
org.springframework.jdo.JdoTransactionManager | 当持久化机制是 Jdo 时使用。 |
org.springframework.transaction.jta.JtaTransactionManager | 使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。 |
这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。
Spring 将 XML 配置中的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。
TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();
}
该接口中方法说明如下。
方法 | 说明 |
---|---|
String getName() | 获取事务的名称 |
int getIsolationLevel() | 获取事务的隔离级别 |
int getPropagationBehavior() | 获取事务的传播行为 |
int getTimeout() | 获取事务的超时时间 |
boolean isReadOnly() | 获取事务是否只读 |
事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。
在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。
在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。
Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。
方法 | 说明 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读 |
ISOLATION_READ_COMMITTED | Oracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读 |
ISOLATION_REPEATABLE_READ | MySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读 |
ISOLATION_SERIALIZABLE | 完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读 |
关于事务隔离级别、脏读、幻读、不可重复度等概念的详细介绍,请阅读《数据库事务隔离级别》一节。
事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。例如,事务方法 A 在调用事务方法 B 时,B 方法是继续在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。
事务方法指的是能让数据库表数据发生改变的方法,例如新增数据、删除数据、修改数据的方法。
Spring 提供了以下 7 种不同的事务传播行为。
名称 | 说明 |
---|---|
PROPAGATION_MANDATORY | 支持当前事务,如果不存在当前事务,则引发异常。 |
PROPAGATION_NESTED | 如果当前事务存在,则在嵌套事务中执行。 |
PROPAGATION_NEVER | 不支持当前事务,如果当前事务存在,则引发异常。 |
PROPAGATION_NOT_SUPPORTED | 不支持当前事务,始终以非事务方式执行。 |
PROPAGATION_REQUIRED | 默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。 |
PROPAGATION_REQUIRES_NEW | 创建新事务,如果已经存在事务则暂停当前事务。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在事务,则以非事务方式执行。 |
TransactionStatus 接口提供了一些简单的方法,来控制事务的执行、查询事务的状态,接口定义如下。
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
该接口中各方法说明如下。
名称 | 说明 |
---|---|
boolean hasSavepoint() | 获取是否存在保存点 |
boolean isCompleted() | 获取事务是否完成 |
boolean isNewTransaction() | 获取是否是新事务 |
boolean isRollbackOnly() | 获取事务是否回滚 |
void setRollbackOnly() | 设置事务回滚 |
Spring 声明式事务管理是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建(或加入)一个事务,在执行完目标方法后,根据执行情况提交或者回滚事务。
声明式事务最大的优点就是对业务代码的侵入性低,可以将业务代码和事务管理代码很好地进行解耦。
Spring 实现声明式事务管理主要有 2 种方式:
Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。
**注意:**由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。
在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。
当我们使用 tx:advice 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。
如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。
但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 tx:advice 元素中通过 transaction-manager 参数指定。
对于tx:advice 来说,事务属性是被定义在tx:attributes 中的,该元素可以包含一个或多个 tx:method 元素。
tx:method 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。
事务属性 | 说明 |
---|---|
propagation | 指定事务的传播行为。 |
isolation | 指定事务的隔离级别。 |
read-only | 指定是否为只读事务。 |
timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 |
rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 |
no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
tx:advice 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 tx:advice 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。
在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。
1.创建数据库文件
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint DEFAULT NULL COMMENT '用户id',
`total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
`used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', '1', '1000', '0', '1000');
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_id` varchar(200) NOT NULL,
`user_id` varchar(200) NOT NULL COMMENT '用户id',
`product_id` varchar(200) NOT NULL COMMENT '产品id',
`count` int DEFAULT NULL COMMENT '数量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金额',
`status` int DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `storage`;
CREATE TABLE `storage` (
`id` bigint NOT NULL AUTO_INCREMENT,
`product_id` bigint DEFAULT NULL COMMENT '产品id',
`total` int DEFAULT NULL COMMENT '总库存',
`used` int DEFAULT NULL COMMENT '已用库存',
`residue` int DEFAULT NULL COMMENT '剩余库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `storage` VALUES ('1', '1', '100', '0', '100');
2.创建一个Java项目,导入相应的jar包
3.创建一个名为 Order 的实体类
public class Order {
//自增 id
private Long id;
//订单 id
private String orderId;
//用户 id
private String userId;
//商品 id
private String productId;
//订单商品数量
private Integer count;
//订单金额
private BigDecimal money;
//订单状态
private Integer status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getMoney() {
return money;
}
public void setMoney(BigDecimal money) {
this.money = money;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
4.创建一个名为 Account 的实体类
public class Account {
//自增 id
private Long id;
//用户 id
private String userId;
//账户总金额
private BigDecimal total;
//已用账户金额
private BigDecimal used;
//剩余账户金额
private BigDecimal residue;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public BigDecimal getTotal() {
return total;
}
public void setTotal(BigDecimal total) {
this.total = total;
}
public BigDecimal getUsed() {
return used;
}
public void setUsed(BigDecimal used) {
this.used = used;
}
public BigDecimal getResidue() {
return residue;
}
public void setResidue(BigDecimal residue) {
this.residue = residue;
}
}
4.创建一个名为 Storage 的实体类
public class Storage {
//自增 id
private Long id;
//商品 id
private String productId;
//商品库存总数
private Integer total;
//已用商品数量
private Integer used;
//剩余商品数量
private Integer residue;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getUsed() {
return used;
}
public void setUsed(Integer used) {
this.used = used;
}
public Integer getResidue() {
return residue;
}
public void setResidue(Integer residue) {
this.residue = residue;
}
}
5.创建一个名为 OrderDao 的接口
public interface OrderDao {
/**
* 创建订单
* @param order
* @return
*/
int createOrder(Order order);
/**
* 修改订单状态
* 将订单状态从未完成(0)修改为已完成(1)
* @param orderId
* @param status
*/
void updateOrderStatus(String orderId, Integer status);
}
6.创建一个名为 AccountDao 的接口
public interface AccountDao {
/**
* 根据用户查询账户金额
* @param userId
* @return
*/
Account selectByUserId(String userId);
/**
* 扣减账户金额
* @param userId
* @param money
* @return
*/
int decrease(String userId, BigDecimal money);
}
7.创建一个名为 StorageDao 的接口
public interface StorageDao {
/**
* 查询商品的库存
* @param productId
* @return
*/
Storage selectByProductId(String productId);
/**
* 扣减商品库存
* @param record
* @return
*/
int decrease(Storage record);
}
8.创建 OrderDao 的实现类 OrderDaoImpl
@Repository
public class OrderDaoImpl implements OrderDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int createOrder(Order order) {
String sql = "insert into `order` (order_id,user_id, product_id, `count`, money, status) values (?,?,?,?,?,?)";
int update = jdbcTemplate.update(sql, order.getOrderId(), order.getUserId(), order.getProductId(), order.getCount(), order.getMoney(), order.getStatus());
return update;
}
@Override
public void updateOrderStatus(String orderId, Integer status) {
String sql = " update `order` set status = 1 where order_id = ? and status = ?;";
jdbcTemplate.update(sql, orderId, status);
}
}
9.创建 AccountDao 的实现类 AccountDaoImpl
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account selectByUserId(String userId) {
String sql = " select * from account where user_id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Account.class), userId);
}
@Override
public int decrease(String userId, BigDecimal money) {
String sql = "UPDATE account SET residue = residue - ?, used = used + ? WHERE user_id = ?;";
return jdbcTemplate.update(sql, money, money, userId);
}
}
10.创建 StorageDao 的实现类 StorageDaoImpl
@Repository
public class StorageDaoImpl implements StorageDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Storage selectByProductId(String productId) {
String sql = "select * from storage where product_id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Storage.class), productId);
}
@Override
public int decrease(Storage record) {
String sql = " update storage set used =? ,residue=? where product_id=?";
return jdbcTemplate.update(sql, record.getUsed(), record.getResidue(), record.getProductId());
}
}
11.创建一个名为 OrderService 的接口
public interface OrderService {
/**
* 创建订单
* @param order
* @return
*/
public void createOrder(Order order);
}
11.创建 OrderService 的实现类 OrderServiceImpl
@Service("orderService")
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private AccountDao accountDao;
@Autowired
private StorageDao storageDao;
@Override
public void createOrder(Order order) {
//自动生成订单 id
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String format = df.format(new Date());
String orderId = order.getUserId() + order.getProductId() + format;
System.out.println("自动生成的订单 id 为:" + orderId);
order.setOrderId(orderId);
System.out.println("开始创建订单数据,订单号为:" + orderId);
//创建订单数据
orderDao.createOrder(order);
System.out.println("订单数据创建完成,订单号为:" + orderId);
System.out.println("开始查询商品库存,商品 id 为:" + order.getProductId());
Storage storage = storageDao.selectByProductId(order.getProductId());
if (storage != null && storage.getResidue().intValue() >= order.getCount().intValue()) {
System.out.println("商品库存充足,正在扣减商品库存");
storage.setUsed(storage.getUsed() + order.getCount());
storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
int decrease = storageDao.decrease(storage);
System.out.println("商品库存扣减完成");
} else {
System.out.println("警告:商品库存不足,正在执行回滚操作!");
throw new RuntimeException("库存不足");
}
System.out.println("开始查询用户的账户金额");
Account account = accountDao.selectByUserId(order.getUserId());
if (account != null && account.getResidue().intValue() >= order.getMoney().intValue()) {
System.out.println("账户金额充足,正在扣减账户金额");
accountDao.decrease(order.getUserId(), order.getMoney());
System.out.println("账户金额扣减完成");
} else {
System.out.println("警告:账户余额不足,正在执行回滚操作!");
throw new RuntimeException("账户余额不足");
}
System.out.println("开始修改订单状态,未完成》》》》》已完成");
orderDao.updateOrderStatus(order.getOrderId(), 0);
System.out.println("修改订单状态完成!");
}
}
12.,创建一个 jdbc.properties,
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring-tx-db
jdbc.username=root
jdbc.password=root
15.创建一个名为 MainApp 的类
public class MainApp {
public static void main(String[] args) {
ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
OrderService orderService = context2.getBean("orderService", OrderService.class);
Order order = new Order();
//设置商品 id
order.setProductId("1");
//商品数量为 30
order.setCount(30);
//商品金额为 600
order.setMoney(new BigDecimal(600));
//设置用户 id
order.setUserId("1");
//订单状态为未完成
order.setStatus(0);
orderService.createOrder(order);
}
}
我们通过 tx:advice 元素极大的简化了 Spring 声明式事务所需的 XML 配置。但其实我们还可以通过另一种方式进行进一步的简化,那就是“使用注解实现事务管理”。
在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面我们就来介绍下,通过注解是如何实现声明式事务管理。
tx 命名空间提供了一个 tx:annotation-driven 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。
tx:annotation-driven 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。
与 tx:advice 元素一样,tx:annotation-driven 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。
通过 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。
@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。
@Transactional
public class XXX {
@Transactional
public void A(Order order) {
……
}
public void B(Order order) {
……
}
}
若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。
Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。
@Transactional 注解包含多个属性,其中常用属性如下表。
事务属性 | 说明 |
---|---|
propagation | 指定事务的传播行为。 |
isolation | 指定事务的隔离级别。 |
read-only | 指定是否为只读事务。 |
timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 |
rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 |
no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
对于一款软件而言,日志记录都是十分重要的。它不仅能够监控程序的运行情况,周期性的记录到文件中,还能够跟踪程序中代码的运行轨迹,向文件或控制台打印代码的调试信息。当程序出现错误时,日志记录可以帮助开发人员及时定位问题,因此对开发人员来说,日志记录更是尤为重要。
Spring 5 框架自带了通用的日志封装,但是我们依然可以整合其他的日志框架对日志进行记录,其中最广为人知的就是大名鼎鼎的 Log4j。
Log4j 是 Apache 提供的一款开源的强有力的 Java 日志记录工具。它可以通过配置文件灵活、细致地控制日志的生成过程,例如日志级别、日志的输出类型、日志的输出方式以及输出格式等。
Log4j 共有两个大版本,如下表所示。
版本 | 时间 | 说明 |
---|---|---|
Log4j 1.x | 1999 年至 2015 年 | 即我们常说的 Log4j, 它于 1999 年首次发布,就迅速成为有史以来最常用的日志框架。 2015 年 8 月 5 日,Apache Logging Services 宣布 Log4j 1.x 生命周期结束,其代码库不再发布更新,并鼓励用户升级到 Log4j 2.x。 |
Log4j 2.x | 2014 年至今 | 即我们常说的 Log4j2,2014 年 Log4j 2.x 作为 Log4j 1.x 的替代品发布。 Log4j 2.x 是对 Log4j 1.x 的重大升级,它完全重写了 Log4j 的日志实现,比 Log4j 1.x 效率更高、更可靠且更易于开发和维护。此外,Log4j 2.x 还对 Logback 进行了许多改进,修复了 Logback 架构中的一些固有问题,目前已经更新到 2.17.1 版本。 |
Spring 5 是基于 Java 8 实现的,其自身作了不少的优化,将许多不建议使用的类和方法从代码库中删除,其中就包括了 Log4jConfigListener(Spring 加载 Log4j 配置的监听器)。因此从 Spring 5 开始就不在支持对 Log4j 的整合,而更加推荐我们通过 Log4j2 进行日志记录。
下面我们们就来介绍下 Spring 是如何整合 Log4j2 的。
1.新建一个名为 my-spring-log4j-demo 的 Spring 项目,并将与 Spring 相关的 Jar 包导入到该项目中。
2.使用浏览器访问 Log4j2 官网,点击左侧导航栏中的 Download,跳转到 Log4j2 的下载页面。
3.我们根据自身操作系统的不同,选择不同的压缩包
4.对下载完成的压缩包进行解压,并将以下 3 个依赖包导入到 my-spring-log4j-demo 项目中。
5.此外,我们还需要向 my-spring-log4j-demo 项目中导入一个 slf4j-api-xxx.jar ,但该依赖包的版本是有限制的。
此前,我们下载的 Log4j2 的依赖包中有一个 log4j-slf4j18-impl-2.17.1.jar,它是 Log4j2 提供的绑定到 SLF4J 的配置器。
Log4j2 提供了以下 2 个适配器:
6.创建一个名为 log4j2.xml 的配置文件
7.创建一个名为 HelloLog4j 的 Java 类
public class HelloLog4j {
private static final Logger log = LoggerFactory.getLogger(HelloLog4j.class);
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
log.info("消息为:" + message);
}
}
8.创建一个 Beans.xml,
9.创建一个 MainApp 的类
public class MainApp {
private static final Logger log = LoggerFactory.getLogger(MainApp.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
log.info("正在从容器中获取 HelloLog4j 的 Bean");
HelloLog4j obj = context.getBean("helloLog4j", HelloLog4j.class);
obj.getMessage();
log.info("代码执行完成!");
}
}