Spring
文章目录
请点击这里 获取本文代码
Spring概览
什么是spring?
-
spring是一个框架,核心技术是ioc,aop,实现解耦合
-
spring也是一个容器, 容器中存放的是java对象,需要做的是把对象放入容器中
怎么使用spring?
-
spring是一个容器,把项目中要用的对象放入容器中.
-
让容器完成创建对象,对象之间关系的管理(属性赋值)
-
我们在程序中之间从容器中获取要使用的对象
什么样的对象放入容器中?
- dao类,service类,controller类型,工具类
- spring中的对象默认都是单例的,在容器中叫这个名称的对象只有一个
不放入spring容器中的对象
- 实体类对象, 实体类数据来自数据库.
- servlet,listener,filter等.
第一章 Spring 概述
spring: 出现在2002年左右,解决企业开发的难度.减轻对项目模块之间的管理,类和类之间的管理,帮助开发人员创建对象,管理对象之间的关系.
Spring的核心技术, ioc ,aop.能实现模块之间,类之间的解耦合.
依赖: class A 中使用Class B 的属性或者方法,叫做 class A依赖class B
Spring的优点
-
轻量
Spring 框架运行占用的资源少,运行效率高.不依赖其他jar.
-
针对接口编程,解耦合.
Spring 提供了Ioc控制反转, 由容器管理对象,对象的依赖关系. 原来在程序代码中的对象创建方式,现在由容器完成, 对象之间的依赖解耦合.
-
aop编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程, 许多不容易使用 传统oop实现的功能 都可以通过 AOP 轻松应对
在Spring中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务管理, 提高开发效率和质量.
-
方便集成各种优秀框架
可以将spring和其他框架一起使用
Spring体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理(Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)
第二章 IOC 控制反转
IOC (Inversion Of Control) : 控制反转
Ioc是一个理论,思想.描述的是 : 把对象的创建,赋值,管理工作都交给代码之外的容器实现. 也就是对象的创建是由其他外部资源完成的.
控制: 创建对象,给对象的属性赋值,对象之间的关系管理.
反转:把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现.由容器代替开发人员管理对象.创建对象,给属性赋值.
正转: 由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象.
public static void main(String args[]){
Student student = new Student();// 在代码中,创建对象.-- 正转
}
容器:是一个服务器软件,一个框架(Spring)
为什么使用ioc: 目的是减少对代码的改动,也能实现不同的功能.也就是实现解耦合.ioc能够实现业务对象之间的解耦合,例如service和dao对象之间的解耦合
java中创建对象的方式
-
构造方法, new Student()
-
反射
-
序列化
-
克隆
-
ioc : 容器创建对象
-
动态代理
ioc的体现
比如: servlet
-
创建类继承 HttpServlet
-
在web.xml中注册servlet,
<servlet-name>myservlet</servlet-name> <servlet-class>com.xxx.MyServlet</servlet-class>
-
没有创建Servlet对象, 即没有 MyServlet myServlet = new MyServlet()
-
Servlet 是Tomcat 服务器创建的, Tomcat也称为容器.
Tomcat作为容器,里面存放的有Servlet对象,Listener,Filter对象.
IOC的技术实现
IOC的技术实现 是 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行 完成。即只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建,赋值,查找都由容器内部实现.
Spring是使用DI实现了ioc的功能, spring是一个容器,管理对象,给属性赋值.底层是反射创建对象.
基于配置文件的DI
Spring创建对象实现步骤
-
创建maven项目
-
加入maven的依赖
spring的依赖 和 junit依赖
pom.xml
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> </dependencies>
-
创建类(接口类和他的实现类)
和没有使用框架一样,就是一个普通的类型
public interface SomeService { void doSome(); }
public class SomeServiceImpl implements SomeService { public SomeServiceImpl(){ System.out.println("SomeServiceImpl的构造方法执行了"); } @Override public void doSome() { System.out.println("执行SomeServiceImpl的doSome()方法"); } }
-
创建spring需要使用的配置文件
声明类的信息,这些类由spring创建和管理
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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 告诉Spring创建对象 声明bean,就是告诉spring要创建某个类的对象. id:对象的自定义名称,唯一值.spring通过这个名称找到对象. class:类的全限定名称,(不能是接口,因为spring是反射机制创建对象的.必须使用类) spring 就完成 SomeService someService = new SomeServiceImpl(); spring 是把创建好的对象放入到map中,spring框架有一个map存放对象的. springMap.put(id的值,对象); springMap.put("someService",new SomeServiceImpl()); 一个bean标签声明一个java对象. --> <bean id="someService" class="com.nguyenxb.service.impl.SomeServiceImpl"/> <bean id="someService1" class="com.nguyenxb.service.impl.SomeServiceImpl"/> <bean id="mydate" class="java.util.Date"/> </beans> <!-- spring的配置文件 1,beans: 是根标签,spring把java对象称为bean. 2.spring-beans.xsd 是约束文件, 和mybatis指定 dtd是一样的. -->
测试spring创建的对象是否成功
测试1 , 手工创建对象
@Test
public void test01(){
SomeService someService = new SomeServiceImpl();
someService.doSome();
}
/*
输出:
SomeServiceImpl的构造方法执行了
执行SomeServiceImpl的doSome()方法
*/
测试2, 使用spring创建对象
@Test
public void test02(){
// 使用spring 容器创建的对象
// 1. 指定spring配置文件的名称
String config = "beans.xml";
// 2. 创建表示spring 容器的对象. ApplicationContext
// ApplicationContext就是表示spring容器,就能通过容器获取对象了.
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 3. 从容器获取某个对象,你要调用对象的方法.
// getBean("配置文件中bean的id的值")
SomeService someService = (SomeService) ac.getBean("someService");
// 4. 使用spring 创建好的对象
someService.doSome();
}
/*
SomeServiceImpl的构造方法执行了
SomeServiceImpl的构造方法执行了
执行SomeServiceImpl的doSome()方法
*/
测试3, 获取spring容器中, java对象的信息
/**
* 获取spring容器中, java对象的信息
* */
@Test
public void test03(){
String config = "beans.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 使用spring提供的方法, 获取容盎中定义的对象的数量.
int nums = ac.getBeanDefinitionCount();
System.out.println("容器中定义的对象数量:"+nums);
// 容器中每个定义的对象的名称
String[] names = ac.getBeanDefinitionNames();
for (String name: names){
System.out.println("对象名称:"+name);
}
}
测试4, 创建非自定义的对象
// 创建非自定义的对象
@Test
public void test04(){
String config = "beans.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 使用getBean();
Date mydate = (Date) ac.getBean("mydate");
System.out.println("Date:"+mydate);
}
小结
spring 默认创建对象的时间: 在创建spring的容器时,会创建配置文件中的所有对象. spring 创建对象, 默认调用的是无参数构造方法.
用spring, 给java对象属性赋值(注入)
spring默认调用java对象的无参数构造方法.
di: 依赖注入,表示创建对象,给属性赋值.
di实现的语法有两种:
- 在spring的配置文件中,使用标签和属性完成赋值, 叫做基于xml的di实现.
- 使用spring中注解,完成属性赋值,叫做基于注解的di实现.
di语法分类:
- set注入(设置注入): spring调用类的set方法,在set方法可以实现属性的赋值.80%都是使用set注入的.
- 构造注入,spring调用类的有参数构造方法,创建对象,在构造方法中完成赋值.
项目文件目录:
项目根目录
|--src
|--main
| |--java
| | |--com
| | |--nguyenxb
| | |--ba01
| | |--自定义的类 如Student.java
| | |--ba02
| | |--自定义的类 如Student.java
| | ....
| | |--baxx
| | |--自定义的类 如Student.java
| |--resources
| |--ba01 (不同的知识点使用不同包下的spring的配置文件)
| |--applicationContext.xml
| |--ba02 (不同的知识点使用不同包下的spring的配置文件)
| |--applicationContext.xml
| ....
| |--baXX (不同的知识点使用不同包下的spring的配置文件)
| |--applicationContext.xml
|--test (存放测试方法的目录)
|--java
|--com
|--nguyenxb
|--MyTest01.java
|--MyTest02.java
...
|--MyTestxx.java
java文件一律放在java目录下的响应包中,applicationContext.xml一律放在resources目录下的相应目录下.并且java包,资源包的编号相对应.
set注入
注入就是赋值的意思
set注入 (设值注入): spring调用类的set方法, 可以在set方法中完成赋值.注意: 必需有set方法,否则spring会报错
简单类型的set注入
简单类型: spring中规定java的基本数据类型和String都是简单类型
di: 给属性赋值
简单类型的set注入语法:
<bean id="xxx" class="yyy">
<property name="属性名字" value="此属性的值"/>
一个property只能给一个属性赋值.如果要给多个属性赋值,只能用多个property标签
</bean>
ba01\applicationContext.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.xsd">
<!-- 声明student对象
-->
<bean id="mystudent" class="com.nguyenxb.ba01.Student">
<property name="name" value="李四"/><!--setName("李四")-->
<property name="age" value="20"/><!--setAge(20)-->
</bean>
</beans>
ba01\Student.java
public class Student {
private String name;
private int age;
// 已省略set|get|toString方法;
}
测试
@Test
public void test01(){
String config = "ba01/applicationContext.xml";
// 创建spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取对象
Student mystudent = (Student) ac.getBean("mystudent");
System.out.println("student对象="+mystudent);
}
// 输出:student对象=Student{name='李四', age=20}
引用类型的set注入
引用类型的set注入 : spring 调用类的set方法.
语法:
<bean id="xxx" class="yyy">
<property name="属性名字" ref="bean的id(对象的名称)"/>
</bean>
ba02\applicationContext.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.xsd">
<bean id="mystudent" class="com.nguyenxb.ba02.Student">
<property name="name" value="李四"/><!--setName("李四")-->
<property name="age" value="20"/><!--setAge(20)-->
<!-- 引用类型赋值-->
<property name="school" ref="myschool"/>
</bean>
<!-- 声明School对象-->
<bean id="myschool" class="com.nguyenxb.ba02.School">
<property name="name" value="xx大学"/>
<property name="address" value="xx大学路"/>
</bean>
</beans>
ba02\Student.java
public class Student {
private String name;
private int age;
// 声明一个引用类型
private School school;
// 已省略set|get|toString方法;
}
ba02\School.java
public class School {
private String name;
private String address;
// 已省略set|get|toString方法;
}
测试
@Test
public void test02(){
String config = "ba02/applicationContext.xml";
// 创建spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取对象
Student mystudent = (Student) ac.getBean("mystudent");
System.out.println("student对象="+mystudent);
}
/*输出:
student对象=Student{name='李四', age=20, school=School{name='xx大学', address='xx大学路'}}
*/
构造注入
构造注入: spring调用类的有参数构造方法,在创建对象的同时,在构造方法中给属性赋值.
构造注入使用<constructor-arg>
标签
<constructor-arg>
标签:一个<constructor-arg>
表示构造方法的一个参数.
<constructor-arg>
标签属性:
name: 表示构造方法的形参名
index:表示构造方法的参数的位置, 参数从左往右位置是0,1,2…的顺序.
value:构造方法的形参类型是简单类型的,使用value.
ref: 构造方法的形参类型是引用类型的,使用ref.
构造注入可以通过构造方法的参数名,构造方法的参数位置实现创建对象.并且这两种方式不用考虑赋值的顺序, 而省略构造方法的参数位置必须从上到下依次赋值给 0…n的位置.
ba03\applicationContext.xml
<!-- 使用name属性实现构造方法-->
<bean id="mystudent" class="com.nguyenxb.ba03.Student">
<constructor-arg name="myname" value="张三"/><!--setName("李四")-->
<constructor-arg name="myage" value="23"/><!--setAge(20)-->
<!-- 引用类型赋值 -->
<constructor-arg name="myschool" ref="myschool11"/>
</bean>
<!-- 使用index属性实现构造方法-->
<bean id="mystudent2" class="com.nguyenxb.ba03.Student">
<constructor-arg index="0" value="张1"/>
<constructor-arg index="1" value="12"/>
<constructor-arg index="2" ref="myschool11"/>
</bean>
<!-- 省略index属性实现构造方法: 默认从上到下是 0 , 1, 2 的参数位置-->
<bean id="mystudent3" class="com.nguyenxb.ba03.Student">
<constructor-arg value="周瑜"/>
<constructor-arg value="18"/>
<constructor-arg ref="myschool11"/>
</bean>
<!-- 声明School对象-->
<bean id="myschool11" class="com.nguyenxb.ba03.School">
<property name="name" value="03xx大学"/>
<property name="address" value="03xx大学路"/>
</bean>
ba03\School.java
public class School {
private String name;
private String address;
// 已省略set|get|toString方法;
}
ba03\Student.java
public class Student {
private String name;
private int age;
// 声明一个引用类型
private School school;
public Student(String myname,
int myage, School myschool) {
this.name = myname;
this.age = myage;
this.school = myschool;
}
// 已省略set|get|toString方法;
}
测试:
@Test
public void test03(){
String config = "ba03/applicationContext.xml";
// 创建spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取对象, 使用name属性创建的对象
Student mystudent = (Student) ac.getBean("mystudent");
System.out.println("使用name创建student对象="+mystudent);
// 从容器中获取对象,使用index属性创建的对象
Student mystudent2 = (Student) ac.getBean("mystudent2");
System.out.println("使用index创建student对象="+mystudent2);
// 从容器中获取对象,直接省略index而创建的对象
Student mystudent3 = (Student) ac.getBean("mystudent3");
System.out.println("省略index创建student对象="+mystudent3);
}
/*
student对象=Student{name='张三', age=23, school=School{name='03xx大学', address='03xx大学路'}}
student对象=Student{name='张1', age=12, school=School{name='03xx大学', address='03xx大学路'}}
student对象=Student{name='周瑜', age=18, school=School{name='03xx大学', address='03xx大学路'}}
*/
小案例
在 src\main\resources\ba04目录下新建一个haha.txt文件,通过构造注入的方式获取文件对象.
ba04\applicationContext.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.xsd">
<!-- 使用name属性实现构造方法-->
<bean id="myfile" class="java.io.File">
<constructor-arg name="parent" value="src\main\resources\ba04"/>
<constructor-arg name="child" value="haha.txt"/>
</bean>
</beans>
测试
@Test
public void test04(){
String config = "ba04/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
File myfile = (File) ac.getBean("myfile");
System.out.println("文件名称:"+myfile.getName());
}
//输出: 文件名称:haha.txt
自动注入
引用类型的自动注入: spring框架根据某些规则库给引用类型赋值.不用手动赋值了.使用的常用规则是byName,byType.
byName
byName(按照名字注入) : java 类中引用类型的属性名和spring容器中(配置文件)<bean>标签的id名称一样,并且数据数据类型是一样的,这样spring就能够自动赋值给<bean>标签中的引用类型.
语法规则:
<bean id="xx" class="yyy" autowire="byName>
简单类型属性赋值
</bean>
School.java
public class School {
private String name;
private String address;
// 已省略set|get|toString方法;
}
Student.java
public class Student {
private String name;
private int age;
// 声明一个引用类型
private School school;
// 已省略set|get|toString方法;
}
applicationContext.xml
<!-- 使用name属性实现构造方法-->
<bean id="mystudent" class="com.nguyenxb.ba05.Student" autowire="byName">
<property name="name" value="张三"/>
<property name="age" value="28"/>
</bean>
<!-- 声明School对象-->
<bean id="school" class="com.nguyenxb.ba05.School">
<property name="name" value="05xx大学"/>
<property name="address" value="05xx大学路"/>
</bean>
<!--如上诉例子中, School的对象名为school,那么Student的属性名中必须要有一个叫school的属性名,并且属性的类型为School -->
测试:
@Test
public void test05(){
String config = "ba05/applicationContext.xml";
// 创建spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取对象, 使用name属性创建的对象
Student mystudent = (Student) ac.getBean("mystudent");
System.out.println("student对象="+mystudent);
}
/*输出:
student对象=Student{name='张三', age=28,school=School{name='05xx大学', address='05xx大学路'}}
*/
byType
byType(按类型注入) : java类中的引用类型和spring容器中(配置文件)<bean>的
class属性是同源关系,这样的bean能够赋值给引用类型.
同源就是一类的意思:
1. java类中的引用类型的数据类型和bean的class的值是一样的.
2. java类中的引用类型的数据类型和bean的class的值是父子类关系的.
3. java类中的引用类型的数据类型和bean的class的值接口是实现类关系的.
语法:
<bean id="xx" class="yyy" autowire="byType>
简单类型属性赋值
</bean>
注意: 在byType中,xml配置文件中声明bean只能有一个符合条件的,多于一个就会发生错误.
applicationContext.xml
<!--byType -->
<bean id="mystudent" class="com.nguyenxb.ba06.Student" autowire="byType">
<property name="name" value="张三asdasd"/>
<property name="age" value="288"/>
</bean>
<!-- 声明School对象-->
<!-- <bean id="MySchool" class="com.nguyenxb.ba06.School">-->
<!-- <property name="name" value="06xx大学"/>-->
<!-- <property name="address" value="06xx大学路"/>-->
<!-- </bean>-->
<!-- School的子类-->
<bean id="primarySchool" class="com.nguyenxb.ba06.PrimarySchool">
<property name="name" value="06xx小学"/>
<property name="address" value="06xx小学路"/>
</bean>
<!--实现类关系同理-->
@Test
public void test06(){
String config = "ba06/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
Student myStudent = (Student) ac.getBean("mystudent");
System.out.println(myStudent);
}
多个配置文件
优点:
- 每个文件的大小比一个文件要小很多,效率高.
- 避免多人操作同一个文件而产生冲突.
多文件的分配方式
- 按照功能模块,一个功能模块一个配置文件.
- 按照类的功能,比如数据库相关的配置一个配置文件,事务相关的一个配置文件等等.
- spring支持两种加载其他配置文件的方式,第一种是文件列表加载,第二种是使用通配符加载.
ba07/spring-student.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.xsd">
<!-- 学生模块:所有bean的声明-->
<bean id="mystudent" class="com.nguyenxb.ba07.Student" autowire="byType">
<property name="name" value="张三asdasd"/>
<property name="age" value="288"/>
</bean>
</beans>
ba07/spring-school.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.xsd">
<!-- 声明School对象-->
<bean id="MySchool" class="com.nguyenxb.ba07.School">
<property name="name" value="06xx大学"/>
<property name="address" value="06xx大学路"/>
</bean>
</beans>
ba07/total.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.xsd">
<!-- spring-total 表示主配置文件:
作用: 用来包含其他的配置文件的,主配置文件一般是不定义对象的
语法: <import resource="其他配置文件的路径"/>
关键字: "classpath":表示类路径(class文件所在的目录)
在spring的配置文件中要指定其他文件的位置,需要使用classpath,
告诉spring去哪里加载读取文件.
-->
<!-- 加载方式一 : 加载的是文件列表-->
<!-- <import resource="classpath:ba07/spring-school.xml"/>-->
<!-- <import resource="classpath:ba07/spring-student.xml"/>-->
<!-- 加载方式二: 在包含关系的配置文件中,可以使用通配符(*:表示任意字符)
注意:主配置文件名称不能包含在通配符的范围内(比如不能叫做spring-total.xml)
使用通配符的文件必须在同一个目录中,否则会读取不到
-->
<import resource="classpath:ba07/spring-*.xml"/>
</beans>
测试
@Test
public void test07(){
String config = "ba07/total.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
Student myStudent = (Student) ac.getBean("mystudent");
System.out.println(myStudent);
}
基于注解的DI
通过注解完成java对象的创建,属性赋值.
使用注解的步骤:
- 加入maven的依赖 spring-context, 在你加入spring-context的同时,会间接加入spring-aop的依赖,使用注解必须使用spring-aop依赖.
- 在类中加入spring的注解(多个不同功能的注解)
- 在spring的配置文件中,加入一个注解扫描器的标签,说明注解在你的项目的位置.
如何通过spring的注解完成对象的创建,属性赋值.代替xml文件.
实现步骤:
- 加入依赖
- 创建类,在类中加入注解
- 创建spring配置文件,声明组件扫描器的标签,指明注解在项目中的位置.
- 使用注解创建对象, 创建容器ApplicationContext
学习的注解:
- @Component
- @Respotory
- @Service
- @Controller
- @Value
- @Autowired
- @Resource
如何配置注解扫描器
<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 声明 组件扫描器(component-scan), 组件就是java对象
base-package: 指定注解在你的项目的包名.
component-scan工作方式:spring会扫描遍历base-package指定的包,
把包和子包中的所有类,找到类中的注解,安装注解的功能创建对象,或者给属性赋值.
加入了 component-scan标签,配置文件的变化:
1.加入了一个新的约束文件 spring-context.xsd
2.给这个新的约束文件起了一个命名空间的名称 context
而 context 代表的是 http://www.springframework.org/schema/context
而该地址指向了一个 https://www.springframework.org/schema/context/spring-context.xsd 文件
-->
<!-- 指定多个包的三种方式-->
<!-- 第一种方式: 使用多次组件扫描器,扫描不同的包-->
<context:component-scan base-package="com.nguyenxb.ba01"/>
<context:component-scan base-package="com.nguyenxb.ba02"/>
<!-- 第二种方式: 使用分隔符(;或者,)分隔多个包名-->
<context:component-scan base-package="com.nguyenxb.ba01;com.nguyenxb.ba02"/>
<!-- 第三种方式:指定父包,就是把父包里面所有的注解都扫描完-->
<context:component-scan base-package="com.nguyenxb"/>
</beans>
如何使用注解创建对象
@Component: 创建对象的 等同于<bean>的功能
属性: value 就是对象的名称, 也就是bean的id值
value的值是唯一的.创建的对象在整个spring容器中就一个
写法位置: 写在类的上面
1. @component(value="myStudent") 等同于
<bean id="myStudent" class="com.nguyenxb.ba01.Student"></bean>
2. 可以将value省略即: @component("myStudent")
3.也可以使用spring的默认注解 @component ,生成的对象名就是student,即类的首字母是小写,等价于:
<bean id="student" class="com.nguyenxb.ba01.Student"></bean>
spring中和@component功能一致,创建对象的注解还有:
1.@Repository(用在持久层类的上面): 放在dao的实现类上面
表示创建dao对象,dao对象是能访问数据库的.
2.@Service(用在业务层类的上面):放在service的实现类上面,
表示创建service对象,service对象是做业务处理,可以有事务等功能的.
3.@Controller(用在控制器的上面):放在控制器(处理器的上面)
表示创建控制器对象,能够接受用户提交的参数,显示请求的处理结果.
以上三个注解的使用语法和@Component一样的.都能创建对象,但是这三个注解还有额外的功能. @Repository,@Service,@Controller是给项目的对象分层的.都是创建对象的注解,但是他们的功能都是不一样的.
如何使用注解给对象赋值
给简单类型赋值
@value : 简单类型的属性赋值
属性: value 是String类型的,表示简单类型的属性值
位置: 1.在属性定义的上面,无需set方法,推荐使用.
2.在set方法的上面
给引用类型赋值
第一种方式 :
@Autowired: spring 中通过注解给引用类型赋值, 使用的是自动注入的原理.支持 byName, byType
@Autowired:默认使用的是byType自动注入
@Autowired 的属性: required, 是一个boolean 类型的,默认true
required=true : 表示引用类型赋值失败,程序报错,并终止执行
required=false: 表示 引用类型如果赋值失败.程序正常执行,引用类型是null
用法: @Autowired(required=true)
@Autowired:默认使用的是byType自动注入
位置: 1.在属性定义的上面,无需set方法,推荐使用
2.在set方法上面
如果要使用byName方式,需要做的是:
1.在属性上面加入 @Autowired
2.在属性上面加入@Qualifier(value="bean的id"):
表示使用指定名称的bean完成赋值.
第二种方式:
@Resource: 来自jdk中的注解,sprin框架提供了对这个注解的功能支持,可以使用它给引用类型赋值使用的也是自动注入原理,支持byName,byType.默认是byName.
位置: 1.在属性定义的上面,无需set方法,推荐使用
2.在set方法上面
工作原理: 先使用byName自动注入,如果byName注入失败,再使用byType
使用方式: @Resource
如何让@Resource只使用byName方式?
需要增加一个属性,name的值是 bean的id(名称)
如: @Resource(name="mySchool")//如果找不到,直接报错
@Autowried与@Value和Qualifier的注解综合案例:
通过注解创建一个学生类,并且给引用类型赋值
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 使用多次组件扫描器,扫描对应的包下的注解-->
<context:component-scan base-package="com.nguyenxb.ba03"/>
</beans>
School类
@Component("mySchool")
//@Component
public class School {
@Value("03xxx学校")
private String name;
@Value("03xxx地址")
private String address;
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student类
@Component("myStudent")
public class Student {
@Value("张三")// 在属性上使用注解赋值
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
/*
* 如果要使用byName方式,需要做的是:
* 1.在属性上面加入 @Autowired
* 2.在属性上面加入@Qualifier(value="bean的id"):
* 表示使用指定名称的bean完成赋值.
* */
// 两个没有先后顺序, 按名称自动赋值
@Autowired
@Qualifier("mySchool")// 即把这个注释掉了就是默认的byType,
private School school;
@Value("24")// 在set方法上使用注解赋值
public void setAge(int age) {
this.age = age;
}
}
测试:
@Test
public void test03(){
String config = "ba03/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
Student myStudent = (Student) ac.getBean("myStudent");
System.out.println(myStudent);
}
@Resource的用法:
@Component("mySchool")
//@Component
public class School {
@Value("04xxx学校")
private String name;
@Value("04xxx地址")
private String address;
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("myStudent")
public class Student {
@Value("张五")
private String name;
private int age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
// @Resource //先使用byName自动注入,如果byName注入失败,再使用byType
@Resource(name="mSchool")//只使用byName自动注入,如果找不到,直接报错
private School school;
@Value("30")
public void setAge(int age) {
this.age = age;
}
}
注解与${}结合使用
用法: 在只能在赋值的时候使用,无法用于定义
resources/ba05目录下
applicationContext.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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.nguyenxb.ba05"/>
<!-- 加载属性的配置文件-->
<context:property-placeholder location="classpath:ba05/test.properties"/>
</beans>
test.properties
schoolClassName=mySchool
age=25
schoolAddress=xxx05address
java代码
School类
package com.nguyenxb.ba05;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("mySchool")
//@Component
public class School {
@Value("05xxx学校")
private String name;
@Value("${schoolAddress}")
private String address;
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student类
package com.nguyenxb.ba05;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component("MyStudent")
public class Student {
@Value("张五")
private String name;
private int age;
// @Resource //先使用byName自动注入,如果byName注入失败,再使用byType
@Resource(name="${schoolClassName}")//只使用byName自动注入,如果找不到,直接报错
private School school;
@Value("${age}")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
测试
package com.nguyenxb;
import com.nguyenxb.ba05.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest05 {
@Test
public void test05(){
String config = "ba05/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
Student myStudent = (Student) ac.getBean("MyStudent");
System.out.println(myStudent);
}
}
// 运行结果: Student{name='张五', age=25, school=School{name='05xxx学校', address='xxx05address'}}
注解与xml配置文件的对比
注解优点是:
- 方便
- 直观
- 高效(代码少,没有配置文件的书写那么复杂)。
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
XML 方式优点是:
- 配置和代码是分离的
- 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
第三章 AOP面向切面编程
动态代理
项目aop_leadin1
先定义好接口与一个实现类,该实现类中除了要实现接口中的方法外,还要再写两个非 业务方法。非业务方法也称为交叉业务逻辑:
- doTrans():用于事务处理
- doLog():用于日志处理
然后,再使接口方法调用它们。接口方法也称为主业务逻辑
public interface SomeService {
void doSome();
void doOther();
}
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
doLog();
System.out.println("执行了业务方法doSome");
doTrans();
}
@Override
public void doOther() {
doLog();
System.out.println("执行了业务方法doOther");
doTrans();
}
public void doLog(){
System.out.println("非业务功能,日志功能,在方法开始是输出日志"+new Date());
}
public void doTrans(){
System.out.println("非业务事务功能,在方法执行后,加入事务");
}
}
项目 aop_leadin2
另一种解决方案:将这些交叉业务逻辑代码放到专门的工具类或处理类 中,由主业务逻辑调用
public interface SomeService2 {
void doSome();
void doOther();
}
public class SomeServiceImpl2 implements SomeService2 {
@Override
public void doSome() {
ServiceTool.doLog();
System.out.println("执行了业务方法doSome");
ServiceTool.doTrans();
}
@Override
public void doOther() {
ServiceTool.doLog();
System.out.println("执行了业务方法doOther");
ServiceTool.doTrans();
}
}
public class ServiceTool {
public static void doLog(){
System.out.println("非业务功能,日志功能,在方法开始是输出日志"+new Date());
}
public static void doTrans(){
System.out.println("非业务事务功能,在方法执行后,加入事务");
}
}
项目 aop_leadin3 (动态代理的实现)
以上的解决方案,还是存在弊端:交叉业务与主业务深度耦合在一起。当交叉业务逻辑 较多时,在主业务代码中会出现大量的交叉业务逻辑代码调用语句,大大影响了主业务逻辑 的可读性,降低了代码的可维护性,同时也增加了开发难度。 所以,可以采用动态代理方式。在不修改主业务逻辑的前提下,扩展和增强其功能。下面代码中,使用动态代理实现doSome()方法时才输出日志,否则就正常输出.
public interface SomeService {
void doSome();
void doOther();
}
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("执行了业务方法doSome");
}
@Override
public void doOther() {
System.out.println("执行了业务方法doOther");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过代理对象执行方法时,会调用这个invoke()
Object res = null;
// 只有执行doSome()时, 才输出日志
if ("doSome".equals(method.getName())){
ServiceTool.doLog();
// 执行目标类的方法,通过Method类实现
res = method.invoke(target,args);// SomeServiceImpl.doSome() , doOther()
ServiceTool.doTrans();
}
// 目标方法执行结果
return res;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// 使用jad的Proxy创建代理对象
// 创建目标对象
SomeService target = new SomeServiceImpl();
// 创建InvocationHandler对象
InvocationHandler handler = new MyInvocationHandler(target);
// 使用Proxy创建代理
/*
* 传入参数:
* 1, 目标类的类加载器
* 2,目标类实现的接口
* 3,代理对象,要执行的功能
* */
SomeService proxy = (SomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
// 通过代理对象执行业务方法,会调用handler中的invoke(),即实现日志,事务的增强
proxy.doSome();
System.out.println("==============================");
proxy.doOther();
}
}
jdk动态代理
实现方式: jdk动态代理,使用jdk中Proxy,Method,InvocationHandler创建代理对象.
jdk的动态代理要求目标对象必须实现接口,这是java设计上的要求.
CGLIB动态代理
CGLIB动态代理: 是第三方工具库,创建代理对象,原理是继承.
CGLIB代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象.所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final的类,方法也不能时final的.
CGLIB经常被应用在框架中,如spring,Hibernate等. CGLIB的代理效率高于Jdk. 项目中之间使用动态代理的地方不多,一般都使用框架提供的功能.
动态代理的作用
-
在目标类源代码不改变的情况下,增加功能.
-
减少代码的重复.
-
专注业务逻辑代码
-
实现解耦合, 让业务的功能和日志,事务等非业务功能分离.
AOP概述
AOP(Aspect Orient Programming):面向切面编程,基于动态代理的,可以使用jdk,cglib两种代理方式.Aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式来使用动态代理
Aspect : 切面,给你的目标类增加功能,就是切面. 像上面用的日志,事务都是切面.
切面的特点: 一般都是非业务方法,独立使用的.
怎么理解面向切面编程?
- 需要在分析项目功能时,找出切面
- 合理的安排切面的执行时间(在目标方法前,还是目标方法后)
- 合理的安排切面执行的位置,在哪个类,哪个方法增加增强功能
术语:
-
Aspect:切面 , 表示 增强的功能,就是一段代码完成某一个非业务功能.
常见的切面功能有日志功能, 事务功能, 统计功能,参数检查, 权限验证.
-
JoinPoint: 连接点, 连接业务方法和切面的位置.就是某类中的业务方法
-
Pointcut: 切入点, 指给多个连接点增加功能, 这个类就是目标对象
-
目标对象: 给哪个类的方法增加功能,这个类就是目标对象.
-
Advice : 通知, 表示切面功能执行的时间.
切面的三要素
- 切面的代码功能,干什么样的功能.
- 切面的执行位置,使用Pointcut表示切面的执行位置.
- 切面的执行时间, 使用Advice表示时间,在目标方法之前,还是目标方法之后.
AOP的实现
aop是一个规范 , 是动态的一个规范化, 一个标准
aop的技术实现框架:
-
spring: spring在内部实现了一个aop规范,可以做aop的工作.
spring主要在事务处理时使用aop.
而在实际项目开发中很少使用spring的aop实现. 因为spring的aop比较笨重.
-
aspectJ: 一个开源的专门做aop的框架, spring 框架中集成了 aspectj 框架,通过spring就能使用aspectJ的功能.aspectJ框架实现aop有两种方式:
- 使用xml的配置文件: 配置全局事务
- 使用注解,我们在项目中使用aop功能时,一般使用注解,aspectJ有5个注解.
aspectJ框架的使用语法
-
切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强) , 在aspectj框架中使用注解表示的.也可以使用xml配置文件中的标签.
- @Before : 前置通知
- @AfterReturning : 后置通知
- @Around : 环绕通知
- @AftterThrowing : 异常通知
- @After : 最终通知
-
表示切面执行的位置,使用的是切入点表达式.
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 解释: modifiers-pattern] 访问权限类型 ret-type-pattern 返回值类型 declaring-type-pattern 包名类名 name-pattern(param-pattern) 方法名(参数类型和参数个数) throws-pattern 抛出异常类型 ?表示可选的部分 注意: 方法的返回值类型和方法名(参数类型和参数个数)是必须要有的 即以上表达式共 4 个部分。 execution(访问权限 方法返回值 方法声明(参数) 异常类型
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可 以使用以下符号:
符号 | 意义 |
---|---|
* | 0 至多个任意字符 |
.. | 用在方法参数中,表示任意多个参数 用在包名后,表示当前包及其子包路径 |
+ | 用在类名后,表示当前类及其子类 用在接口后, 表示当前接口及其实现类 |
举例:
1. execution(public * *(..))
//指定切入点为:任意公共方法。
2. execution(* set*(..))
//指定切入点为:任何一个以“set”开始的方法。
3. execution(* com.xyz.service.*.*(..))
//指定切入点为:定义在 service 包里的任意类的任意方法。
4. execution(* com.xyz.service..*.*(..))
//指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
5. execution(* *..service.*.*(..))
//指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
6. execution(* *.service.*.*(..))
//指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
7. execution(* *.ISomeService.*(..))
//指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
8. execution(* *..ISomeService.*(..))
//指定所有包下的 ISomeSerivce 接口中所有方法为切入点
9. execution(* com.xyz.service.IAccountService.*(..))
//指定切入点为:IAccountService 接口中的任意方法。
10. execution(* com.xyz.service.IAccountService+.*(..))
//指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
11. execution(* joke(String,int)))
//指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
12. execution(* joke(String,*)))
//指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是
13. execution(* joke(String,..)))
//指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
14. execution(* joke(Object))
//指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
15. execution(* joke(Object+)))
//指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。
使用aspectj框架实现aop
目的: 给已经存在的一些类和方法,增加额外的功能.前提是不改变原来类的代码.
使用aspectj实现aop的基本步骤:
-
新建maven项目
-
加入依赖
-
spring依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
aspectj依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
-
创建目标类:接口和它的实现类.要做的就是给类中的方法增加功能.
-
创建切面类型:普通类
-
在类的上面加入@Aspect
-
在类中定义方法, 方法里面封装封装的就是切面要执行的功能代码
要在方法上面加入aspectj中的通知注解,例如@Before,还需要指定切入点表达式execution()
-
-
创建spring的配置文件: 声明对象,把对象交给容器统一管理,声明对象可以使用注解或者xml配置文件
<bean>
-
声明目标对象
-
声明切面类对象
-
声明aspectj框架中的自动代理生成器标签.
自动代理生成器: 用来完成对象的自动创建功能.
-
-
spring使用什么代理?
-
当目标类有接口的时候, 使用的是jdk动态代理,
-
没有接口使用的是spring框架提供的cglib动态代理
-
当有接口时, 可以也可以使用spring提供的动态代理 , 即在配置文件中加入:
<!-- proxy-target-class="true" : 告诉 spring 框架使用 cglib动态代理--> <aop:aspectj-autoproxy proxy-target-class="true"/>
-
前置通知实例:@Before
applicationContext.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.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 把对象交给spring容器,由spring容器统一创建,管理对象-->
<!-- 声明目标对象-->
<bean id="someService" class="com.nguyenxb.ba01.SomeServiceImpl"/>
<!-- 声明切面对象-->
<bean id="myAspect" class="com.nguyenxb.ba01.MyAspect"/>
<!-- 声明自动代理生成器: 使用aspectj框架内部的功能,创建目标对象的代理对象.
创建代理对象是哎内存中实现的,修改目标对象的内存中的结果.创建为代理对象
所有目标对象就是被修改后的代理对象.
如果下面这些东西没有,需要自己手动加
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象,
-->
<aop:aspectj-autoproxy/>
</beans>
目标接口定义:
public interface SomeService {
void doSome(String name,Integer age);
}
目标类实现
// 目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, Integer age) {
// 给doSome方法增加一个功能,在doSome()执行之前,输出方法的执行时间.
System.out.println("=====目标方法doSome()执行========"+name+":"+age);
}
}
切面类
/**
* @Aspect : 是aspectj框架中的注解.
* 作用: 表示当前类是切面类.
* 切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/*
* 定义方法, 方法是实现切面功能的.
* 方法的定义要求:
* 1.公共方法 public
* 2.方法没有返回值
* 3. 方法名称自定义
* 4. 方法可以有参数,也可以没有参数
* 如果有参数,参数不是自定义的,有几个参数类型可以使用.
* */
/**
* @Before: 前置通知注解,可以有多个
* 属性: value, 是切入点表达式.表示切面的功能执行的位置.
* 位置:在方法的上面
*
* 特点:
* 1.在目标方法之前先执行的.
* 2. 不会改变目标方法的执行结果
* 3.不会影响目标方法的执行.
*/
//变形 1
// @Before(value = "execution(public void com.nguyenxb.ba01.SomeServiceImpl.doSome(String,Integer))")
//变形 2
// @Before(value = "execution(void com.nguyenxb.ba01.SomeServiceImpl.doSome(String,Integer))")
//变形 3
// @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
//变形 4
// @Before(value = "execution(void *..SomeServiceImpl.doSome(..))")
//变形 5
@Before(value = "execution(void *..SomeServiceImpl.do*(..))")
public void myBefore(){
// 就是你切面要执行的功能代码
System.out.println("前置通知,切面功能: 在目标方法执行之前输出执行时间:"+new Date());
}
}
测试
public class MyTest01 {
@Test
public void test01(){
String config="applicationContext.xml";
// 获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
SomeService proxy = (SomeService) ac.getBean("someService");
System.out.println("proxy=="+proxy.getClass().getName());//该类类型为com.sun.proxy.$Proxy8
proxy.doSome("haha",12);
}
}
切面类方法获取目标类的数据:joinPoint
/**
* 指定通知方法的参数: joinPoint
* JoinPoint: 业务方法, 要加入切面功能的业务方法
* 作用是: 可以在通知方法中获取方法执行的信息, 例如方法名称,方法的实参
* 这个JoinPoint参数的值是框架赋予的,必须是第一个位置的参数
* */
public void myBefore(JoinPoint joinPoint){
// 就是你切面要执行的功能代码
System.out.println("方法的签名(定义):"+joinPoint.getSignature());
System.out.println("方法的名称"+joinPoint.getSignature().getName());
// 获取方法的实参
Object args[] = joinPoint.getArgs();
for (Object arg:args){
System.out.println("参数:"+arg);
}
System.out.println("前置通知,切面功能: 在目标方法执行之前输出执行时间:"+new Date());
}
/** 以前面的例子举例,输出结果为 :
方法的签名(定义):void com.nguyenxb.ba01.SomeService.doSome(String,Integer)
方法的名称doSome
参数:haha
参数:12
*/
后置通知@AfterReturning
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
/**
* @Aspect : 是aspectj框架中的注解.
* 作用: 表示当前类是切面类.
* 切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/*
* 后置通知定义方法, 方法是实现切面功能的.
* 方法的定义要求:
* 1.公共方法 public
* 2.方法没有返回值
* 3. 方法名称自定义
* 4. 方法要有参数,推荐是Object, 参数名自定义
* */
/**
* @AfterReturning: 前置通知注解,可以执行多个
* 属性: 1.value, 是切入点表达式.表示目标方法的返回值.
* 2. returning 自定义的变量,表示目标方法的返回值的,
* 自定义变量名必须和通知方法的形参名一样
* 位置:在方法定义的上面
*
* 特点:
* 1.在目标方法之后执行的.
* 2. 能够获取到目标放啊的返回值,可以根据这个返回值做不同的处理功能
* 3.可以修改这个返回值
*
* 后置通知执行的顺序
* Object res = doOther()
* myAfterReturning(res);
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
// 如果要使用JoinPoint, 只需要改成 : public void myAfterReturning(JoinPoint jp,Object res) 即可
public void myAfterReturning(Object res){
// Object res : 是目标方法执行后的返回值,根据返回值做你的切面的功能处理
System.out.println("后置通知,在目标之后执行的,获取的返回值是:"+res);
// 如果是普通值,在这里更改数据后,目标方法返回的结果不会改变,
// 如果是引用类型,在这里修改数据后,目标方法会发生改变
res = "asdasd";
}
}
环绕通知: @Around
package com.nguyenxb.ba03;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
* @Aspect : 是aspectj框架中的注解.
* 作用: 表示当前类是切面类.
* 切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 环绕通知方法的定义格式
* 1,public
* 2,必须有一个返回值,推荐使用Object
* 3,方法名称定义
* 4,方法有参数,固定的参数 ProceedingJoinPoint
* */
/**
* @Around: 环绕通知
* 属性: value 切入点表达式
* 位置: 在方法定义上面
*
* 特点:
* 1. 他是功能最强的通知
* 2. 在目标方法的前和后都能增强功能.
* 3. 控制目标方法是否被调用执行.
* 4. 修改原来的目标方法的执行结果. 影响最好的调用结果
*
* 环绕通知,等同与jdk的动态代理的,InvocationHandler接口
* 参数: ProceedingJoinPoint 就等同于 Method
* 作用 : 执行目标方法的
* 返回值 : 就是目标方法的执行结果,结果可以被修改
*
* 环绕通知经常做事务, 在目标放之前开启事务,执行目标方法之后提交事务.
*
* */
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 获取目标类的第一个参数值
Object args[] = proceedingJoinPoint.getArgs();
for (Object arg: args){
System.out.println("参数:"+arg);
}
// 实现环绕通知
Object result = null;
System.out.println("环绕通知: 在目标方法之前,输出时间:"+new Date());
// 1. 目标方法的调用
result = proceedingJoinPoint.proceed(); // 等同于method.invoke(); Object result = doFirst();
System.out.println("环绕通知: 在目标方法之后,如提交事务");
// 2. 在目标方法的前或者后加入功能
// 返回目标方法的执行结果,可以改变结果,并且会影响返回的结果
return result;
}
}
异常通知:@AfterThrowing
package com.nguyenxb.ba04;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
* @Aspect : 是aspectj框架中的注解.
* 作用: 表示当前类是切面类.
* 切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 异常通知方法的定义格式:
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法有一个Exception,如果还有就是JoinPoint
*
* */
/**
* @AfterThrowing : 异常通知
* 属性: 1.value 切入点表达式
* 2. throwing 自定义变量名, 表示目标方法抛出的异常对象,
* 变量名必须和方法的参数名一样
*
* 执行类比于:
* try{
* doSecond()
* }catch(Exception ex){
* System.out.println("异常通知: 方法发生异常时, 执行: "+ex);
* }
*
* */
@AfterThrowing(value = "execution(* *..doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知: 方法发生异常时, 执行: "+ex);
// 发生邮件,或者短信通知开发人员
}
}
最终通知:@After
package com.nguyenxb.ba05;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
/**
* @Aspect : 是aspectj框架中的注解.
* 作用: 表示当前类是切面类.
* 切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 最终通知方法的定义格式
* 1.public
* 2. 没有返回值
* 3.方法名称自定义
* 4. 方法没有参数 , 如果还有是JoinPoint,
*
* */
/**
* @After : 最终通知
* 属性: value 切入点表达式
* 位置: 在方法的上面
* 特点:
* 1. 总是会执行
* 2. 在目标方法之后执行的
*
*
* 其实执行就等价于
* try{
*
* }catch(Exception e){
*
* }finally{
*
* }
* */
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
// 一般是做资源清除工作的.
}
}
切入点表达式别名 @Pointcut
package com.nguyenxb.ba06;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* @Aspect : 是aspectj框架中的注解.
* 作用: 表示当前类是切面类.
* 切面类: 是用来给业务方法增加功能的类, 在这个类中有切面的功能代码.
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 最终通知方法的定义格式
* 1.public
* 2. 没有返回值
* 3.方法名称自定义
* 4. 方法没有参数 , 如果还有是JoinPoint,
*
* */
/**
* @After : 最终通知
* 属性: value 切入点表达式
* 位置: 在方法的上面
* 特点:
* 1. 总是会执行
* 2. 在目标方法之后执行的
*
*
* 其实执行就等价于
* try{
*
* }catch(Exception e){
*
* }finally{
*
* }
* */
// @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
@After(value = "myPointcut()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
// 一般是做资源清除工作的.
}
// @Before(value = "execution(* *..SomeServiceImpl.doThird(..))")
@Before(value = "myPointcut()")
public void myBefore(){
System.out.println("前置通知,总是在目标方法之前执行");
// 一般是做资源清除工作的.
}
/**
* @Pointcut : 定义和管理切入点, 如果项目中有多个切入点表达式是重复的,可以复用的,
* 那么可以使用 @Pointcut
* 属性: value 切入点表达式
* 位置: 在自定义的方法的上面
* 特点: 当使用 @Pointcut定义在一个方法的上面,次数这个方法的名称就是切入点的别名
* 其他的通知值,value属性就可以使用这个方法的名称代替切入点表达式了
*
* */
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myPointcut(){}
}
第四章 Spring集成mybatis框架
将mybatis框架和spring集成在一起,像一个框架一样使用.
使用技术: ioc
为什么使用ioc ?
因为ioc能创建对象,可以把mybatis框架中的对象交给spring统一创建,开发人员从spring中获取对象. 开发人员就不用同时面对两个或者多个框架了,就面对一个spring就行了.
mybatis使用步骤,创建对象
-
定义dao接口,StudentDao
-
定义mapper文件, StudentDao.xml
-
定义主配置文件 mubatis.xml
-
创建dao的代理对象,
StudentDao dao = sqlSession.getMapper(StudentDao.class); List<Student> students = dao.selectStudents();
使用dao对象, 需要使用getMapper()方法,
使用getMapper()方法,需要的条件
- 获取SqlSession对象,需要使用SqlSessionFactory的openSession()方法.
- 创建SqlSessionFactory对象.通过读取mybatis的主配置文件,就能创建sqlSessionFactory对象.
我们会使用性能相对更好的独立的连接池来代替mybatis默认自己带的,把连接此类也交给spring创建对象.
主配置文件信息:
-
数据库信息
<environment id="development"> <transactionManager type="JDBC"/> <!--配置数据源: 创建Connection对象--> <dataSource type="POOLED"> <!--driver:驱动的内容--> <property name="driver" value="com.mysql.jdbc.Driver"/> <!--连接数据库的url: 不能直接使用 & 符号,要将其转换为 html 实体即 & 代表的就是 & 符号 --> <property name="url" value="jdbc:mysql://localhost:3306/mysql_DBName? useSSL=true&useUnicode=true&characterEncoding=utf-8"/> <!--用户名--> <property name="username" value="root"/> <!--用户密码--> <property name="password" value="root"/> </dataSource> </environment>
-
mapper文件的位置
<mappers> <!-- 使用mapper的resource属性指定mapper文件的路径. 这个路径是从target/classes路径开始的 使用注意: resource = "mapper文件的路径,使用 / 分割路径" 一个mapper resource 指定一个mapper文件 --> <mapper resource="com\nguyenxb\dao\StudentDao.xml"/> </mappers>
通过以上说明, 我们需要让spring创建以下对象
- 独立的连接类的对象, 使用阿里的druid连接池
- SqlSessionFactory对象
- 创建出dao对象
即使用xml的bean标签创建上面三个对象.
spring和mybatis的集成
步骤:
- 新建maven项目
- 加入maven依赖
- spring依赖
- mybatis依赖
- mysql驱动
- spring的事务依赖
- mybatis和spring集成的依赖: mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory,dao对象的.
- 创建实体类
- 创建dao接口和mapper文件
- 创建mybatis主配置文件
- 创建Service接口和实现类,属性是dao.
- 创建spring的配置文件:声明mybatis的对象交给spring创建
- 数据源
- SqlSessionFactory
- Dao对象
- 声明自定义的service
所有依赖:
<dependencies>
<!-- 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring核心ioc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- spring事务要用到的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mybatis和spring集成的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!-- 阿里公司的数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<!-- 插件-->
<build>
<!-- 目的是把src/main/java 目录中的xml文件包含到输出classes目录中-->
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<!-- 指定jdk版本的-->
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
spring的配置文件:applicationContext.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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 声明数据源DataSource,作用是连接数据库的
使用属性配置文件中的数据
-->
<bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<!-- 声明的是mybatis中提供的SqlSessionFactoryBean类,
这个类内部创建SqLSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- set注入,把数据库连接池赋值给dataSource属性-->
<property name="dataSource" ref="myDataSources"/>
<!-- mybatis主配置文件的位置
configLocation 是Resource类型, 读取配置文件
它的赋值是使用value,指定文件的路径, 即 使用 classpath: 表示文件的位置
-->
<property name="configLocation" value="mybatis.xml"/>
</bean>
<!-- 创建dao对象,使用sqlSession的getMapper(Student.class)
MapperScannerConfigurer: 在北部调用getMapper()歙观察每个dao接口的代理对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定SqlSessionFactory对象的id-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 指定包名,包名是dao接口所在的包名.
mapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行异常getMapper()方法
得到每个接口的dao对象,创建好的dao对象放入到spring的容器中.
dao对象的默认名称是接口名的首字母小写
-->
<property name="basePackage" value="com.nguyenxb.dao"/>
</bean>
<bean id="studentService" class="com.nguyenxb.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
</beans>
mybatis主配置文件:mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--配置输出日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 设置别名-->
<typeAliases>
<!-- name: 实体类所在的包名-->
<package name="com.nguyenxb.entity"/>
</typeAliases>
<!--指定其他mapper文件的位置:
目的: 使用这里的连接数据库信息来执行其他文件的sql语句-->
<!--Enter the path to your mapper file -->
<mappers>
<!-- mapper文件所在的包名-->
<package name="com.nguyenxb.dao"/>
</mappers>
</configuration>
属性文件: jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/ssm? useSSL=true&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
jdbc.maxActive=20
Dao包下的文件
操作学生表的接口:StudentDao.java
package com.nguyenxb.dao;
import com.nguyenxb.entity.Student;
import java.util.List;
public interface StudentDao {
int insertStudent(Student student);
List<Student> selectStudents();
}
mapper文件:StudentDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nguyenxb.dao.StudentDao">
<!--Enter your sql statement with (select | insert | update |delete) tag
The id of the tag is suggested to use the name of the method that called it-->
<select id="selectStudents" resultType="com.nguyenxb.entity.Student">
select id,name,email,age from student order by id desc
</select>
<insert id="insertStudent" >
insert into student values (#{id},#{name},#{email},#{age})
</insert>
</mapper>
entity包下的文件
Student.java
package com.nguyenxb.entity;
public class Student {
private Integer id;
private String name;
private String email;
private Integer age;
public Student() {
}
public Student(Integer id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
service包下的文件
学生业务接口:StudentService.java
package com.nguyenxb.service;
import com.nguyenxb.entity.Student;
import java.util.List;
public interface StudentService {
int addStudent(Student student);
List<Student> queryStudents();
}
学生业务实现类: impl.StudentServiceImpl.java
package com.nguyenxb.service.impl;
import com.nguyenxb.dao.StudentDao;
import com.nguyenxb.entity.Student;
import com.nguyenxb.service.StudentService;
import java.util.List;
public class StudentServiceImpl implements StudentService {
// 引用类型
private StudentDao studentDao;
// set注入赋值
public void setStudentDao(StudentDao studentDao) {
this.studentDao=studentDao;
}
public StudentServiceImpl(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
int rows = studentDao.insertStudent(student);
return rows;
}
@Override
public List<Student> queryStudents() {
List<Student> students = studentDao.selectStudents();
return students;
}
}
测试代码:
package com.nguyenxb;
import com.nguyenxb.dao.StudentDao;
import com.nguyenxb.entity.Student;
import com.nguyenxb.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class MyTest {
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
String[] names = ac.getBeanDefinitionNames();
for (String name:names){
System.out.println("容器中对象名称:"+name+"---|---" + ac.getBean(name).getClass());
}
}
@Test
public void testInsertStudent(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
StudentDao studentDao = (StudentDao) ac.getBean("studentDao");
Student student = new Student(1009,"张伟","zhangwei@qq.com",20);
int row = studentDao.insertStudent(student);
System.out.println(row);
}
@Test
public void testServiceInsert(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
StudentService studentService = (StudentService) ac.getBean("studentService");
Student student = new Student(1010,"李双","zhangwei@qq.com",20);
int row = studentService.addStudent(student);
// spring和mybatis整合在一起使用,事务是自动提交的,无需执行sqlSession.commit()
System.out.println(row);
}
@Test
public void testSelectStudents(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
StudentService studentService = (StudentService) ac.getBean("studentService");
List<Student> students = studentService.queryStudents();
for (Student student: students){
System.out.println(student);
}
}
}
第五章 spring的事务处理
什么是事务?
事务是 一组sql语句的集合, 集合中有多条sql语句,可能是insert, update, select, delete, 我们希望这些多个sql语句都能执行成功, 否则就都失败. 这些sql语句的执行都是一致的,作为一个整体执行.
在什么时候要使用事务?
当我的操作涉及到多个表,或者多个sql语句的insert,update,delete.需要这些语句都是成功才能完成我的功能,或者都失败,保证操作都是符合要求的.比如用于转账.
在java代码中写程序,控制事务,此时事务应该放在哪里呢?
答: 应该放在service类的业务方法上,因为我们的业务方法会调用多个dao方法,执行多个sql语句.
通常使用jdbc访问数据库,还有使用mybaits访问数据库怎么处理事务?
-
jdbc访问数据库,处理事务
Connection conn; // 提交事务 conn.commit(); // 回滚事务 conn.rollback();
-
mybati访问数据库,处理事务
SqlSession.commit(); // 回滚事务 SqlSession.rollback();
前一个问题中事务的处理方式,有什么不足?
-
不同的数据库访问技术,处理事务的对象, 方法不同, 需要了解不同数据库访问技术使用事务的原理.
-
需要掌握多种数据库中事务的处理逻辑.什么时候提交,什么时候回滚事务.
-
需要掌握处理事务的多种方法.
总结: 就是多种数据库的访问技术,有不同的事务处理的机制, 对象, 方法.
怎么解决不足?
spring提供一种处理事务的统一模型, 能够统一使用步骤, 方式完成多种不同数据库访问技术的事务处理.
spring的事务处理模型: 抽象了事务处理的各个方面,定义了事务的处理步骤.
spring使用的是声明式事务,把事务相关的资源和内容都提供给spring, spring就能处理事务提交,回滚了.
即使用spring的事务处理机制,可以完成多个不同的访问数据库的事务处理, 比如可以完成mybatis的访问数据库事务处理,也能完成hibernate访问数据的事务处理.等等.
处理事务,需要做什么, 怎么做?
spring处理事务的模型,使用的步骤是固定的.把事务使用的信息提供给spring就可以了.
1) 事务内部提交,回滚事务.
使用的事务管理器对象,代替你完成commit, roolback. 事务管理器是一个接口和它的众多实现类.
接口:
PlatformTransactionManager, 定义了事务重要方法,commit,rollback.
实现类:
spring把每一种数据库访问技术对应的事务处理类都创建好了,
mybatis访问数据库—-spring创建好的是DataSourceTransactionManager
hibernate访问数据库—–spring创建的是HibernateTransactionManager
怎么使用:
你需要告诉spring , 你用的是哪种数据库访问技术.
怎么告诉spring你使用的是哪种数据库访问技术呢?
声明数据库访问技术对应的事务管理器的实现类, 在spring的配置文件中使用<bean>
声明就可以了.
例如, 你要使用mybatis访问数据库, 你应该在配置文件中声明:
<bean id="xxx" class="...DataSourceTransactionManager">
2)你的业务方法需要什么样的事务,说明需要事务的类型.
1)事务的隔离级别: 有4个值.
DEFAULF: 采用DB默认的事务隔离级别. MySql 的默认的隔离级别为 REPEATABLE_READ, Oracle默认为READ_COMMITTED.
1, READ_UNCOMMITTED:读未提交,未解决任何并发问题.
2, READ_COMMITTED: 读已提交. 解决脏读, 存在不可重复读与幻读.
3, REPEATABLE_READ: 可重复读. 解决脏读, 不可重复读, 存在幻读.
4, SERIALIZABLE: 串行化. 不存在并发问题.
(2) 事务的超时时间:
表示一个方法最长的执行时间, 如果方法执行时超过了时间, 事务就回滚. 单位是秒, 整数值, 默认是 -1.
(3) 事务的传播行为 :
控制业务方法是不是有事务, 是什么样的传播行为.7个传播行为, 表示你的业务方法调用时, 事务在方法之间如何使用的.
a. PROPAGATION_REQUIRED
指定的方法必须在事务内执行. 若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务. 这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为.
**例如:**该传播行为加在 doOther()方法上. 若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行. 若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行,
用实际生活中的来说就是, 比如张三和李四在打王者荣耀, 两个人都是使用的4G网络, 但是张三的流量比李四的流量多. 然后李四就问张三: “能不能开个热点给我 ?” 若张三说: “可以开热点给你”. 那么张三和李四就使用的是同一个网络,即一起使用张三的流量.
若张三说: “我拒绝开热点给你”, 那么张三和李四就使用各自的流量玩.
b. PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行.
c. PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
d. PROPAGATION_MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常。
e. PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
f. PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
g. PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
3)事务管理器接口定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。 注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该 值一般就使用默认值即可。
4)spring提交事务,回滚事务的时机.
-
当你的业务方法, 执行成功, 没有异常抛出. 即在方法执行完毕后,spring提交事务.使用的是事务管理器 commit.
-
当你的业务方法抛出运行时异常,spring执行回滚, 调用事务管理器rollback.
运行时异常 的定义 : RuntimeException 和他的子类都是运行时异常.
-
当业务方法抛出非运行时异常, 主要是受查异常时, 提交事务.
受查异常: 在你写代码中, 必须处理的异常, 例如 IOException, SQLException
总结spring的事务
-
管理事务的是 事务管理器和它的实现类
-
spring的事务是一个统一模型
- 指定要使用的事务管理器实现类, 使用
<bean>
- 指定哪些类, 哪些方法需要加入事务的功能
- 指定方法需要的隔离级别, 传播行为, 超时
你需要告诉spring, 你的项目中类信息, 方法的名称, 方法的事务传播行为.
- 指定要使用的事务管理器实现类, 使用
spring框架中提供的事务处理方案
A. 适合中小项目使用的,注解方案.
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务.@Transactional注解是spring框架的注解, 放在public方法的上面, 表示当前方法具有事务.可以给注解的属性赋值, 表示具体的隔离级别, 传播行为, 异常信息等.
@Transactional 的所有可选属性如下所示:
- propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
- isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
- readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
- timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。
- rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
- rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
- noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
- noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
使用@Transactional的步骤:
1.需要声明事务管理器对象
<bean id="xx" class="DataSourceTransactionManager">
- 开启事务注解驱动, 告诉spring框架, 我要使用注解的方式管理事务.
spring使用aop机制, 创建@Transactional所在的类代理对象, 给方法加入事务的功能.
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务, 在业务放啊之后提交或回滚事务, 使用aop的环绕通知.
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
// 开启事务,spring 给你开启
try{
buy(1001,10);
spring的事务管理.commit();
}catch(Exception e){
spring的事务管理.rollback();
}
}
3.在你的方法上面加入@Trancational注解
例子:
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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 声明数据源DataSource,作用是连接数据库的
使用属性配置文件中的数据
-->
<bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<!-- 声明的是mybatis中提供的SqlSessionFactoryBean类,
这个类内部创建SqLSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- set注入,把数据库连接池赋值给dataSource属性-->
<property name="dataSource" ref="myDataSources"/>
<!-- mybatis主配置文件的位置
configLocation 是Resource类型, 读取配置文件
它的赋值是使用value,指定文件的路径, 即 使用 classpath: 表示文件的位置
-->
<property name="configLocation" value="mybatis.xml"/>
</bean>
<!-- 创建dao对象,使用sqlSession的getMapper(Student.class)
MapperScannerConfigurer: 在北部调用getMapper()歙观察每个dao接口的代理对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定SqlSessionFactory对象的id-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 指定包名,包名是dao接口所在的包名.
mapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行异常getMapper()方法
得到每个接口的dao对象,创建好的dao对象放入到spring的容器中.
dao对象的默认名称是接口名的首字母小写
-->
<property name="basePackage" value="com.nguyenxb.dao"/>
</bean>
<!-- 声明service-->
<bean id="buyGoodsService" class="com.nguyenxb.service.impl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!-- 使用spring的事务处理-->
<!-- 声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接的数据库, 指定的数据库源-->
<property name="dataSource" ref="myDataSources"/>
</bean>
<!-- 开启事务注解驱动, 告诉spring使用注解管理事务, 创建代理对象
导入的应该是这个地址: http://www.springframework.org/schema/tx
transaction-manager 表示的是事务管理器的id
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
业务方法:
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
/*
* 声明事务
*
* propagation: 设置传播行为
* readOnly : 只读
* rollbackFor: 如果发生指定的异常, 一定回滚.
* 处理逻辑:
* 1. spring框架会先检查方法抛出的异常是不是在rollbackFor的
* 属性值中,如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚.
* 2.如果抛出的异常不在rollbackFor列表中, spring会判断异常是不是运行时异常,
* 如果是,则一定回滚.
*
* */
/* @Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class,
NotEnoughException.class
}
)*/
/* 使用的是事务控制的默认值, 默认的传播行为是Required, 默认的
隔离级别Default,默认抛出运行时异常的时候,就回滚事务.
*/
@Transactional
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("======buy 方法的开始===");
// 记录销售信息, 向sale表添加记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
// 更新库存
Goods goods = goodsDao.selectGoods(goodsId);
if (goods==null){
// 商品不存在
throw new NullPointerException("编号是:"+goodsId+", 商品不存在");
}else if ( goods.getAmount() < nums){
throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");
}
// 修改库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=========buy方法完成===========");
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
}
B. 适合大型项目,有很多类,方法.
需要大量的配置事务, 使用aspectj框架功能, 在spring配置文件中声明类, 方法需要的事务. 这种方式业务方法和事务配置完全分离.
实现步骤: 都是在xml配置文件中实现.
(1) 要使用的是ascpectj框架, 需要加入依赖.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
(2) 声明事务管理器对象
<bean id="xx" class="DataSourceTransactionManager">
(3) 声明方法需要的事务类型(配置方法的事务属性 [隔离级别, 传播行为, 超时] )
<!-- 声明式事务处理 : 和源代码完全分离的-->
<!-- 1. 声明事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSources"/>
</bean>
<!-- 2. 声明业务方法的事务属性,(隔离级别,传播行为,超时时间)
id: 自定义名称, 表示 <tx:advice> 和 </tx:advice> 之间的配置内容的
transaction-manager: 事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- tx:attributes 配置事务属性 -->
<tx:attributes>
<!-- tx:method : 给具体的方法配置事务属性 , method可以有多个, 分布给不同的方法设置事务属性,
name: 方法名称, 1)完成的方法名称, 不带有包和类.
2)方法可以使用通配符, * 表示任意字符
propagation: 传播行为, 枚举值
isolation: 隔离级别
rollback-for : 你指定的异常类名,全限定类名. 发生异常一定回滚.
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.nguyenxb.excep.NotEnoughException"/>
<!-- 使用通配符,指定很多的方法-->
<!-- 如设置添加方法, 若所有的添加方法命名都是 add开头的-->
<tx:method name="add*" propagation="REQUIRES_NEW"/>
<!-- 如设置修改方法, 若所有的修改方法命名都是 modify开头的-->
<tx:method name="modify*" />
<!-- 如设置删除方法, 若所有的删除方法命名都是 remove开头的-->
<tx:method name="remove*"/>
<!-- 如设置查询方法, 若所有的添加方法命名是query,search,find开头的-->
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
(4) 配置aop : 指定哪些类要创建代理.
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式: 指定哪些包中的类,需要使用事务
id : 切入点表达式的名称, 唯一值
Expression: 切入点表达式,指定哪些类要使用事务.
如要求几个包下有事务:
com.sadads.service
com.crm.service
com.nguyenxb.service
-->
<!-- <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>-->
<aop:pointcut id="servicePt" expression="execution(* *..serive..*(..))"/>
<!--配置增强器 : 管理advice和pointcut
advice-ref : 通知, 上面tx: advice是那里的配置
pointcut-ref : 切入点表达式.
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
例子:
要导入的依赖:
<!-- 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring核心ioc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- spring事务要用到的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mybatis和spring集成的依�?-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!-- 阿里公司的数据库连接�?-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- aspectj-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
业务类:
package com.nguyenxb.service.impl;
import com.nguyenxb.dao.GoodsDao;
import com.nguyenxb.dao.SaleDao;
import com.nguyenxb.entity.Goods;
import com.nguyenxb.entity.Sale;
import com.nguyenxb.excep.NotEnoughException;
import com.nguyenxb.service.BuyGoodsService;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("======buy 方法的开始===");
// 记录销售信息, 向sale表添加记录
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
// 更新库存
Goods goods = goodsDao.selectGoods(goodsId);
if (goods==null){
// 商品不存在
throw new NullPointerException("编号是:"+goodsId+", 商品不存在");
}else if ( goods.getAmount() < nums){
throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");
}
// 修改库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=========buy方法完成===========");
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
}
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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 声明数据源DataSource,作用是连接数据库的
使用属性配置文件中的数据
-->
<bean id="myDataSources" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<!-- 声明的是mybatis中提供的SqlSessionFactoryBean类,
这个类内部创建SqLSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- set注入,把数据库连接池赋值给dataSource属性-->
<property name="dataSource" ref="myDataSources"/>
<!-- mybatis主配置文件的位置
configLocation 是Resource类型, 读取配置文件
它的赋值是使用value,指定文件的路径, 即 使用 classpath: 表示文件的位置
-->
<property name="configLocation" value="mybatis.xml"/>
</bean>
<!-- 创建dao对象,使用sqlSession的getMapper(Student.class)
MapperScannerConfigurer: 在北部调用getMapper()歙观察每个dao接口的代理对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定SqlSessionFactory对象的id-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 指定包名,包名是dao接口所在的包名.
mapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行异常getMapper()方法
得到每个接口的dao对象,创建好的dao对象放入到spring的容器中.
dao对象的默认名称是接口名的首字母小写
-->
<property name="basePackage" value="com.nguyenxb.dao"/>
</bean>
<bean id="buyGoodsService" class="com.nguyenxb.service.impl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!-- 声明式事务处理 : 和源代码完全分离的-->
<!-- 1. 声明事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSources"/>
</bean>
<!-- 2. 声明业务方法的事务属性,(隔离级别,传播行为,超时时间)
id: 自定义名称, 表示 <tx:advice> 和 </tx:advice> 之间的配置内容的
transaction-manager: 事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!-- tx:attributes 配置事务属性 -->
<tx:attributes>
<!-- tx:method : 给具体的方法配置事务属性 , method可以有多个, 分布给不同的方法设置事务属性,
name: 方法名称, 1)完成的方法名称, 不带有包和类.
2)方法可以使用通配符, * 表示任意字符
propagation: 传播行为, 枚举值
isolation: 隔离级别
rollback-for : 你指定的异常类名,全限定类名. 发生异常一定回滚.
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.nguyenxb.excep.NotEnoughException"/>
<!-- 使用通配符,指定很多的方法-->
<!-- 如设置添加方法, 若所有的添加方法命名都是 add开头的-->
<tx:method name="add*" propagation="REQUIRES_NEW"/>
<!-- 如设置修改方法, 若所有的修改方法命名都是 modify开头的-->
<tx:method name="modify*" />
<!-- 如设置删除方法, 若所有的删除方法命名都是 remove开头的-->
<tx:method name="remove*"/>
<!-- 如设置查询方法, 若所有的添加方法命名是query,search,find开头的-->
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式: 指定哪些包中的类,需要使用事务
id : 切入点表达式的名称, 唯一值
Expression: 切入点表达式,指定哪些类要使用事务.
如要求几个包下有事务:
com.sadads.service
com.crm.service
com.nguyenxb.service
-->
<!-- <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>-->
<aop:pointcut id="servicePt" expression="execution(* *..serive..*(..))"/>
<!--配置增强器 : 管理advice和pointcut
advice-ref : 通知, 上面tx: advice是那里的配置
pointcut-ref : 切入点表达式.
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
</beans>
第六章 web项目中怎么使用容器对象
在web项目中使用spring完成学生注册的功能.
实现步骤:
-
创建maven,web项目
-
加入依赖
<!-- 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- spring核心ioc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- spring事务要用到的 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency> <!-- mybatis和spring集成的依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.9</version> </dependency> <!-- 阿里公司的数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!-- servlet依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- jsp依赖 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2.1-b03</version> <scope>provided</scope> </dependency>
-
创建实体类
-
创建jsp发起请求,有参数id,name,email,age.
-
创建servlet, 接受请求参数, 调用service,调用dao,完成注册.
-
创建结果页面,作为显示结果页面.
需求:
web项目中容器对象只需要创建异常, 把容器对象放入全局作用域ServletContext中.
怎么实现?
使用监听器,当全局作用域对象被创建时 ,创建容器,存入servletContext.
监听器的作用:
(1) 创建容器对象, 执行ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);
(2) 把容器对象放入到servletContext, servletContext.setAttribute(key,ac);
监听器可以自己创建,也可以使用框架写好的ContextLoaderListener.
要使用框架写好的监听器, 需要加入依赖
<!-- 为了使用监听器对象,需要加入依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
然后在web.xml中注册监听器
<!-- 注册监听器ContextLoaderListener
监听器被创建对象后, 会读取/WEB-INF/applicationContext.xml
为什么要读取文件?
因为在监听器中要创建ApplicationContext对象, 需要读取加载配置文件
/WEB-INF/applicationContext.xml 就是监听器默认读取的spring配置文件路径
可以修改默认的文件位置, 使用 context-param 重新指定配置文件的路径
-->
<context-param>
<!-- contextConfigLocation: 表示配置文件的路径-->
<param-name>contextConfigLocation</param-name>
<!-- 自定义配置文件的路径, 注意: 此时我的spring配置文件放在自定义的resources目录下-->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
该监听器的基本内部实现
public interface WebApplicationContext extends ApplicationContext {
// 获取spring容器的名称
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
}
public class ContextLoader {
// 定义web容器对象
private WebApplicationContext context;
// 将web容器对象存入 servletContext(全局作用域)中.
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}
}
三种获取spring容器对象的方法
public class RegisterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html;utf-8");
String stuID = req.getParameter("id");
String stuName = req.getParameter("name");
String stuEmail = req.getParameter("email");
String stuAge = req.getParameter("age");
// // 方式一 : 创建spring的容器对象,每请求一次就创建一次容器对象,效率很低
/*String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
System.out.println("容器的对象信息:" + ac);*/
// 方式二 自己写代码获取容器对象 :
/* WebApplicationContext ac = null;
// 获取servletContext中的容器对象, 它已经是创建好的,可以直接使用,
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attribute = getServletContext().getAttribute(key);
// 当 attribute 不为空的时候, 就说明已经存在容器对象
if (attribute != null){
ac = (WebApplicationContext)attribute;
}*/
// 方式三 : 通过框架中的方法获取容器对象.其实现原理类似于方式二
ServletContext sc = getServletContext();
WebApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
//获取service
StudentService studentService = (StudentService) ac.getBean("studentService");
Student student = new Student();
student.setId(Integer.parseInt(stuID));
student.setName(stuName);
student.setEmail(stuEmail);
student.setAge(Integer.parseInt(stuAge));
studentService.addStudent(student);
// 给一个注册成功页面
req.getRequestDispatcher("/result.jsp").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}