spring
1.spring ioc
? IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
? Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
? IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。
1.控制反转
? 在传统的 Java 应用中,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 new Object() 的方式将后者的对象创建出来,然后才能实现属性或方法的调用。为了方便理解和描述,我们可以将前者称为“调用者”,将后者称为“被调用者”。也就是说,调用者掌握着被调用者对象创建的控制权。
但在 Spring 应用中,Java 对象创建的控制权是掌握在 IoC 容器手里的,其大致步骤如下。
- 开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 标签、在 Java 类上使用 @Component 注解等。
- Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
- 当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。
? IoC 带来的最大改变不是代码层面的,而是从思想层面上发生了“主从换位”的改变。原本调用者是主动的一方,它想要使用什么资源就会主动出击,自己创建;但在 Spring 应用中,IoC 容器掌握着主动权,调用者则变成了被动的一方,被动的等待 IoC 容器创建它所需要的对象(Bean)。
这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”。
2.依赖注入(DI)
? 在了解了 IoC 之后,我们还需要了解另外一个非常重要的概念:依赖注入。
? 依赖注入(Denpendency Injection,简写为 DI)是 Martin Fowler 在 2004 年在对“控制反转”进行解释时提出的。Martin Fowler 认为“控制反转”一词很晦涩,无法让人很直接的理解“到底是哪里反转了”,因此他建议使用“依赖注入”来代替“控制反转”。
? 在面向对象中,对象和对象之间是存在一种叫做“依赖”的关系。简单来说,依赖关系就是在一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象。
3.Ioc容器的两种实现
? IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。
BeanFactory
? BeanFactory 是 IoC 容器的基本实现,也是 Spring 提供的最简单的 IoC 容器,它提供了 IoC 容器最基本的功能,由 org.springframework.beans.factory.BeanFactory 接口定义。
? BeanFactory 采用懒加载(lazy-load)机制,容器在加载配置文件时并不会立刻创建 Java 对象,只有程序中获取(使用)这个对对象时才会创建。
示例
@Test
public void test1() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloWorld",HelloWorld.class);
obj.setMessage("我的世界 我的世界");
obj.getMessage();
}
ApplicationContext
? ApplicationContext 是 BeanFactory 接口的子接口,是对 BeanFactory 的扩展。ApplicationContext 在 BeanFactory 的基础上增加了许多企业级的功能,例如 AOP(面向切面编程)、国际化、事务支持等。
ApplicationContext 接口有两个常用的实现类,具体如下表。
实现类 | 描述 | 示例代码 |
---|
ClassPathXmlApplicationContext | 加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 | ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation); | FileSystemXmlApplicationContext | 加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 | ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation); |
示例
public static void main(String[] args) {
BeanFactory context = new FileSystemXmlApplicationContext("D:\\eclipe workspace\\spring workspace\\HelloSpring\\src\\Beans.xml");
HelloWorld obj = context.getBean("helloWorld", HelloWorld.class);
obj.getMessage();
}
2.spring Bean定义
? 由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。
? 我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。如果希望这个大工厂生产和管理 Bean,就需要告诉容器需要哪些 Bean,以哪种方式装配。
Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
- Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
- XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。
通常情况下,Spring 的配置文件都是使用 XML 格式的。XML 配置文件的根元素是 ,该元素包含了多个子元素 。每一个 元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。
<?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-3.0.xsd">
<bean id="helloWorld" class="com.yc.www.HelloWorld">
<property name="message" value="Hello World!" />
</bean>
</beans>
在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。
3.Bean的属性定义
所谓 Bean 属性注入,简单点说就是将属性注入到 Bean 中的过程,而这属性既可以普通属性,也可以是一个对象(Bean)。
1.构造函数注入
我们可以通过 Bean 的带参构造函数,以实现 Bean 的属性注入。
使用构造函数实现属性注入大致步骤如下:
- 在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;
- 在 Spring 的 XML 配置文件中,通过 及其子元素 对 Bean 进行定义;
- 在 元素内使用 元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 元素。
示例
我们创建两个普通类,类中主要实现构造方法和tostring方法,使用构造函数创建bean,通过ref引用另一个Java对象的bean使对象成功输出。
- 创建HelloWorld.java,内部实现以下:
public class HelloWorld {
private static final Log loger=LogFactory.getLog(HelloWorld.class);
private String message;
private int age;
public HelloWorld(String message,int age) {
loger.info("正在执行 HelloWorld 的有参构造方法,参数分别为:message=" +message + ",age=" + age);
this.message = message;;
this.age=age;
}
@Override
public String toString() {
return "HelloWorld [message=" + message + ", age=" + age + "]";
}
}
- 创建Student.java,内部实现一个HelloWorld的属性,也就是引用HelloWorld里的方法。
public class Student {
private static final Log LOGGER = LogFactory.getLog(Student.class);
private int id;
private String name;
private HelloWorld hw;
public Student(int id, String name, HelloWorld hw) {
LOGGER.info("正在执行 Student 的有参构造方法,参数分别为:id=" + id + ",name=" + name + ",HelloWorld=" + hw);
this.id = id;
this.name = name;
this.hw = hw;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", HelloWorld=" + hw +
'}';
}
}
- 创建beans.xml的bean定义
<bean id="helloWorld" class="com.yc.www.HelloWorld">
<constructor-arg name="message" value="学习spring真有趣"/>
<constructor-arg name="age" value="20" />
</bean>
<bean id="student" class="com.yc.www.Student">
<constructor-arg name="id" value="10"/>
<constructor-arg name="name" value="我是spring爱好者"/>
<constructor-arg name="hw" ref="helloWorld" />
</bean>
- 创建测试类
@Test
public void test() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
Student obj = context.getBean("student",Student.class);
System.out.println(obj.toString());
}
- 控制台输出如下
3月 24, 2022 1:36:41 下午 com.yc.www.HelloWorld <init>
信息: 正在执行 HelloWorld 的有参构造方法,参数分别为:message=学习spring真有趣,age=20
3月 24, 2022 1:36:41 下午 com.yc.www.Student <init>
信息: 正在执行 Student 的有参构造方法,参数分别为:id=10,name=我是spring爱好者,HelloWorld=HelloWorld [message=学习spring真有趣, age=20]
Student{id=10, name='我是spring爱好者', HelloWorld=HelloWorld [message=学习spring真有趣, age=20]}
2.setter注入
我们可以通过 Bean 的 setter 方法,将属性值注入到 Bean 的属性中。
在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
使用 setter 注入的方式进行属性注入,大致步骤如下:
- 在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法;
- 在 Spring 的 XML 配置文件中,使用 及其子元素 对 Bean 进行定义;
- 在 元素内使用 元素对各个属性进行赋值。
示例
下面我们把上面的构造函数方法去掉,使用setter方法进行创建,在没有有参构造方法下,bean默认装配好了。
- HelloWorld.java如下
public class HelloWorld {
private static final Log loger=LogFactory.getLog(HelloWorld.class);
private String message;
private int age;
public void setMessage(String message) {
loger.info("正在执行---HelloWorld的setMessage---方法");
this.message = message;
}
public void setAge(int age) {
loger.info("正在执行---HelloWorld的setAge---方法");
this.age = age;
}
@Override
public String toString() {
return "HelloWorld [message=" + message + ", age=" + age + "]";
}
}
- Student.java如下
public class Student {
private static final Log loger = LogFactory.getLog(Student.class);
private int id;
private String name;
private HelloWorld hw;
public void setId(int id) {
loger.info("正在执行---Strudent的setid---方法");
this.id = id;
}
public void setName(String name) {
loger.info("正在执行---Strudent的setName---方法");
this.name = name;
}
public void setHw(HelloWorld hw) {
loger.info("正在执行---Strudent的setHw---方法");
this.hw = hw;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", HelloWorld=" + hw +
'}';
}
}
- beans.xml如下
<bean id="student" class="com.yc.www.Student">
<property name="id" value="10"/>
<property name="name" value="学习spring真有趣"/>
<property name="hw" ref="helloWorld"/>
</bean>
<bean id="helloWorld" class="com.yc.www.HelloWorld">
<property name="message" value="我是spring爱好者"/>
<property name="age" value="20" />
</bean>
- 创建测试类
@Test
public void test() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
Student obj = context.getBean("student",Student.class);
System.out.println(obj.toString());
}
3.短命名空间注入(p,c)
我们在通过构造函数或 setter 方法进行属性注入时,通常是在 元素中嵌套 和 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。
Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。
短命名空间 | 简化的xml设置 | 说明 |
---|
p 命名空间 | 元素中嵌套的 元素 | 是 setter 方式属性注入的一种快捷实现方式 | c 命名空间 | 元素中嵌套的 元素 | 是构造函数属性注入的一种快捷实现方式 |
p命名空间注入
p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。
首先我们需要在配置文件的 元素中导入以下 XML 约束。
xmlns:p="http://www.springframework.org/schema/p"
在导入 XML 约束后,我们就能通过以下形式实现属性注入。
<bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用">
使用 p 命名空间注入依赖时,必须注意以下 3 点:
- Java 类中必须有 setter 方法;
- Java 类中必须有无参构造器(类中不包含任何带参构造函数的情况,无参构造函数默认存在);
- 在使用 p 命名空间实现属性注入前,XML 配置的 元素内必须先导入 p 命名空间的 XML 约束。
示例
根据上面的setter方式注入改变,
<?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-3.0.xsd"
xmlns:p="http://www.springframework.org/schema/p">
<bean id="helloworld" class="com.yc.www.HelloWorld" p:message="spring最强" p:age="10" />
<bean id="student" class="com.yc.www.Student" p:id="20" p:name="spring最爱" p:hw-ref="helloworld"/>
</beans>
c命名空间注入
c 命名空间是构造函数注入的一种快捷实现方式。通过它,我们能够以 属性的形式实现构造函数方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。
首先我们需要在配置文件的 元素中导入以下 XML 约束。
xmlns:c="http://www.springframework.org/schema/c"
在导入 XML 约束后,我们就能通过以下形式实现属性注入。
<bean id="Bean 唯一标志符" class="包名+类名" c:普通属性="普通属性值" c:对象属性-ref="对象的引用">
使用 c 命名空间注入依赖时,必须注意以下 2 点:
- Java 类中必须包含对应的带参构造器;
- 在使用 c 命名空间实现属性注入前,XML 配置的 元素内必须先导入 c 命名空间的 XML 约束。
示例
根据上面构造函数方式注入改变
<?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-3.0.xsd"
xmlns:c="http://www.springframework.org/schema/c">
<bean id="helloworld" class="com.yc.www.HelloWorld" c:message="spring最强" c:age="10" />
<bean id="student" class="com.yc.www.Student" c:id="20" c:name="spring最爱" c:hw-ref="helloworld"/>
</beans>
4.spring注入内部bean
我们将定义在 元素的 或 元素内部的 Bean,称为“内部 Bean”。
1.setter方式注入内部bean
我们可以通过 setter 方式注入内部 Bean。此时,我们只需要在 标签下的 元素中,再次使用 元素对内部 Bean 进行定义,格式如下。
注意:内部 Bean 都是匿名的,不需要指定 id 和 name 的。即使制定了,IoC 容器也不会将它作为区分 Bean 的标识符,反而会无视 Bean 的 Scope 标签。因此内部 Bean 几乎总是匿名的,且总会随着外部的 Bean 创建。内部 Bean 是无法被注入到它所在的 Bean 以外的任何其他 Bean 的。
根据上面的的setter方法改变bean:
<bean id="student" class="com.yc.www.Student">
<property name="id" value="10" />
<property name="name" value="spring的世界"/>
<property name="hw">
<bean class="com.yc.www.HelloWorld">
<property name="message" value="spring全家桶"/>
<property name="age" value="20"/>
</bean>
</property>
</bean>
2.构造函数方式注入内部bean
我们可以通过构造方法注入内部 Bean。此时,我们只需要在 标签下的 元素中,再次使用 元素对内部 Bean 进行定义,格式如下。
<bean id="student" class="com.yc.www.Student">
<constructor-arg name="id" value="10" />
<constructor-arg name="name" value="spring的世界"/>
<constructor-arg name="hw">
<bean class="com.yc.www.HelloWorld">
<constructor-arg name="message" value="spring全家桶"/>
<constructor-arg name="age" value="20"/>
</bean>
</constructor-arg>
</bean>
5.spring注入集合
我们还可以在 Bean 标签下的 元素中,使用以下元素配置 Java 集合类型的属性和参数,例如 List、Set、Map 以及 Properties 等。
类型 | 说明 |
---|
<list> | 用于注入 list 类型的值,允许重复 | <set> | 用于注入 set 类型的值,不允许重复 | <map> | 用于注入 key-value 的集合,其中 key 和 value 都可以是任意类型 | <props> | 用于注入 key-value 的集合,其中 key 和 value 都是字符串类型 |
示例1:在集合中设置普通类型的值
- 创建HelloWorld.java
public class HelloWorld {
private String[] str;
private List<String> list;
private Map<String,String> map;
private Set<String> set;
public void setStr(String[] str) {
this.str = str;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setSet(Set<String> set) {
this.set = set;
}
@Override
public String toString() {
return "HelloWorld [str=" + Arrays.toString(str) + ", list=" + list + ", map=" + map + ", set=" + set + "]";
}
}
- 创建bean
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="str">
<array>
<value>java</value>
<value>c++</value>
<value>html开发</value>
</array>
</property>
<property name="list">
<list>
<value>vue开发</value>
<value>react</value>
<value>c语言</value>
</list>
</property>
<property name="map">
<map>
<entry key="语言" value="java"></entry>
<entry key="兴趣" value="Java开发"></entry>
</map>
</property>
<property name="set">
<set>
<value>vue开发</value>
<value>react</value>
<value>c语言</value>
</set>
</property>
</bean>
- 创建测试类
@Test
public void test() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloworld",HelloWorld.class);
System.out.println(obj.toString());
}
- 控制台如下
HelloWorld [str=[java, c++, html开发], list=[vue开发, react, c语言], map={语言=java, 兴趣=Java开发}, set=[vue开发, react, c语言]]
示例2:在集合中设置对象的值
- 创建一个对象Student.java
public class Student {
private int id;
private String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
- 创建一个HelloWorld.java,内部实现多个集合指定Student作为对象
public class HelloWorld {
private Student[] str;
private List<Student> list;
private Map<Student,Student> map;
private Set<Student> set;
public void setStr(Student[] str) {
this.str = str;
}
public void setList(List<Student> list) {
this.list = list;
}
public void setMap(Map<Student, Student> map) {
this.map = map;
}
public void setSet(Set<Student> set) {
this.set = set;
}
@Override
public String toString() {
return "HelloWorld [str=" + Arrays.toString(str) + ", list=" + list + ", map=" + map + ", set=" + set + "]";
}
}
- 修改beans
<bean id="student" class="com.yc.www.Student">
<property name="id" value="20010" />
<property name="name" value="spring"/>
</bean>
<bean id="student1" class="com.yc.www.Student">
<property name="id" value="20010" />
<property name="name" value="springmvc"/>
</bean>
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="str">
<array>
<ref bean="student" />
<ref bean="student1"/>
</array>
</property>
<property name="list">
<list>
<ref bean="student"/>
</list>
</property>
<property name="map">
<map>
<entry key-ref="student" value-ref="student1" />
</map>
</property>
<property name="set">
<set>
<ref bean="student"/>
</set>
</property>
</bean>
- 创建测试类
@Test
public void test() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloworld",HelloWorld.class);
System.out.println(obj.toString());
}
- 控制输出
HelloWorld [str=[Student [id=20010, name=spring], Student [id=20010, name=springmvc]], list=[Student [id=20010, name=spring]], map={Student [id=20010, name=spring]=Student [id=20010, name=springmvc]}, set=[Student [id=20010, name=spring]]]
6.spring注入其他类型的属性
除了普通属性、对象属性(Bean)、集合等属性外,Spring 也能够将其他类型的属性注入到 Bean 中,例如 Null 值、字面量、复合物属性等。
1.注入null
我们可以在 XML 配置文件中,通过 元素将 Null 值注入到 Bean 中。而不是直接给定一个空字符串。
- 创建一个HelloWorld类
public class HelloWorld {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "HelloWorld [name=" + name + "]";
}
}
- 创建bean并且给定一个空值
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="name">
<null/>
</property>
</bean>
- 创建测试类最后输出
HelloWorld [name=null]
2.注入字面量
我们知道,在 XML 配置中“<”、“>”、“&”等特殊字符是不能直接保存的,否则 XML 语法检查时就会报错。此时,我们可以通过以下两种方式将包含特殊符号的属性注入 Bean 中。
在 XML 中,需要转义的字符如下表所示。
特殊字符 | 转义字符 |
---|
< | < | & | & | > | > | " | " | ’ | &apos |
在转义过程中,需要注意以下几点:
- 转义序列字符之间不能有空格;
- 转义序列必须以“;”结束;
- 单独出现的“&”不会被认为是转义的开始;
- 区分大小写。
下面我们通过一个简单的实例,演示下如何通过转义将包含特殊符号的属性注入到 Bean 中。
- 创建一个HelloWorld类
public class HelloWorld {
private String name;
private String pwd;
public void setPwd(String pwd) {
this.pwd = pwd;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "HelloWorld [name=" + name + ", pwd=" + pwd + "]";
}
}
- 创建bean
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="name" value="<你好世界>" />
<property name="pwd" value=""这是密码"" />
</bean>
- 创建测试类最后输出
HelloWorld [name=<你好世界>, pwd="这是密码"]
3.使用短字符串 <![CDATA[]]>
通过短字符串<![CDATA[]]> 将包含特殊符号的属性值包裹起来,可以让 XML 解析器忽略对其中内容的解析,以属性原本的样子注入到 Bean 中。
使用短字符串 <![CDATA[]]> 需要注意以下几点:
- 此部分不能再包含”]]>”;
- 不允许嵌套使用;
- “]]>”中不能包含空格或者换行。
- 在HelloWorld类中添加一个message的属性,在bean中注入
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="name" value="<你好世界>" />
<property name="pwd" value=""这是密码"" />
<property name="message">
<value><![CDATA[<"输入的这几个符号照样输出">]]></value>
</property>
</bean>
- 创建测试类最后输出
HelloWorld [name=<你好世界>, pwd="这是密码", message=<"输入的这几个符号照样输出">]
4.级联属性赋值
我们可以在 的 子元素中,为它所依赖的 Bean 的属性进行赋值,这就是所谓的“级联属性赋值”。
使用级联属性赋值时,需要注意以下 3点:
- Java 类中必须有 setter 方法;
- Java 类中必须有无参构造器(默认存在);
- 依赖其他 Bean 的类中,必须提供一个它依赖的 Bean 的 getXxx() 方法。
- 创建Student类
public class Student {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [name=" + name + "]";
}
}
2,. 创建HelloWorld类
public class HelloWorld {
private String name;
private String pwd;
private String message;
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void setMessage(String message) {
this.message = message;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "HelloWorld [name=" + name + ", pwd=" + pwd + ", message=" + message + ", student=" + student + "]";
}
}
- 创建bean
<bean id="student" class="com.yc.www.Student">
<property name="name" value="学习java真有趣" />
</bean>
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="name" value="<你好世界>" />
<property name="pwd" value=""这是密码"" />
<property name="message">
<value><![CDATA[<"输入的这几个符号照样输出">]]></value>
</property>
<property name="student" ref="student" />
<property name="student.name" value="学习是不太容易的事"/>
</bean>
- 创建测试类输出
HelloWorld [name=<你好世界>, pwd="这是密码", message=<"输入的这几个符号照样输出">, student=Student [name=学习是不太容易的事]]
7.Spring bean的作用域
默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。
我们可以在 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。
下面介绍两种最常用的模式
1.ingleton(单例模式)
singleton 是 Spring 容器默认的作用域。当 Bean 的作用域为 singleton 时,Spring IoC 容器中只会存在一个共享的 Bean 实例。这个 Bean 实例将存储在高速缓存中,所有对于这个 Bean 的请求和引用,只要 id 与这个 Bean 定义相匹配,都会返回这个缓存中的对象实例。
如果一个 Bean 定义的作用域为 singleton ,那么这个 Bean 就被称为 singleton bean。在 Spring IoC 容器中,singleton bean 是 Bean 的默认创建方式,可以更好地重用对象,节省重复创建对象的开销。
在 Spring 配置文件中,可以使用 元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:
<bean id="..." class="..." scope="singleton"/>
示例
- 创建一个HelloWorld类
public class HelloWorld {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 创建bean
<bean id="helloworld" class="com.yc.www.HelloWorld" scope="singleton">
<property name="name" value="这是单例模式" />
</bean>
- 创建测试类
@Test
public void test() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloworld",HelloWorld.class);
HelloWorld obj1 = context.getBean("helloworld",HelloWorld.class);
System.out.println(obj);
System.out.println(obj1);
}
- 控制台输出
com.yc.www.HelloWorld@2ad48653
com.yc.www.HelloWorld@2ad48653
从控制台的输出可以看出,两次获得的 Bean 实例的地址完全一样,这说明 IoC 容器只创建了一个 helloworld实例。由于 singleton 是 Spring IoC 容器的默认作用域,因此即使省略 scope 属性,控制台的输出结果也一样的。
2.prototype(原型模式)
如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。
在 Spring 配置文件中,可以使用 元素的 scope 属性将 Bean 的作用域定义成 prototype,其配置方式如下所示:
<bean id="..." class="..." scope="prototype"/>
示例
- 根据上面的javalei修改bean就可以
<bean id="helloworld" class="com.yc.www.HelloWorld" scope="prototype">
<property name="name" value="这是单例模式" />
</bean>
- 创建测试类
@Test
public void test() {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloworld",HelloWorld.class);
HelloWorld obj1 = context.getBean("helloworld",HelloWorld.class);
System.out.println(obj);
System.out.println(obj1);
}
- 控制台输出
com.yc.www.HelloWorld@e3b3b2f
com.yc.www.HelloWorld@50f6ac94
8.spring bean的生命周期
在传统的 Java 应用中,Bean 的生命周期很简单,使用 Java 关键字 new 进行 Bean 的实例化后,这个 Bean 就可以使用了。一旦这个 Bean 长期不被使用,Java 自动进行垃圾回收。
相比之下,Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:
- Bean 的实例化
- Bean 属性赋值
- Bean 的初始化
- Bean 的使用
- Bean 的销毁
Spring 根据 Bean 的作用域来选择 Bean 的管理方式,
- 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
- 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。
1.spring生命周期流程
Spring Bean 的完整生命周期从创建 Spring IoC 容器开始,直到最终 Spring IoC 容器销毁 Bean 为止,其具体流程如下图所示。
Bean 生命周期的整个执行过程描述如下。
- Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。
- 对 Bean 进行属性注入。
- 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
- 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
- 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
- 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
- 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
- 如果在 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
- 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
9.spring后置处理器(BeanPostProcessor)
本节主要介绍在《Spring Bean生命周期》一节提到的 BeanPostProcessor 接口。BeanPostProcessor 接口也被称为后置处理器,通过该接口可以自定义调用初始化前后执行的操作方法。
BeanPostProcessor 接口源码如下:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
该接口中包含了两个方法:
- postProcessBeforeInitialization() 方法:在 Bean 实例化、属性注入后,初始化前调用。
- postProcessAfterInitialization() 方法:在 Bean 实例化、属性注入、初始化都完成后调用。
当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,取值越大优先级越低。
示例
下面我们就来演示下 BeanPostProcessor 接口的用法,步骤如下:
- 创建HelloWorld类
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Message : " + message);
}
public void init() {
System.out.println("Bean 正在初始化");
}
public void destroy() {
System.out.println("Bean 将要被销毁");
}
}
- 创建initHelloWorld类
public class initHelloWorld implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 5;
}
}
需要注意的是,postProcessBeforeInitialization 和 postProcessAfterInitialization 方法返回值不能为 null,否则会报空指针异常或者通过 getBean() 方法获取不到 Bean 实例对象。
- 创建initHelloWorld2类
public class initHelloWorld2 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
- 创建bean
<bean id="helloworld" class="com.yc.www.HelloWorld" init-method="init" destroy-method="destroy">
<property name="message" value="这是后置" />
</bean>
<bean class="com.yc.www.initHelloWorld" />
<bean class="com.yc.www.initHelloWorld2" />
- 创建测试类
@Test
public void test() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloworld",HelloWorld.class);
obj.getMessage();
context.registerShutdownHook();
}
- 控制台输出
B Before : helloworld
A Before : helloworld
Bean 正在初始化
B After : helloworld
A After : helloworld
Message : 这是后置
Bean 将要被销毁
由运行结果可以看出,postProcessBeforeInitialization 方法是在 Bean 实例化和属性注入后,自定义初始化方法前执行的。而 postProcessAfterInitialization 方法是在自定义初始化方法后执行的。由于 getOrder 方法返回值越大,优先级越低,因此 InitHelloWorld2 先执行。
10.Spring bean继承
在 Spring 中,Bean 和 Bean 之间也存在继承关系。我们将被继承的 Bean 称为父 Bean,将继承父 Bean 配置信息的 Bean 称为子 Bean。
Spring Bean 的定义中可以包含很多配置信息,例如构造方法参数、属性值。子 Bean 既可以继承父 Bean 的配置数据,也可以根据需要重写或添加属于自己的配置信息。
在 Spring XML 配置中,我们通过子 Bean 的 parent 属性来指定需要继承的父 Bean,配置格式如下。
<bean id="parentBean" class="xxx.xxxx.xxx.ParentBean" >
<property name="xxx" value="xxx"></property>
<property name="xxx" value="xxx"></property>
</bean>
<bean id="childBean" class="xxx.xxx.xxx.ChildBean" parent="parentBean"></bean>
示例
- 创建一个HelloWorld类
public class HelloWorld {
private String name;
private Integer age;
public void setName(String name) {
System.out.println("helloworld==="+name);
this.name = name;
}
public void setAge(Integer age) {
System.out.println("helloworld===="+age);
this.age = age;
}
@Override
public String toString() {
return "HelloWorld [name=" + name + ", age=" + age + "]";
}
}
- 创建一个Dog类
public class Dog {
private String name;
private Integer age;
private String call;
public void setName(String name) {
System.out.println("dog====="+name);
this.name = name;
}
public void setAge(Integer age) {
System.out.println("dog====="+age);
this.age = age;
}
public void setCall(String call) {
System.out.println("dog====="+call);
this.call = call;
}
@Override
public String toString() {
return "Dog [name=" + name + ", age=" + age + ", call=" + call + "]";
}
}
- 创建bean
<bean id="helloworld" class="com.yc.www.HelloWorld">
<property name="name" value="小王" />
<property name="age" value="20" />
</bean>
<bean id="dog" class="com.yc.www.Dog" parent="helloworld">
<property name="name" value="小海"/>
<property name="call" value="汪汪"/>
</bean>
- 创建测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Dog obj = context.getBean("dog",Dog.class);
System.out.println(obj.toString());
}
- 控制台输出
helloworld===小王
helloworld====20
dog=====小海
dog=====20
dog=====汪汪
Dog [name=小海, age=20, call=汪汪]
1.bean定义模板
在父 Bean 的定义中,有一个十分重要的属性,那就是 abstract 属性。如果一个父 Bean 的 abstract 属性值为 true,则表明这个 Bean 是抽象的。
抽象的父 Bean 只能作为模板被子 Bean 继承,它不能实例化,也不能被其他 Bean 引用,更不能在代码中根据 id 调用 getBean() 方法获取,否则就会返回错误。
在父 Bean 的定义中,既可以指定 class 属性,也可以不指定 class 属性。如果父 Bean 定义没有明确地指定 class 属性,那么这个父 Bean 的 abstract 属性就必须为 true。
示例
- 修改bean
<bean id="helloworld" abstract="true">
<property name="name" value="小王" />
<property name="age" value="20" />
</bean>
<bean id="dog" class="com.yc.www.Dog" parent="helloworld">
<property name="name" value="小海"/>
<property name="call" value="汪汪"/>
</bean>
- 创建测试类最终输出
dog=====小海
dog=====20
dog=====汪汪
Dog [name=小海, age=20, call=汪汪]
11.spring自动装配
我们把 Spring 在 Bean 与 Bean 之间建立依赖关系的行为称为“装配”。
Spring 的 IOC 容器虽然功能强大,但它本身不过只是一个空壳而已,它自己并不能独自完成装配工作。需要我们主动将 Bean 放进去,并告诉它 Bean 和 Bean 之间的依赖关系,它才能按照我们的要求完成装配工作。
在前面的学习中,我们都是在 XML 配置中通过 和 中的 ref 属性,手动维护 Bean 与 Bean 之间的依赖关系的。
例如,一个部门(Dept)可以有多个员工(Employee),而一个员工只可能属于某一个部门,这种关联关系定义在 XML 配置的 Bean 定义中,形式如下。
<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-3.0.xsd">
<bean id="dept" class="net.biancheng.c.Dept"></bean>
<bean id="employee" class="net.biancheng.c.Employee">
<property name="dept" ref="dept"></property>
</bean>
</beans>
对于只包含少量 Bean 的应用来说,这种方式已经足够满足我们的需求了。但随着应用的不断发展,容器中包含的 Bean 会越来越多,Bean 和 Bean 之间的依赖关系也越来越复杂,这就使得我们所编写的 XML 配置也越来越复杂,越来越繁琐。
我们知道,过于复杂的 XML 配置不但可读性差,而且编写起来极易出错,严重的降低了开发人员的开发效率。为了解决这一问题,Spring 框架还为我们提供了“自动装配”功能。
1.spring自动装配
Spring 的自动装配功能可以让 Spring 容器依据某种规则(自动装配的规则,有五种),为指定的 Bean 从应用的上下文(AppplicationContext 容器)中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何 和 元素 ref 属性的情况下进行的。
Spring 的自动装配功能能够有效地简化 Spring 应用的 XML 配置,因此在配置数量相当多时采用自动装配降低工作量。
Spring 框架是默认不支持自动装配的,要想使用自动装配,则需要对 Spring XML 配置文件中 元素的 autowire 属性进行设置。
<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-3.0.xsd">
<bean id="dept" class="net.biancheng.c.Dept"></bean>
<bean id="employee" class="net.biancheng.c.Employee" autowire="byName">
</bean>
</beans>
2.自动装配规则
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 属性来定义。 |
示例
- 创建Dept类
public class Dept {
private String deptNo;
private String deptName;
public Dept() {
System.out.println("正在执行 Dept 的无参构造方法>>>>");
}
public Dept(String deptNo, String deptName) {
System.out.println("正在执行 Dept 的有参构造方法>>>>");
this.deptNo = deptNo;
this.deptName = deptName;
}
public void setDeptNo(String deptNo) {
System.out.println("正在执行 Dept 的 setDeptNo 方法>>>>");
this.deptNo = deptNo;
}
public void setDeptName(String deptName) {
System.out.println("正在执行 Dept 的 setDeptName 方法>>>>");
this.deptName = deptName;
}
public String getDeptNo() {
return deptNo;
}
public String getDeptName() {
return deptName;
}
@Override
public String toString() {
return "Dept{" +
"deptNo='" + deptNo + '\'' +
", deptName='" + deptName + '\'' +
'}';
}
}
- 创建Employee类
public class Employee {
private String empNo;
private String empName;
private Dept dept;
public Employee() {
System.out.println("正在执行 Employee 的无参构造方法>>>>");
}
public Employee(String empNo, String empName, Dept dept) {
System.out.println("正在执行 Employee 的有参构造方法>>>>");
this.empNo = empNo;
this.empName = empName;
this.dept = dept;
}
public void setEmpNo(String empNo) {
System.out.println("正在执行 Employee 的 setEmpNo 方法>>>>");
this.empNo = empNo;
}
public void setEmpName(String empName) {
System.out.println("正在执行 Employee 的 setEmpName 方法>>>>");
this.empName = empName;
}
public void setDept(Dept dept) {
System.out.println("正在执行 Employee 的 setDept 方法>>>>");
this.dept = dept;
}
public Dept getDept() {
return dept;
}
public String getEmpNo() {
return empNo;
}
public String getEmpName() {
return empName;
}
@Override
public String toString() {
return "Employee{" +
"empNo='" + empNo + '\'' +
", empName='" + empName + '\'' +
", dept=" + dept +
'}';
}
}
- 创建测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Employee obj = context.getBean("employee",Employee.class);
System.out.println(obj.toString());
}
3. 不使用自动装配(autowire=“no”)
autowire=“no” 表示不使用自动装配,此时我们必须通过 元素的 和 元素的 ref 属性维护 Bean 的依赖关系。
<bean id="dept" class="com.yc.www.Dept">
<property name="deptNo" value="1002" />
<property name="deptName" value="技术部" />
</bean>
<bean id="employee" class="com.yc.www.Employee" autowire="no">
<property name="empNo" value="2001"/>
<property name="empName" value="小郭"/>
<property name="dept" ref="dept" />
</bean>
控制台输出
正在执行 Dept 的无参构造方法>>>>
正在执行 Dept 的 setDeptNo 方法>>>>
正在执行 Dept 的 setDeptName 方法>>>>
正在执行 Employee 的无参构造方法>>>>
正在执行 Employee 的 setEmpNo 方法>>>>
正在执行 Employee 的 setEmpName 方法>>>>
正在执行 Employee 的 setDept 方法>>>>
Employee{empNo='2001', empName='小郭', dept=Dept{deptNo='1002', deptName='技术部'}}
4.按名称自动装配(autowire=“byName”)
autowire=“byName” 表示按属性名称自动装配,XML 文件中 Bean 的 id 或 name 必须与类中的属性名称相同。
<bean id="dept" class="com.yc.www.Dept">
<property name="deptNo" value="1002" />
<property name="deptName" value="技术部" />
</bean>
<bean id="employee" class="com.yc.www.Employee" autowire="byName">
<property name="empNo" value="2001"/>
<property name="empName" value="小郭"/>
</bean>
控制台输出
正在执行 Dept 的无参构造方法>>>>
正在执行 Dept 的 setDeptNo 方法>>>>
正在执行 Dept 的 setDeptName 方法>>>>
正在执行 Employee 的无参构造方法>>>>
正在执行 Employee 的 setEmpNo 方法>>>>
正在执行 Employee 的 setEmpName 方法>>>>
正在执行 Employee 的 setDept 方法>>>>
Employee{empNo='2001', empName='小郭', dept=Dept{deptNo='1002', deptName='技术部'}}
在 Beans.xml 中,将员工 Bean 的 id 修改为 dept2,配置如下。
<bean id="dept1" class="com.yc.www.Dept">
<property name="deptNo" value="1002" />
<property name="deptName" value="技术部" />
</bean>
<bean id="employee" class="com.yc.www.Employee" autowire="byName">
<property name="empNo" value="2001"/>
<property name="empName" value="小郭"/>
</bean>
控制台输出
正在执行 Dept 的无参构造方法>>>>
正在执行 Dept 的 setDeptNo 方法>>>>
正在执行 Dept 的 setDeptName 方法>>>>
正在执行 Employee 的无参构造方法>>>>
正在执行 Employee 的 setEmpNo 方法>>>>
正在执行 Employee 的 setEmpName 方法>>>>
Employee{empNo='2001', empName='小郭', dept=null}
5.按类型自动装配(autowire=“byType”)
autowire=“byType” 表示按类中对象属性数据类型进行自动装配。即使 XML 文件中 Bean 的 id 或 name 与类中的属性名不同,只要 Bean 的 class 属性值与类中的对象属性的类型相同,就可以完成自动装配。
<bean id="dept1" class="com.yc.www.Dept">
<property name="deptNo" value="1002" />
<property name="deptName" value="技术部" />
</bean>
<bean id="employee" class="com.yc.www.Employee" autowire="byType">
<property name="empNo" value="2001"/>
<property name="empName" value="小郭"/>
</bean>
控制台输出
正在执行 Dept 的无参构造方法>>>>
正在执行 Dept 的 setDeptNo 方法>>>>
正在执行 Dept 的 setDeptName 方法>>>>
正在执行 Employee 的无参构造方法>>>>
正在执行 Employee 的 setEmpNo 方法>>>>
正在执行 Employee 的 setEmpName 方法>>>>
正在执行 Employee 的 setDept 方法>>>>
Employee{empNo='2001', empName='小郭', dept=Dept{deptNo='1002', deptName='技术部'}}
如果同时存在多个相同类型的 Bean,则注入失败,并且引发异常。
<bean id="dept" class="com.yc.www.Dept">
<property name="deptNo" value="1002" />
<property name="deptName" value="技术部的世界" />
</bean>
<bean id="dept1" class="com.yc.www.Dept">
<property name="deptNo" value="1002" />
<property name="deptName" value="技术部" />
</bean>
<bean id="employee" class="com.yc.www.Employee" autowire="byType">
<property name="empNo" value="2001"/>
<property name="empName" value="小郭"/>
</bean>
6.构造函数自动装配(autowire=“constructor”)
autowire=“constructor” 表示按照 Java 类中构造函数进行自动装配。
<bean id="dept" class="com.yc.www.Dept">
<constructor-arg name="deptNo" value="2001"/>
<constructor-arg name="deptName" value="技术部" />
</bean>
<bean id="employee" class="com.yc.www.Employee" autowire="constructor">
<constructor-arg name="empNo" value="1999" />
<constructor-arg name="empName" value="小郭" />
</bean>
控制台输出
正在执行 Dept 的有参构造方法>>>>
正在执行 Employee 的有参构造方法>>>>
Employee{empNo='1999', empName='小郭', dept=Dept{deptNo='2001', deptName='技术部'}}
12.spring自动装配(基于注解)
从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。
Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。
Spring 通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注解定义 Bean
- 依赖注入
1.开启组件扫描
Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。
<?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/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<context:component-scan base-package="com.yc.www"></context:component-scan>
</beans>
注意:在使用 <context:component-scan> 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。
2.使用注解定义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 相同。 |
3.基于注解方式实现依赖注入
注解 | 说明 |
---|
@Autowired | 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。@Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用 | @Resource | 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。 1. Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。2. 如果指定 name 属性,则按实例名称进行装配;3. 如果指定 type 属性,则按 Bean 类型进行装配; 4. 如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。 | @Qualifier | 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定. |
示例
- 创建一个USerdao接口
public interface Userdao {
public void print();
}
- 创建一个UserService接口
public interface UserService {
public void out();
}
- 创建一个UserDaoImpl类,继承Userdao接口
@Repository("userDao")
public class UserDaoImpl implements Userdao{
@Override
public void print() {
System.out.println("spring全家桶");
}
}
- 创建一个UserServiceImpl类,实现UserService接口
@Service("UserService")
public class UserServiceImpl implements UserService {
@Autowired
private Userdao userdao;
public Userdao getUserdao() {
return userdao;
}
public void setUserdao(Userdao userdao) {
this.userdao = userdao;
}
@Override
public void out() {
userdao.print();
System.out.println("一个优秀实用的网站");
}
}
- 创建一个UserController类
@Controller("usercontroller")
public class UserController {
@Autowired
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void dostr() {
userService.out();
System.out.println("完美的网站");
}
}
- 创建一个测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserController bean = context.getBean("usercontroller",UserController.class);
bean.dostr();
}
- 控制台输出
spring全家桶
一个优秀实用的网站
完美的网站
@Autowired的required属性
- 默认为required=true,用来注入已有的bean,表示注入的时候,该bean必须存在,否则会注入失败,报错
- requird=false,表示忽略当前注入的bean,即使没有bean也不会报错,如果有注入的bean,则会自动匹配。
4.@Qualifier的使用
@Qualifier注释主要是碰见多个相同的bean注入,但属性名不同,并不意味就会直接报错,@Autowired注释会去查找上下文与byName元素相同的属性,找到与@Qualifier指定的value值相同的bean。@Resource也可以像@Qualifier指定bean,使用name属性指定。
- xml配置
<bean class="com.yc.ch.Address" id="address" p:name="游泳"/>
<bean class="com.yc.ch.Address" id="address2" p:name="打排球"/>
<bean class="com.yc.ch.Game" id="game" p:name="计算机"/>
<bean class="com.yc.ch.User" id="user" p:name="Tom"/>
@Autowired
@Qualifier("address2")
private Address address;
13.Spring AOP切面编程
除了控制反转(IoC)和依赖注入(DI)外,Spring 框架还提供了对面向切面编程(AOP)的支持。本节,我们就对 AOP 面向切面编程进行讲解。
AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。
通常情况下,我们会根据业务使用 OOP(面向对象)思想,将应用划分为多个不同的业务模块,每个模块的核心功能都只为特定的业务领域提供服务,例如电商系统中的订单模块、商品模块、库存模块就分别是为维护电商系统的订单信息、商品信息以及库存信息而服务的。
但除此之外,应用中往往还存在一些非业务的通用功能,例如日志管理、权限管理、事务管理、异常管理等。这些通用功能虽然与应用的业务无关,但几乎所有的业务模块都会使用到它们,因此这些通用功能代码就只能横向散布式地嵌入到多个不同的业务模块之中。这无疑会产生大量重复性代码,不利于各个模块的复用。
您可能会想,可以将这些重复性代码封装成为公共函数,然后在业务模块中显式的调用,不也能减少重复性代码吗?是的,这样做的确能一定程度上减少重复性代码,但这样也增加了业务代码与公共函数的耦合性,任何对于公共函数的修改都会对所有与之相关的业务代码造成影响。
1.面向切面编程(AOP)
与 OOP 中纵向的父子继承关系不同,AOP 是通过横向的抽取机制实现的。它将应用中的一些非业务的通用功能抽取出来单独维护,并通过声明的方式(例如配置文件、注解等)定义这些功能要以何种方式作用在那个应用中,而不是在业务模块的代码中直接调用。
这虽然设计公共函数有几分类似,但传统的公共函数除了在代码直接硬调用之外并没有其他手段。AOP 则为这一问题提供了一套灵活多样的实现方法(例如 Proxy 代理、拦截器、字节码翻译技术等),可以在无须修改任何业务代码的基础上完成对这些通用功能的调用和修改。
AOP 编程和 OOP 编程的目标是一致的,都是为了减少程序中的重复性代码,让开发人员有更多的精力专注于业务逻辑的开发,只不过两者的实现方式大不相同。
OOP 就像是一根“绣花针”,是一种婉约派的选择,它使用继承和组合方式,仔细地为所有涉及通用功能的模块编制成一套类和对象的体系,以达到减少重复性代码的目标。而 AOP 则更像是一把“砍柴刀”,是一种豪放派的选择,大刀阔斧的规定,凡是某包某类下的某方法都一并进行处理。
AOP 不是用来替换 OOP 的,而是 OOP 的一种延伸,用来解决 OOP 编程中遇到的问题。
2.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 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。 |
3.AOP术语
与大多数的技术一样,AOP 已经形成一套属于自己的概念和术语。
名称 | 说明 |
---|
Joinpoint(连接点) | AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。 | Pointcut(切入点) | 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 | Advice(通知) | 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。 | Target(目标) | 指代理的目标对象,通常也被称为被通知(advised)对象。 | Weaving(织入) | 指把增强代码应用到目标对象上,生成代理对象的过程。 | Proxy(代理) | 指生成的代理对象。 | Aspect(切面) | 切面是切入点(Pointcut)和通知(Advice)的结合。 |
Advice 直译为通知,也有人将其翻译为“增强处理”,共有 5 种类型,如下表所示。
通知 | 说明 |
---|
before(前置通知) | 通知方法在目标方法调用之前执行 | after(后置通知) | 通知方法在目标方法返回或异常后调用 | after-returning(返回后通知) | 通知方法会在目标方法返回后调用 | after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 | around(环绕通知) | 通知方法会将目标方法封装起来 |
4.AOP类型
动态 AOP
动态 AOP 的织入过程是在运行时动态执行的。其中最具代表性的动态 AOP 实现就是 Spring AOP,它会为所有被通知的对象创建代理对象,并通过代理对象对被原对象进行增强。
相较于静态 AOP 而言,动态 AOP 的性能通常较差,但随着技术的不断发展,它的性能也在不断的稳步提升。
动态 AOP 的优点是它可以轻松地对应用程序的所有切面进行修改,而无须对主程序代码进行重新编译。
静态 AOP
静态 AOP 是通过修改应用程序的实际 Java 字节码,根据需要修改和扩展程序代码来实现织入过程的。最具代表性的静态 AOP 实现是 AspectJ。
相较于动态 AOP 来说,性能较好。但它也有一个明显的缺点,那就是对切面的任何修改都需要重新编译整个应用程序。
5.AOP的优势
AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。
在 Spring 框架中使用 AOP 主要有以下优势。
- 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品,最重要的是,这种服务是声明式事务管理。
- 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
- 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。
14.Spring-AOP编程
Spring AOP 是 Spring 框架的核心模块之一,它使用纯 Java 实现,因此不需要专门的编译过程和类加载器,可以在程序运行期通过代理方式向目标类织入增强代码。
1.Spring AOP的代理机制
Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。
Spring AOP 的底层通过以下 2 种动态代理机制,为目标对象(Target Bean)执行横向织入的。
代理技术 | 说明 |
---|
JDK 动态代理 | Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。 | CGLIB 动态代理 | 若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。 |
注意:由于被标记为 final 的方法是无法进行覆盖的,因此这类方法不管是通过 JDK 动态代理机制还是 CGLIB 动态代理机制都是无法完成代理的。
2.Spring AOP的连接点
Spring AOP 并没有像其他 AOP 框架(例如 AspectJ)一样提供了完成的 AOP 功能,它是 Spring 提供的一种简化版的 AOP 组件。其中最明显的简化就是,Spring AOP 只支持一种连接点类型:方法调用。您可能会认为这是一个严重的限制,但实际上 Spring AOP 这样设计的原因是为了让 Spring 更易于访问。
方法调用连接点是迄今为止最有用的连接点,通过它可以实现日常编程中绝大多数与 AOP 相关的有用的功能。如果需要使用其他类型的连接点(例如成员变量连接点),我们可以将 Spring AOP 与其他的 AOP 实现一起使用,最常见的组合就是 Spring AOP + ApectJ。
3.Spring AOP通知类型
AOP 联盟为通知(Advice)定义了一个 org.aopalliance.aop.Interface.Advice 接口。
Spring 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 | 在目标类中添加一些新的方法和属性。 |
4.Spring AOP的切面编程
Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。
在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。
切面类型 | 接口 | 描述 |
---|
一般切面 | org.springframework.aop.Advisor | Spring AOP 默认的切面类型。由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。 | 切点切面 | org.springframework.aop.PointcutAdvisor | Advisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。 | 引介切面 | org.springframework.aop.IntroductionAdvisor | Advisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。 |
5.一般切面的AOP开发
当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。
- 创建一个UserDao接口
public interface UserDao {
public void add();
public void delete();
public void modify();
public void get();
}
- 创建一个UserDaoImpl.类,实现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 void modify() {
System.out.println("正在执行 UserDao 的 modify() 方法……");
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的 get() 方法……");
}
}
- 创建一个UserDaoBeforeAdvice.的前置增强类
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("正在执行前置增强操作…………"+method.getName()+" ");
}
}
- 创建beans.xml
<?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/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="userdao" class="com.yc.www.UserDaoImpl"></bean>
<bean id="beforeAdvice" class="com.yc.www.UserDaoBeforeAdvice"></bean>
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userdao"></property>
<property name="proxyInterfaces" value="com.yc.www.UserDao"></property>
<property name="interceptorNames" value="beforeAdvice"></property>
</bean>
</beans>
- 创建测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserDao bean = context.getBean("userDaoProxy",UserDao.class);
bean.add();
bean.delete();
bean.get();
bean.modify();
}
- 控制台输出
正在执行前置增强操作…………add
正在执行 UserDao 的 add() 方法……
正在执行前置增强操作…………delete
正在执行 UserDao 的 delete() 方法……
正在执行前置增强操作…………get
正在执行 UserDao 的 get() 方法……
正在执行前置增强操作…………modify
正在执行 UserDao 的 modify() 方法……
Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。
ProxyFactoryBean 的常用属性如下表所示。
属性 | 描述 |
---|
target | 需要代理的目标对象(Bean) | proxyInterfaces | 代理需要实现的接口,如果需要实现多个接口,可以通过 元素进行赋值。 | proxyTargetClass | 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理 | interceptorNames | 拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。 | singleton | 返回的代理对象是否为单例模式,默认值为 true。 | optimize | 是否对创建的代理进行优化(只适用于CGLIB)。 |
6.切点切面的AOP开发
PointCutAdvisor 是 Adivsor 接口的子接口,用来表示带切点的切面。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。
Spring 提供了多个 PointCutAdvisor 的实现,其中常用实现类如如下。
- NameMatchMethodPointcutAdvisor:指定 Advice 所要应用到的目标方法名称,例如 hello* 代表所有以 hello 开头的所有方法。
- RegExpMethodPointcutAdvisor:使用正则表达式来定义切点(PointCut),RegExpMethodPointcutAdvisor 包含一个 pattern 属性,该属性使用正则表达式描述需要拦截的方法。
示例
- 创建UserDao接口
public interface UserDao {
public void add();
public void delete();
public void modify();
public void get();
}
- 创建UserDaoImpl类,实现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 void modify() {
System.out.println("正在执行 UserDao 的 modify() 方法……");
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的 get() 方法……");
}
}
- 创建OrderDaoAroundAdvice 环绕增强类
public class OrderDaoAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕增强前********");
Object result = methodInvocation.proceed();
System.out.println("环绕增强后********");
return result;
}
}
- 创建beans.xml
<?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/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="userdao" class="com.yc.www.UserDaoImpl"></bean>
<bean id="beforeAdvice" class="com.yc.www.OrderDaoAroundAdvice"></bean>
<bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns" value="com.yc.www.UserDao.add,com.yc.www.UserDao.delete"></property>
<property name="advice" ref="beforeAdvice"></property>
</bean>
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userdao"></property>
<property name="proxyInterfaces" value="com.yc.www.UserDao"></property>
<property name="interceptorNames" value="myPointCutAdvisor"></property>
</bean>
</beans>
- 创建测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserDao bean = context.getBean("userDaoProxy",UserDao.class);
bean.add();
bean.delete();
bean.get();
bean.modify();
}
- 控制台输出
环绕增强前********
正在执行 UserDao 的 add() 方法……
环绕增强后********
环绕增强前********
正在执行 UserDao 的 delete() 方法……
环绕增强后********
正在执行 UserDao 的 get() 方法……
正在执行 UserDao 的 modify() 方法……
7.自动代理
在前面的案例中,所有目标对象(Target Bean)的代理对象(Proxy Bean)都是在 XML 配置中通过 ProxyFactoryBean 创建的。但在实际开发中,一个项目中往往包含非常多的 Bean, 如果每个 Bean 都通过 ProxyFactoryBean 创建,那么开发和维护成本会十分巨大。为了解决这个问题,Spring 为我们提供了自动代理机制。
Spring 提供的自动代理方案,都是基于后处理 Bean 实现的,即在 Bean 创建的过程中完成增强,并将目标对象替换为自动生成的代理对象。通过 Spring 的自动代理,我们在程序中直接拿到的 Bean 就已经是 Spring 自动生成的代理对象了。
Spring 为我们提供了 3 种自动代理方案:
- BeanNameAutoProxyCreator:根据 Bean 名称创建代理对象。
- DefaultAdvisorAutoProxyCreator:根据 Advisor 本身包含信息创建代理对象。
- AnnotationAwareAspectJAutoProxyCreator:基于 Bean 中的 AspectJ 注解进行自动代理对象。
本节我们就通过两个简单的实例,对 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator 进行演示,至于 AnnotationAwareAspectJAutoProxyCreator,我们会在后续的教程中进行讲解。
1.根据bean名称创建代理对象
我们实现一个前置和一个后置的通知,
- 创建UserDao接口
public interface UserDao {
public void add();
public void delete();
public void modify();
public void get();
}
- 创建一个UserDaoImpl的类,实现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 void modify() {
System.out.println("正在执行 UserDao 的 modify() 方法……");
}
@Override
public void get() {
System.out.println("正在执行 UserDao 的 get() 方法……");
}
}
- 创建UserDaoafter前置通知
<?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/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="userdao" class="com.yc.www.UserDaoImpl"></bean>
<bean id="afteradvice" class="com.yc.www.UserDaoafter"></bean>
<bean id="beforeadvice" class="com.yc.www.UserDaobefore"></bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="userdao"></property>
<property name="interceptorNames" value="afteradvice,beforeadvice"></property>
</bean>
</beans>
- 创建测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserDao bean = context.getBean("userdao",UserDao.class);
bean.add();
bean.delete();
bean.get();
bean.modify();
}
- 控制台输出
实现前置通知
正在执行 UserDao 的 add() 方法……
实现后置通知
实现前置通知
正在执行 UserDao 的 delete() 方法……
实现后置通知
实现前置通知
正在执行 UserDao 的 get() 方法……
实现后置通知
实现前置通知
正在执行 UserDao 的 modify() 方法……
实现后置通知
2.根据切面中信息创建代理对象
前面内容大多都是一样的,下面只实现beans.xml
<?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/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="userdao" class="com.yc.www.UserDaoImpl"></bean>
<bean id="afteradvice" class="com.yc.www.UserDaoafter"></bean>
<bean id="beforeadvice" class="com.yc.www.UserDaobefore"></bean>
<bean id="before" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns" value="com.yc.www.UserDao.add.*,com.yc.www.UserDao.delete.*"></property>
<property name="advice" ref="beforeadvice"></property>
</bean>
<bean id="after" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns" value="com.yc.www.UserDao.add.*,com.yc.www.UserDao.delete.*,com.yc.www.UserDao.get.*"></property>
<property name="advice" ref="afteradvice"></property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
</beans>
创建测试类后输出
实现前置通知
正在执行 UserDao 的 add() 方法……
实现后置通知
实现前置通知
正在执行 UserDao 的 delete() 方法……
实现后置通知
正在执行 UserDao 的 get() 方法……
实现后置通知
正在执行 UserDao 的 modify() 方法……
15.Spring集成AspectJ
我们知道,Spring AOP 是一个简化版的 AOP 实现,并没有提供完整版的 AOP 功能。通常情况下,Spring AOP 是能够满足我们日常开发过程中的大多数场景的,但在某些情况下,我们可能需要使用 Spring AOP 范围外的某些 AOP 功能。
例如 Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。
AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。
但由于 AspectJ 支持通过 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美补充,通常情况下,我们都是将 AspectJ 和 Spirng 框架一起使用,简化 AOP 操作。
使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。
- spring-aop-xxx.jar
- spring-aspects-xxx.jar
- aspectjweaver-xxxx.jar
在以上 3 个 Jar 包中,spring-aop-xxx.jar 和 spring-aspects-xxx.jar 为 Spring 框架提供的 Jar 包,而 aspectjweaver-xxxx.jar 则是 AspectJ 提供的。
1.Spring使用AspectJ进行AOP开发(基于XML)
我们可以在 Spring 项目中通过 XML 配置,对切面(Aspect 或 Advisor)、切点(PointCut)以及通知(Advice)进行定义和管理,以实现基于 AspectJ 的 AOP 开发。
Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 aop:config 元素。
- 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 aop:config 元素中;
- 在 Spring 配置中,可以使用多个 aop:config。
- 每一个 aop:config 元素内可以包含 3 个子元素: pointcut、advisor 和 aspect ,这些子元素必须按照这个顺序进行声明。
2.引入aop命名空间
首先,我们需要在 XML 配置文件中导入 Spring aop 命名空间的约束,如下所示。
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
...
</beans>
3.定义切面<aop:aspect>
在 Spring 配置文件中,使用 <aop:aspect> 元素定义切面。该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 <aop:aspect> 之前需要先定义一个普通的 Spring Bean。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
其中,id 用来定义该切面的唯一标识名称,ref 用于引用普通的 Spring Bean。
4.定义切入点<aop:pointcut>
<aop:pointcut> 用来定义一个切入点,用来表示对哪个类中的那个方法进行增强。它既可以在 <aop:pointcut> 元素中使用,也可以在 <aop:aspect> 元素下使用。
- 当 <aop:pointcut>元素作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
- 当 <aop:pointcut> 元素作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效。
<aop:config>
<aop:pointcut id="myPointCut"
expression="execution(* net.biancheng.service.*.*(..))"/>
</aop:config>
其中,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.*.*(..))
5.定义通知
AspectJ 支持 5 种类型的 advice,如下。
<aop:aspect id="myAspect" ref="aBean">
<aop:before pointcut-ref="myPointCut" method="..."/>
<aop:after-returning pointcut-ref="myPointCut" method="..."/>
<aop:around pointcut-ref="myPointCut" method="..."/>
<aop:after-throwing pointcut-ref="myPointCut" method="..."/>
<aop:after pointcut-ref="myPointCut" method="..."/>
....
</aop:aspect>
示例
- 创建一个UserDao接口
public interface OrderDao {
public void add();
public void delete();
public int modify();
public void get();
}
- 创建UserDaoImpl类,实现UserDao接口
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() 方法");
}
}
- 创建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);
}
}
- 创建beans.xml
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" >
<bean id="orderDao" class="com.yc.www.OrderDaoImpl"></bean>
<bean id="myOrderAspect" class="com.yc.www.MyOrderAspect"></bean>
<aop:config>
<aop:pointcut id="beforePointCut" expression="execution(* com.yc.www.OrderDao.add(..))"/>
<aop:pointcut id="throwPointCut" expression="execution(* com.yc.www.OrderDao.get(..))"/>
<aop:pointcut id="afterReturnPointCut" expression="execution(* com.yc.www.OrderDao.modify(..))"/>
<aop:pointcut id="afterPointCut" expression="execution(* com.yc.www.OrderDao.*(..))"/>
<aop:aspect ref="myOrderAspect">
<aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
<aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
returning="returnValue"></aop:after-returning>
<aop:after-throwing method="afterThrow" pointcut-ref="throwPointCut"
throwing="exception"></aop:after-throwing>
<aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
<aop:around method="around" pointcut-ref="beforePointCut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
- 创建测试类
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
OrderDao orderDao = context.getBean("orderDao", OrderDao.class);
orderDao.add();
orderDao.delete();
orderDao.modify();
orderDao.get();
}
- 控制台输出
前置增强……
环绕增强---前……
正在执行 OrderDao 中的 add() 方法
环绕增强---后……
最终增强……
正在执行 OrderDao 中的 delete() 方法
最终增强……
正在执行 OrderDao 中的 modify() 方法
最终增强……
后置返回增强…… 方法返回值为:1
最终增强……
异常增强…… 异常信息为:/ by zero
16.Spring使用AspectJ进行AOP开发(基于注解)
在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。
为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。
关于注解的介绍如表 1 所示。
名称 | 说明 |
---|
@Aspect | 用于定义一个切面。 | @Pointcut | 用于定义一个切入点。 | @Before | 用于定义前置通知,相当于 BeforeAdvice。 | @AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 | @Around | 用于定义环绕通知,相当于 MethodInterceptor。 | @AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 | @After | 用于定义最终通知,不管是否异常,该通知都会执行。 | @DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握) |
1.启用 @AspectJ 注解支持
在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。
我们可以通过以下 2 种方式来启用 @AspectJ 注解。
- 使用java配置类启用
我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.yc.www")
public class AppConfig {
}
- 使用xml配置启用
<context:component-scan base-package="com.yc.www"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.定义切面 @Aspect
我们可以通过 @Aspect 注解将一个 Bean 定义为切面。
在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。
我们可以在 XML 配置中通过一些配置将这个类定义为一个 Bean,如下。
<bean id="MyOrderAspect" class="com.yc.www.MyOrderAspect" />
在定义完 Bean 后,我们只需要在Bean 对应的 Java 类中使用一个 @Aspect 注解,将这个 Bean 定义为一个切面,代码如下。
@Aspect
public class MyOrderAspect {
}
全注解方式定义切面
我们也可以在 Java 类上使用以下 2 个注解,使用全注解方式定义切面。
@Component
@Aspect
public class MyOrderAspect {
}
在以上代码中共使用两个注解:
- @Component 注解:将这个类的对象定义为一个 Bean;
- @Aspect 注解:则是将这个 Bean 定义为一个切面。
3.定义切点 @Pointcut
在 AspectJ 中,我们可以使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void,示例代码如下。
@Pointcut("execution(* com.yc.www.*.*(..))")
private void myPointCut() {
}
@Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。有关切入点表达式的具体介绍请参考《使用AspectJ实现AOP(基于XML)》中的 execution 语法格式介绍。
值得注意的是,我除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义,示例代码如下。
@Pointcut(value ="execution(* com.yc.www.UserDao.get(..))")
public void pointCut1(){
}
@Pointcut(value ="execution(* com.yc.www.UserDao.delete(..))")
public void pointCut2(){
}
@Pointcut(value ="!pointCut1() && !pointCut2()")
public void pointCut3(){
}
4.定义通知
AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。
注解 | 说明 |
---|
@Before | 用于定义前置通知,相当于 BeforeAdvice。 | @AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 | @Around | 用于定义环绕通知,相当于 MethodInterceptor。 | @AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 | @After | 用于定义最终通知,不管是否异常,该通知都会执行。 | @DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。
@Pointcut(value ="execution(* com.yc.www.UserDao.get(..))")
public void pointCut1(){
}
@Pointcut(value ="execution(* com.yc.www.UserDao.delete(..))")
public void pointCut2(){
}
@Pointcut(value ="!pointCut1() && !pointCut2()")
public void pointCut3(){
}
@Before("MyAspect.pointCut3()")
public void around() throws Throwable {
System.out.println("环绕增强……");
}
@AfterReturning(value = "execution(* com.yc.www.UserDao.get(..))" ,returning = "returnValue")
public void afterReturning(Object returnValue){
System.out.println("方法返回值为:"+returnValue);
}
示例
- 创建一个UserDao接口
public interface UserDao {
public void add();
public void delete();
public int modify();
public void get();
}
- 创建一个UserDaoImpl类,实现Userdao接口i
@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 方法");
}
}
- 创建一个名字为AppConfig的配置类
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.yc.www")
public class AppConfig {
}
- 创建一个名字为MyOrderAspect的切面类
@Component
@Aspect
public class MyOrderAspect {
@Before("execution(* com.yc.www.UserDao.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置增强……" + joinPoint);
}
@After("execution(* com.yc.www.UserDao.get(..))")
public void after(JoinPoint joinPoint) {
System.out.println("最终增强……" + joinPoint);
}
@Pointcut(value = "execution(* com.yc.www.UserDao.get(..))")
public void pointCut1() {
}
@Pointcut(value = "execution(* com.yc.www.UserDao.delete(..))")
public void pointCut2() {
}
@Around("MyOrderAspect.pointCut2()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕增强……1");
proceedingJoinPoint.proceed();
System.out.println("环绕增强……2");
}
@AfterReturning(value = "execution(* com.yc.www.UserDao.modify(..))", returning = "returnValue")
public void afterReturning(Object returnValue) {
System.out.println("后置返回增强……,方法返回值为:" + returnValue);
}
}
- 创建测试类
@Test
public void test() {
ApplicationContext context2 = new AnnotationConfigApplicationContext(AppConfig.class);
UserDao userDao = context2.getBean("userDao", UserDao.class);
userDao.add();
userDao.modify();
userDao.delete();
userDao.get();
}
- 控制台输出
前置增强……execution(void com.yc.www.UserDao.add())
正在执行 UserDao 的 add 方法
正在执行 UserDao 的 modify 方法
后置返回增强……,方法返回值为:1
环绕增强……1
正在执行 UserDao 的 delete 方法
环绕增强……2
正在执行 UserDao 的 get 方法
最终增强……execution(void com.yc.www.UserDao.get())
17.Spring JdbcTemplate(使用详解)
我们知道,JDBC 是 Java 提供的一种用于执行 SQL 语句的 API,可以对多种关系型数据库(例如 MySQL、Oracle 等)进行访问。
但在实际的企业级应用开发中,却很少有人直接使用原生的 JDBC API 进行开发,这是因为使用 JDBC API 对数据库进行操作十分繁琐,需要我们对每一步都做到“步步把控,处处关心”,例如我们需要手动控制数据库连接的开启,异常处理、事务处理、最后还要手动关闭连接释放资源等等。
Spring 提供了一个 Spring JDBC 模块,它对 JDBC API 进行了封装,其的主要目的降低 JDBC API 的使用难度,以一种更直接、更简洁的方式使用 JDBC API。
使用 Spring JDBC,开发人员只需要定义必要的参数、指定需要执行的 SQL 语句,即可轻松的进行 JDBC 编程,对数据库进行访问。
至于驱动的加载、数据库连接的开启与关闭、SQL 语句的创建与执行、异常处理以及事务处理等繁杂乏味的工作,则都是由 Spring JDBC 完成的。这样就可以使开发人员从繁琐的 JDBC API 中解脱出来,有更多的精力专注于业务的开发。
Spring JDBC 提供了多个实用的数据库访问工具,以简化 JDBC 的开发,其中使用最多就是 JdbcTemplate。
1.JdbcTemplate
JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。
JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。
方法 | 说明 |
---|
public int update(String sql)或public int update(String sql,Object… args) | 用于执行新增、更新、删除等语句;1. sql:需要执行的 SQL 语句;2. args 表示需要传入到 SQL 语句中的参数。 | public void execute(String sql)或public T execute(String sql, PreparedStatementCallback action) | 可以执行任意 SQL,一般用于执行 DDL 语句;1. sql:需要执行的 SQL 语句;2. action 表示执行完 SQL 语句后,要调用的函数。 | public List query(String sql, RowMapper rowMapper, @Nullable Object… args) 或public T queryForObject(String sql, RowMapper rowMapper, @Nullable Object… args) | 用于执行查询语句;1. sql:需要执行的 SQL 语句; 2.rowMapper:用于确定返回的集合(List)的类型; 3.args:表示需要传入到 SQL 语句的参数。 | public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) | 用于批量执行新增、更新、删除等语句;1. sql:需要执行的 SQL 语句;2. argTypes:需要注入的 SQL 参数的 JDBC 类型;3. batchArgs:表示需要传入到 SQL 语句的参数。 |
示例
下面我们就结合实例,对使用 JdbcTemplate 进行 JDBC 编程进行讲解,步骤如下。
在 MySQL 数据库中创建一个 ssmbuild数据库实例,并执行以下 SQL 语句创建一个用户信息(user)表。
CREATE TABLE `user` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户 ID',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
`status` varchar(255) DEFAULT NULL COMMENT '用户状态',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
- 在 src 目录下创建一个 jdbc.properties,并在该配置文件中对数据库连接信息进行配置。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/ssmbuild
jdbc.username=root
jdbc.password=root
- 在 src 目录下创建一个 XML 配置文件 Beans.xml,配置内容如下。
<?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/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com,yc.www"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
在以上配置中,我们共定义了两个 Bean,
- dataSource 为数据库连接池对象的 Bean。
- jdbcTemplate 则为 JdbcTemplate 的 Bean,它由一个名为 datasSource 的属性。
Spring 默认使用 DriverManagerDataSource 对数据库连接池进行管理,我们可以在 Spring 的 XML 配置文件中定义 DriverManagerDataSource 的 Bean,并注入到 JdbcTempate 的 Bean 中。
在 dataSource 中,定义了 4 个连接数据库的属性,如下表所示。
属性名 | 说明 |
---|
driverClassName | 所使用的驱动名称,对应驱动 JAR 包中的 Driver 类 | url | 数据源所在地址 | username | 访问数据库的用户名 | password | 访问数据库的密码 |
上表中的属性值需要根据数据库类型或者机器配置的不同进行相应设置。如果数据库类型不同,则需要更改驱动名称;如果数据库不在本地,则需要将 localhost 替换成相应的主机 IP。
示例
- 创建一个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 + '\'' +
'}';
}
}
- 创建UserDao接口
public interface UserDao {
int addUer(User user);
int update(User user);
int delete(User user);
int count(User user);
List<User> getList(User user);
User getUser(User user);
void batchAddUser(List<Object[]> batchArgs);
}
- 创建UserDaoImpl类,实现UserDao接口
@Repository
public class UserDaoImpl implements UserDao {
@Autowired(required = false)
private JdbcTemplate jdbcTemplate;
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<User> getList(User user) {
String sql = "SELECT * FROM `user` where `status`=?;";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(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>(User.class), user.getUserId());
}
@Override
public void batchAddUser(List<Object[]> batchArgs) {
String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
jdbcTemplate.batchUpdate(sql, batchArgs);
}
}
- 创建UserService接口
public interface UserService {
public int addUser(User user);
public int updateUser(User user);
public int deleteUser(User user);
public int countUser(User user);
public List<User> getUserList(User user);
public User getUser(User user);
public void batchAddUser(List<Object[]> batchArgs);
}
- 创建UserServiceImpl类,实现UserService接口
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
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<User> getUserList(User user) {
return userDao.getList(user);
}
@Override
public User getUser(User user) {
return userDao.getUser(user);
}
@Override
public void batchAddUser(List<Object[]> batchArgs) {
userDao.batchAddUser(batchArgs);
}
}
- 创建测试类
public void test() {
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<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"小明", "在线"};
Object[] o2 = {"小龙", "离线"};
Object[] o3 = {"小林", "在线"};
Object[] o4 = {"小李", "在线"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
batchArgs.add(o4);
userService.batchAddUser(batchArgs);
System.out.println("批量增加完毕");
User user2 = new User();
user2.setStatus("在线");
int i1 = userService.countUser(user2);
System.out.println("在线用户的个数为:" + i1);
List<User> userList = userService.getUserList(user2);
System.out.println("在线用户列表查询成功!");
for (User user4 : userList) {
System.out.println("用户 ID:" + user4.getUserId() + ",用户名:" + user4.getUserName() + ",状态:" + user4.getStatus());
}
}
- 控制台输出
新增用户成功!
修改用户成功
批量增加完毕
在线用户的个数为:4
在线用户列表查询成功!
用户 ID:11,用户名:小张,状态:在线
用户 ID:12,用户名:小明,状态:在线
用户 ID:14,用户名:小林,状态:在线
用户 ID:15,用户名:小李,状态:在线
18.spring事务
事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。
事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。
- 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
- 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
- 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
- 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。
事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有的操作都执行成功,那自然万事大吉。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。
在现实世界中,最常见的与事务相关的例子可能就是银行转账了。假设我们需要将 1000 元从 A 账户中转到 B 账户中,这个转账操作共涉及了以下两个操作。
- 从 A 账户中扣除 1000 元;
- 往 B 账户中存入 1000 元。
如果 A 账户成功地扣除了 1000 元,但向 B 账户存入时失败的话,那么我们将凭空损失 1000 元;如果 A 账户扣款时失败,但却成功地向 B 账户存入 1000 元的话,我们的账户就凭空多出了 1000 元,那么银行就会遭受损失。因此我们必须保证事务中的所有操作要么全部成功,要么全部失败,理解了这一点,我们也就抓住了事务的核心。
作为一款优秀的开源框架和应用平台,Spring 也对事务提供了很好的支持。Spring 借助 IoC 容器强大的配置能力,为事务提供了丰富的功能支持。
1.事务管理方式
Spring 支持以下 2 种事务管理方式。
事务管理方式 | 说明 |
---|
编程式事务管理 | 编程式事务管理是通过编写代码实现的事务管理。这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。 | 声明式事务管理 | Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。 |
选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。
- 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
- 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。
2.事务管理
Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。
在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。
public interface User extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
该接口中各方法说明如下:
方法 | 说明 |
---|
getTransaction | 用于获取事务的状态信息 | commit | 提交事务 | rollback | 回滚事务 |
19.Spring基于XML实现事务管理
Spring 声明式事务管理是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建(或加入)一个事务,在执行完目标方法后,根据执行情况提交或者回滚事务。
声明式事务最大的优点就是对业务代码的侵入性低,可以将业务代码和事务管理代码很好地进行解耦。
Spring 实现声明式事务管理主要有 2 种方式:
- 基于 XML 方式的声明式事务管理。
- 通过 Annotation 注解方式的事务管理。
1.引入tx命名空间
Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。
<?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/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
**注意:**由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。
2.配置事务管理器
接下来,我们就需要借助数据源配置,定义相应的事务管理器实现(PlatformTransactionManager 接口的实现类)的 Bean,配置内容如下。
<?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/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.yc.www"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
在以上配置中,配置的事务管理器实现为 DataSourceTransactionManager,即为 JDBC 和 iBatis 提供的 PlatformTransactionManager 接口实现。
3.配置事务通知
在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。
<tx:advice id="tx-advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
</tx:attributes>
</tx:advice>
事务管理器配置
当我们使用 <tx:advice> 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。
如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。
<tx:advice id="tx-advice" >
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
</tx:attributes>
</tx:advice>
但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 tx:advice 元素中通过 transaction-manager 参数指定。
事务属性配置
对于<tx:advice> 来说,事务属性是被定义在<tx:attributes> 中的,该元素可以包含一个或多个 <tx:method> 元素。
<tx:method> 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。
事务属性 | 说明 |
---|
propagation | 指定事务的传播行为。 | isolation | 指定事务的隔离级别。 | read-only | 指定是否为只读事务。 | timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 | rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 | no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
4.配置切点切面
<tx:advice> 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 tx:advice 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。
在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。
<aop:config>
<aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
<aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
</aop:config>
在以上配置中用到了 aop 命名空间,这就是我们为什么在给工程导入依赖时要引入 spring-aop 等 Jar 包的原因。
示例
- 首先创建一个数据库,在数据库中添加以下表
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');
- 创建Order的实体类
public class Order {
private Long id;
private String orderId;
private String userId;
private String productId;
private Integer count;
private BigDecimal money;
private Integer status;
}
- 创建Account 的实体类
public class Account {
private Long id;
private String userId;
private BigDecimal total;
private BigDecimal used;
private BigDecimal residue;
}
- 创建Storage的实体类
public class Storage {
private Long id;
private String productId;
private Integer total;
private Integer used;
private Integer residue;
}
- 创建OrderDao接口
public interface OrderDao {
int createOrder(Order order);
void updateOrderStatus(String orderId, Integer status);
}
- 创建AccountDao接口
public interface AccountDao {
Account selectByUserId(String userId);
int decrease(String userId, BigDecimal money);
}
- 创建StorageDao接口
public interface StorageDao {
Storage selectByProductId(String productId);
int decrease(Storage record);
}
- 创建的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);
}
}
- 创建 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>(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);
}
}
- 创建 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>(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());
}
}
- 创建一个名为 OrderService 的接口
public interface OrderService {
public void createOrder(Order order);
}
- 创建 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) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
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("修改订单状态完成!");
}
}
- beans.xml
<?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/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.yc.www"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="tx-pt" expression="execution(* com.yc.www.OrderServiceImpl.*(..))"/>
<aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
</aop:config>
</beans>
- 创建测试类
@Test
public void test() {
ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
OrderService orderService = context2.getBean("orderService", OrderService.class);
Order order = new Order();
order.setProductId("1");
order.setCount(1);
order.setMoney(new BigDecimal(6));
order.setUserId("1");
order.setStatus(0);
orderService.createOrder(order);
}
20.Spring基于注解实现事务管理
? 我们通过 tx:advice 元素极大的简化了 Spring 声明式事务所需的 XML 配置。但其实我们还可以通过另一种方式进行进一步的简化,那就是“使用注解实现事务管理”。
在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面我们就来介绍下,通过注解是如何实现声明式事务管理。
1.开启注解事务
tx 命名空间提供了一个 <tx:annotation-driven> 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。
<tx:annotation-driven> 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
与 <tx:advice> 元素一样,<tx:annotation-driven> 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。
<tx:annotation-driven/>
通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。
2.使用 @Transactional 注解
@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 | 指定事务对于那些异常应当继续运行,而不回滚。 |
示例
根据上面的xml配置事务进行修改
- 修改OrderServiceImpl里的createOrder方法
@Service("orderService")
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private AccountDao accountDao;
@Autowired
private StorageDao storageDao;
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, timeout = 10, readOnly = false)
@Override
public void createOrder(Order order) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
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("修改订单状态完成!");
}
}
- 修改beans.xml
<?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/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<tx:annotation-driven/>
<context:component-scan base-package="com.yc.www"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
21.Spring整合日志框架Log4j2
对于一款软件而言,日志记录都是十分重要的。它不仅能够监控程序的运行情况,周期性的记录到文件中,还能够跟踪程序中代码的运行轨迹,向文件或控制台打印代码的调试信息。当程序出现错误时,日志记录可以帮助开发人员及时定位问题,因此对开发人员来说,日志记录更是尤为重要。
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 版本。 |
1.Spring 整合 Log4j2
Spring 5 是基于 Java 8 实现的,其自身作了不少的优化,将许多不建议使用的类和方法从代码库中删除,其中就包括了 Log4jConfigListener(Spring 加载 Log4j 配置的监听器)。因此从 Spring 5 开始就不在支持对 Log4j 的整合,而更加推荐我们通过 Log4j2 进行日志记录。
我们根据自身操作系统的不同,选择不同的压缩包
- apache-log4j-2.xx.x-bin.zip:为 Log4j2 为 Windows 系统提供的的压缩包。
- apache-log4j-2.xx.x-bin.tar.gz:为 Log4j2 为 linux、MacOsX 系统提供的压缩包。
特别注意:由于 Log4j2 在 2021 年 12 月 10 日被曝存在远程代码执行漏洞,所有 Apache Log4j 2.x <= 2.14.1 版本均受到影响。随后,Log4j2 官方对此漏洞进行了了修复,因此我们在引入 Log4j2 的依赖时,尽量选择最新版本。编写本教程时,Log4j2 的最新版本为 Log4j 2.17.1,因此这里就以此版本为例进行讲解。
对下载完成的压缩包进行解压,并将以下 3 个依赖包导入到项目中。
- log4j-api-2.17.1.jar
- log4j-core-2.17.1.jar
- log4j-slf4j18-impl-2.17.1.jar
此外,我们还需要向项目中导入一个 slf4j-api-xxx.jar ,但该依赖包的版本是有限制的。
此前,我们下载的 Log4j2 的依赖包中有一个 log4j-slf4j18-impl-2.17.1.jar,它是 Log4j2 提供的绑定到 SLF4J 的配置器。
Log4j2 提供了以下 2 个适配器:
- log4j-slf4j-impl 应该与 SLF4J 1.7.x 版本或更早版本一起使用。
- log4j-slf4j18-impl 应该与 SLF4J 1.8.x 版本或更高版本一起使用。
|