spring-框架详解
一.Spring-基本概念,原理,基本配置
1.Spring的核心思想(IOC和AOP)
IOC:开发人员在容器中,既代码之外管理对象,给属性赋值,管理依赖
-依赖注入:开发人员在项目中只需要提供对象的名称,对象的创建和赋值都由容器内部实现
AOP:aop就是面向切面编程,将日志、事务等相对独立且重复的功能抽取出来,利用Spring的配置文件或者注解的形式将这些功能织入进去,提高了复用性
2.spring配置文件内容和pom.xml依赖
spring.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">
</beans>
pom.xml依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
3.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.xsd">
<!--spring的主配置文件:主配置文件一般不定义对象
语法: <import resource="其他配置文件的路径">
关键字 "classpath:" 表示类路径(class文件所在目录),在spring的配置文件中要指定其他文件的位置
需要使用classpath,告诉spring到哪去加载读取文件
-->
<!--加载文件的列表-->
<import resource="classpath:spring-DITest.xml"/>
<!---->
<!--
在包含关系的配置文件中,可以通配符(*:表示任意字符)
自己本身配置文件名称不能再通配符范围内
-->
<import resource="classpath:spring-*.xml"></import>
</beans>
二.Spring-IOC(控制反转)
1.bean对象创建的两种方式
1).spring-xml创建bean对象
<!--
一个bean标签只声明一个对象
id:对象的自定义名称,唯一值,Spring通过这个名称找到对象
class:类的全限定名称(不能是接口,因为spring是反射机制创建对象)
-->
<bean id="helloService" class="com.gavin.service.Impl.HelloServiceImpl"></bean>
以下是测试类
测试类中调用getBeansInformation测试了获取beans的信息例如数量以及所有对象名称的方法
package com.gavin.service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
public class HelloServiceTest {
@Test
public void testHelloService(){
// 1.加载Spring的配置文件
//该行代码一执行里面所有Bean对象便创建完成,完成实例的创建(默认调用无参构造方法)
//ClassPathXmlApplicationContext表示从类路径中加载Spring的配置文件(target/classed/srping.xml)
ApplicationContext context = new ClassPathXmlApplicationContext("spring-DITest.xml");
// 2.取出Bean容器中的实例
//getBean("配置文件中bean的id值")
HelloService helloService = (HelloService) context.getBean("helloService");
// 3.调用bean方法
helloService.hello();
}
/**
* 获取beans对象中的信息
*/
@Test
public void getBeansInfomation(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-DITest.xml");
//获取Beans数量
int num = context.getBeanDefinitionCount();
System.out.println("容器中定义的对象的数量:" + num);
//获取对象的名称
String[] beanNames = context.getBeanDefinitionNames();
System.out.println(Arrays.toString(beanNames));
}
}
2).spring-注解创建bean对象(@Component)
<!--
/**
* @Component 创建对象的, 等同于<beans>的功能
* 属性:value 就是对象, 也就是bean的id值
* value的值是惟一的,创建的对象在整个spring容器中就一个
* 位置: 在类的上面
*
* @Component(value = "myPerson")等同于
* <bean id="myPerson" class="com.gavin.entity.Person"/>
* spring中和@Component功能一致,创建对象的注解还有:
* 1.@Repository(用在持久层上):放在dao的实现类上,表示创建dao对象,dao对象是能访问数据库的
* 2.@Service(用在业务层类的上面):放在service的实现类上面,表示创建service对象,service对象
* 是做业务处理,可以有事务等功能的
* 3.@Controller(用在控制器上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,
* 能够控制对象,能够接受用户提交的参数,显示请求的处理结果。
* 以上三个注解的使用语法和@Component一样,都能创建对象,但是这三个注解还有额外的功能。
* @Repository,@Service,@Controller是给项目对象分层的
*
*/
-->
1.在对象的class上面使用@Component注解,Component注解有三种形式,详见下面的代码中的注释
package com.gavin.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//可省略value变为Component("myPerson")
//可换成@Component :不指定对象名称,由spring提供默认名称:类的第一个字母小写person
@Component(value = "myPerson")
public class Person {
}
2.在spring配置文件中声明扫描器
<!--声明组件扫描器(component-scan),组件就是java对象
base-package:指定注解在你的项目中的包名
component-scan工作方式:spring会扫描遍历base-package指定的包
把包中和子包中所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值
-->
<context:component-scan base-package="com.gavin.entity"/>
<!--指定多个包的三种方式
第一种方式:使用多次组件扫描器,指定不同的包
<context:component-scan base-package="com.gavin.entity"/>
<context:component-scan base-package="com.gavin.service"/>
第二种方式:使用分隔符;分隔多个包名
方式:使用多次组件扫描器,指定不同的包
<context:component-scan base-package="com.gavin.entity;com.gavin.service"/>
第三种方式:指定父包
<context:component-scan base-package="com.gavin"/>
-->
2.依赖注入(DI)的两种方式
1)基于xml配置文件的依赖注入
<1>Set注入
set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值:
1)基本类型的set注入
<bean id="xx" class="yy">
<property name="属性名字" value="此属性的值">
一个property只能给一个属性赋值
<property.....>
</bean>
2) 引用类型的set注入
<bean id="xx" class="xx'>
<property name="属性名字" ref="对象bean的id"/>
</bean>
以下是set注入的例子
<!-- #######set注入(只要类有set方法就行,因为只是调用set方法)####### -->
<bean id = "myStudent" class="com.gavin.entity.Student">
<property name="name" value="李四"></property>
<property name="age" value="20"></property>
<property name="email" value="dsadasd"></property>
<property name="school" ref="mySchool"></property>
</bean>
<bean id="mySchool" class="com.gavin.entity.School">
<property name="name" value="中山纪念中学"></property>
<property name="id" value="12"></property>
</bean>
<!--################################################ -->
<2>构造注入
构造注入,spring调用类的参数构造方法创建对象,构造注入使用<constructor-arg>标签:一个标签代表一个参数:
<constructor-arg>属性:
name:表示构造参数名
index:表示构造参数的位置(从0开始)
value:构造参数是基本类型,用value
ref:构造参数是引用类型,用ref,后填写引用类型的bean的id
以下是xml的构造注入的三种形式
<!-- 1.使用name进行构造注入 -->
<bean id = "myStudent2" class="com.gavin.entity.Student">
<constructor-arg name="age" value="12"/>
<constructor-arg name="name" value="gavin"/>
<constructor-arg name="school" ref="mySchool"/>
</bean>
<!-- 2.使用index进行构造注入 -->
<bean id = "myStudent3" class="com.gavin.entity.Student">
<constructor-arg index="0" value="sda"/>
<constructor-arg index="1" value="1"/>
<constructor-arg index="2" ref="mySchool"/>
</bean>
<!-- 3.省略index进行构造注入 -->
<bean id = "myStudent4" class="com.gavin.entity.Student">
<constructor-arg value="sda"/>
<constructor-arg value="1"/>
<constructor-arg ref="mySchool"/>
</bean>
<3>引用类型的自动注入
.引用类型的自动注入:
spring框架可以根据某些规则给引用类型赋值,使用byName,byType
1) byName(按名称注入)java类中引用类型的属性名和spring配置中<bean>的id名
一致,这样容器能够赋值给引用类型
语法:
<bean id="xx" class="xx" autowire="byName">
简单类型属性赋值
</bean>
2) byType(按类型注入)java类中引用类型的数据类型和spring配置中<bean>的class属性同源关系,就能赋值
同源就是一类:
<1>引用类型数据类型和和bean的class属性是一类
<2>引用类型数据类型和bean的class属性呈父子类关系
<2>引用类型数据类型和bean的class属性呈接口现实关系
语法:
<bean id="xx" class="xx" autowire="byType">
简单类型属性赋值
</bean>
注意:该形式只能有个一个该类的bean,否则xml会报红
<!-- ###########引用类型自动注入########## -->
<!--改为byType也行,因为只有一个Student的同源对象-->
<bean id="myStudentAutowire" class="com.gavin.entity.Student" autowire="byName">
<property name="name" value="中山纪念中学"></property>
<property name="age" value="12"></property>
</bean>
<bean id="school" class="com.gavin.entity.School">
<property name="name" value="中山纪念中学"></property>
<property name="id" value="12"></property>
</bean>
<!--##########################################################-->
Student类定义如下
package com.gavin.entity;
public class Student {
private String name;
private int age;
private School school;
public Student(){
System.out.println("调用Student无参构造函数");
}
public Student(String name, int age, School school){
this.name = name;
this.age = age;
this.school = school;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public void setName(String name) {
System.out.println("setName" + name);
this.name = name;
}
public void setAge(int age) {
System.out.println("setAge" + age);
this.age = age;
}
public void setEmail(String email){
System.out.println("调用setEmail方法 + 值为:" + email);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
School类定义如下
package com.gavin.entity;
public class School {
String name;
String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
'}';
}
}
2)基于注解的依赖注入
<1>简单类型注入(Value)
<!--简单类型的属性赋值
@Value:
属性:Value是String类型的,表示简单类型的属性值
位置:1.在属性定义的上面,无需set方法,推荐使用
2.在set方法的上面
-->
package com.gavin.entity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//可省略value变为Component("myPerson")
//可换成@Component :不指定对象名称,由spring提供默认名称:类的第一个字母小写person
@Component(value = "myPerson")
public class Person {
@Value(value = "gavin")
private String name;
@Value(value = "29")
private Integer age;
public String getName() {
return name;
}
//也可以放set方法上
//@Value(value = "gavin")
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
<2>引用类型注入(Autowired)
<!--引用类型赋值
@Autowired: spring框架提供的注解,实现引用类型的赋值
spring中通过注解给引用类型赋值,使用的是自动注入原理,支持byName,byType
@Autowired默认使用的是byType注入,对象必须有Component注解或者在xml中的bean声明,并只能有一个同源
属性:
required:是一个boolean类型的,默认true
required=true:表示引用类型如果赋值失败,程序报错,并终止执行.
required=false:表示引用类型如果赋值失败,程序不报错,值赋值为null
位置:
1)在属性定义的上面,无需set方法,推荐使用
2)在set方法的上面
如果要使用byName方式,需要做的是
1.在属性上面加入@Autowired
2.在属性上面加入@Qualifier(value="")
两顺序可以打乱,谁上谁下都一样
-->
package com.gavin.entity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "myPerson")
public class Person {
//使用Autowired直接修饰的对象必须有Component注解或者在xml中的bean声明,并只能有一个同源
@Autowired
private Person_father person_father;
//byName类型测试
//@Autowired
//@Qualifier(value="my_person_father")
//private Person_father person_father;
}
<3>引用类型注入(Resource)
-
/** * 引用类型 * @Resource :来自jdk中的注解,spring框架提供了对这个注解的功能支持 * 可以使用它给引用类型赋值,使用的也是自动注入原理,支持byName,byType, * 默认是byName * 位置: * 1.在属性定义的上面,无需set方法,推荐使用 * 2.在set方法的上面 * 默认是byName:先试用ByName自动注入,如果byName赋值失败,再使用byType * 如果只使用byName方式,需要增加一个属性name * name的值是bean的id */
package com.gavin.entity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "myPerson") public class Person { //@Resource:默认是byName:先试用ByName自动注入,如果byName赋值失败 //,再使用byType //@Resource(name="person_father")限制只用byName的方式 private Person_father person_father; }
Person_father类
package com.gavin.entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Person_father {
@Value("dsa")
private String name;
@Value("12")
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Person_father{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
3.ApplicationContext的几种不同实现
(1)FileSystemXmlApplicationContext
- 对于FileSystemXmlApplicationContext:
1.没有盘符的是项目工作路径,即项目的根目录;
2.有盘符表示的是文件绝对路径. - 注意:如果要使用classpath路径,需要前缀classpath:
// 用文件系统的路径,默认指项目的根路径
ApplicationContext factory = null;
factory = new FileSystemXmlApplicationContext("src/appcontext.xml");
factory = new FileSystemXmlApplicationContext("webRoot/WEB-INF/appcontext.xml");
// 使用了classpath:前缀,这样,FileSystemXmlApplicationContext也能够读取classpath下的相对路径
factory = new FileSystemXmlApplicationContext("classpath:appcontext.xml");
// 加不加file前缀都是一样的。
factory = new FileSystemXmlApplicationContext("file:F:/workspace/example/src/appcontext.xml");
factory = new FileSystemXmlApplicationContext("F:/workspace/example/src/appcontext.xml");
//同时加载多个配置文件
String[] locations = {"bean1.xml", "bean2.xml", "bean3.xml"};
ApplicationContext ctx = new FileSystemXmlApplicationContext(locations);
(2)ClassPathXmlApplicationContext
-
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext的区别如下:
1.classpath:前缀是不需要的,默认就是指项目的classpath路径下面;
2.如果要使用绝对路径,需要加上file:前缀表示这是绝对路径;
ApplicationContext factory = null;
// 用classpath路径,用ClassPathXmlApplicationContext类时,有没有classpath:前缀都是一样的。
factory = new ClassPathXmlApplicationContext("classpath:appcontext.xml");
factory = new ClassPathXmlApplicationContext("appcontext.xml");
// ClassPathXmlApplicationContext使用了file前缀是可以使用绝对路径的。
factory = new ClassPathXmlApplicationContext("file:F:/workspace/example/src/appcontext.xml");
String[] locations = {"bean1.xml", "bean2.xml", "bean3.xml"};
ApplicationContext ctx = newClassPathXmlApplication(locations);//同时加载多个配置文件。
(3)AnnotationConfigApplicationContext
说明
使用AnnotationConfigApplicationContext可以实现基于Java的配置类加载Spring的应用上下文。避免使用application.xml进行配置。相比XML配置,更加便捷。
AppConfig.java
package com.myapp.config;
import com.myapp.Entitlement;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(name="entitlement")
public Entitlement entitlement() {
Entitlement ent= new Entitlement();
ent.setName("Entitlement");
ent.setTime(1);
return ent;
}
@Bean(name="entitlement2")
public Entitlement entitlement2() {
Entitlement ent= new Entitlement();
ent.setName("Entitlement2");
ent.setTime(2);
return ent;
}
}
@Configuration可理解为用spring的时候xml里面的标签
@Bean可理解为用spring的时候xml里面的标签
Entitlement.java
package com.myapp;
public class Entitlement {
private String name;
private int time;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
}
测试:
JavaConfigTest.java
package com.myapp;
import com.myapp.config.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigTest {
public static void main(String[] arg) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
Entitlement ent = (Entitlement)ctx.getBean("entitlement");
System.out.println(ent.getName());
System.out.println(ent.getTime());
Entitlement ent2 = (Entitlement)ctx.getBean("entitlement2");
System.out.println(ent2.getName());
System.out.println(ent2.getTime());
ctx.close();
}
}
结果:
Entitlement
1
Entitlement2
2
4.spring中Bean单元的属性详解
(1)scope
<1>singleton(单一实例)
此取值时表明容器中创建时只存在一个实例,所有引用此bean都是单一实例。如同每个国家都有一个总统,国家的所有人共用此总统,而这个国家就是一个spring容器,总统就是spring创建的类的bean,国家中的人就是其它调用者,总统是一个表明其在spring中的scope为singleton,也就是单例模型。
此外,singleton类型的bean定义从容器启动到第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活,典型单例模式,如同servlet在web容器中的生命周期。
<2>prototype
spring容器在进行输出prototype的bean对象时,会每次都重新生成一个新的对象给请求方,虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不在拥有当前对象的引用,请求方需要自己负责当前对象后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回请求方该对象的一个新的实例之后,就由这个对象“自生自灭”,最典型的体现就是spring与struts2进行整合时,要把action的scope改为prototype。
如同分苹果,将苹果的bean的scope属性声明为prototype,在每个人领取苹果的时候,我们都是发一个新的苹果给他,发完之后,别人爱怎么吃就怎么吃,爱什么时候吃什么时候吃,但是注意吃完要把苹果核扔到垃圾箱!对于那些不能共享使用的对象类型,应该将其定义的scope设为prototype。
<3>request
再次说明request,session和global session类型只实用于web程序,通常是和XmlWebApplicationContext共同使用。
Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,该对象的生命周期即告结束,如同java web中request的生命周期。当同时有100个HTTP请求进来的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且他们相互之间互不干扰,简单来讲,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。
<4>session
对于web应用来说,放到session中最普遍的就是用户的登录信息,对于这种放到session中的信息,我们可以使用如下形式的制定scope为session:
Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,比request scope的bean会存活更长的时间,其他的方面没区别
<5>global session
作用于集群环境的会话范围,即所有服务器共有的一个session,当不是集群,他是session
三.Spring-AOP(面向切面编程)
1.Spring-AOP概念
1)aop概念
aop是一种面向切面编程的思想,将日志、事务等相对独立且重复的功能抽取出来,利用Spring的配置文件或者注解的形式将这些功能织入进去,提高了复用性.AOP也根据实现方式的不同分为静态AOP代理和动态AOP代理,静态AOP代理是在编译阶段就生成代理类,动态代理则依靠jdk的动态代理机制和cglib,AspectJ是AOP的一种实现方案,他底层使用的是JDK动态代理和CGLIB动态代理.
2)aop中三大概念
<1>.aspect:切面,表示给业务方法增加的功能,一般日志输出,事务,权限等都是切面
<2>.pointcut:切入点,是一个或多个joinpoint的的集合,表示切面功能执行的位置
<3>.advice:通知,也叫增强,表示切面执行的时间,在方法前或方法后
3)什么时候用AOP
<1>.要给一个系统中存在的类修改功能,但是原有的类功能不完善,但是你还有源代码,使用aop增加功能
<2>.要给项目中多个类增加一个相同的功能,就用aop
2.aop的静态代理和动态代理的实现
1)静态代理
首先定义一个用户服务的接口,该接口只有一个方法,用来添加一个用户:
package com.gavin.proxyTest;
public interface IUserService {
public void addUser(String name);
}
为该接口添加一个实现类,实现类中对add方法的实现是打印出“添加了一个用户”:
package com.gavin.proxyTest.Impl;
import com.gavin.proxyTest.IUserService;
public class UserServiceImpl implements IUserService {
@Override
public void addUser(String name) {
System.out.println("添加了一个用户");
}
}
现在,我们有了新的需求,需要对在添加用户钱对参数进行校验并且记录日志,添加成功或失败后也要记录日志,最简单的做法就是更改UserServiceImpl类的add方法,但这显然违反了开闭原则
开闭原则强调软件设计应当对扩展开放,对修改关闭,也就是支持使用扩展的方式来实现变化,而不是修改已有的代码。
既然如此,那么我们可以对UserServiceImpl提供一个代理类,那么我们只需要在代理类里面在实际调用UserServiceImpl的add方法前后,实现对参数的校验和日志记录即可。
package com.gavin.proxyTest;
public class StaticUserServiceProxy implements IUserService {
private IUserService userService;
public StaticUserServiceProxy(IUserService userService){
this.userService = userService;
}
@Override
public void addUser(String name) {
System.out.println("准备添加用户 name: " + name);
if (name == null || name.length() == 0) {
return;
} else {
System.out.println("参数校验成功");
}
userService.addUser(name);
System.out.println("添加用户成功");
}
}
测试方法:
package com.gavin.proxyTest;
import com.gavin.proxyTest.Impl.UserServiceImpl;
public class myTest {
public static void main(String[] args) {
IUserService userService = new StaticUserServiceProxy(new UserServiceImpl());
userService.addUser("gavin");
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cWoPJjHx-1615127549345)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228115618899.png)]
上述就是jdk的静态代理方式,静态代理的原理和实现都比较简单易懂,在编译期就会生成UserService的代理类StaticUserServiceProxy.class。静态代理的优缺点是:
- 优点是可以对调用方隐藏对实现类的细节,解耦了调用方和实际服务方。
- 缺点也很明显,代理类和实现类实现了相同的接口,这就产生了大量的重复代码,增加了维护成本。其次是每个代理类只能代理一个对象,如果接口有多个实现类,我们必须为每个实现类提供一个代理类,当然可以通过代理工厂模式去简化,但如果逻辑差别较大,也会造成代理类的逻辑复杂度。
2)动态代理
静态代理是在编译期已经确定了代理类的类型,而动态代理是在运行期实时生成代理类。动态代理的实现需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持
<1>jdk动态代理
InvocationHandler的定义:
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
Proxy类包含一个静态方法,用来获取一个代理对象:
//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
现在我们提供一个动态代理类实现对UserService的代理:
package com.gavin.proxyTest;
import com.alibaba.fastjson.JSON;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy<T> implements InvocationHandler {
private T target;
public DynamicProxy(T target){
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始调用方法 : " + method.getName() + " 参数 :" + JSON.toJSONString(args));
Object result = null;
try {
result = method.invoke(target, args);
System.out.println("调用成功");
} catch (Exception e) {
System.out.println("发生异常");
}
return result;
}
}
测试方法:
package com.gavin.proxyTest;
import com.gavin.proxyTest.Impl.UserServiceImpl;
import org.junit.Test;
public class DynamicProxyTest {
@Test
public void testDynamicProxy(){
IUserService userService = new UserServiceImpl();
DynamicProxy<IUserService> proxy = new DynamicProxy<>(userService);
IUserService userProxy = (IUserService) proxy.getProxyInstance();
userProxy.addUser("gavin");
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WPRuST0d-1615127549347)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228122130111.png)]
<2>cglib动态代理
CGLIB是一种代码生成库,主要应用在AOP代理中,其原理和jdk的动态代理很像,都是在运行期提供对一个对象的代理类,那之所以存在jdk动态代理的同时还需要CGLIB,就是因为jdk动态代理有个强行要求就是代理类必须有上级接口,也就是说jdk的动态代理本质是对接口的代理。而CGLIB的出现恰好弥补了这一缺点,CGLIB在代理没有接口的类时会生成代理类的子类
3.连接点(Joint Point)和切入点(Point cut)
连接点是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。举例来说,所有定义在你的接口中的方法都可以被认为是一个连接点,如果你在这些方法上使用横切关注点的话。
切入点(切入点)是一个匹配连接点的断言或者表达式。由切入点表达式匹配的连接点的概念是 AOP 的核心。Spring 默认使用 AspectJ 切入点表达式语言。
4.AspectJ的切入点表达式
(1)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(访问权限 方法返回值 方法声明(参数) 异常类型)
(2)切入点表达式要匹配的对象就是目标方法的方法名.所以execution表达式中明显就是方法的签名。注意,表达式中未加粗文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符合
* | 0至多个任意字符 |
---|---|
… | 用在方法参数中,表示任意多个参数,用在包名后,表示当前包及其子包路径 |
+ | 用在接口后,表示当前接口及实现类 |
举例
execution(public * *(..)) 指定切入点为:任意公共方法
execution(* set*(..)) 指定切入点为:任何一个以set开始的方法
execution(* com.gavin.service.*.*(..)) com.gavin.service 包中的任意类的任意方法
execution(* *..service.*.*(..)) 所有带service的包中的方法
5.Aspect基于注解的实现
1)实现步骤
(1)创建maven项目
(2)加入依赖
1.spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
2.aspect依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
3.junit单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
(3)创建目标类,接口和他的实现类
要做的是给类中的方法增加功能
(4)创建切面类,普通类
1.在类的上面加入@Aspect
2.在类中定义方法,方法就是切面要执行的功能代码,在方法的上面加入aspect中的通知注解,例如@Before,有需要指定切入点表达式execution()
(5)创建spring的配置文件:声明对象,把对象交给容器统一管理,声明对象可以使用注解或xml配置文件
1.声明目标对象
2.声明切面类对象
3.声明aspectJ框架中的自动代理生成器标签
自动代理生成器:用来完成代理对象的自动创建功能
(6)创建测试类,从spring容器中获取目标对象(实际就是代理对象).通过代理执行方法,实现aop的功能增强
2).Aspect的注解详解
<1>@Aspect注解
package com.gavin.AspectTest;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect 是aspectj框架中的注解
* 作用:表示当前类是切面类
* 切面类:用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 定义方法,方法是实现切面功能的。
* 方法的定义要求:
* 1.公共方法 public
* 2.方法没有返回值
* 3.方法名称自定义
* 3.方法可以有参数,也可以没有参数
* 如果有参数,采纳数不是自定义的,有几个参数类型可以使用
*/
}
<2>@Before前置通知,方法有JoinPoint参数
切面类
package com.gavin.AspectTest;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* @Before:前置通知注解
* 属性:value,是切入点表达式,表示切面的功能执行的位置
* 位置:在方法的上面
* 特点:
* 1.在目标方法之前执行
* 2.不会改变目标方法的执行结果
* 3.不会影响目标方法执行效率
*/
/**
* execution变形:
* @Before(value = "execution(void com.gavin.AspectTest.Impl.SomeServiceImpl.doSome(String))")
* @Before(value = "execution(void *..SomeServiceImpl.doSome(String))")
* @Before(value = "execution(void *..SomeServiceImpl.doSome(..))")
* @Before(value = "execution(void *..SomeServiceImpl.do*(..))")
*/
@Before(value = "execution(public void com.gavin.AspectTest.Impl.SomeServiceImpl.doSome(String))")
public void myBefore(){
System.out.println("前置通知切面功能:在目标方法之前输出执行时间" + new Date());
}
/**
* 针对该通知方法中的参数: JoinPoint
* JoinPoint: 业务方法,要加入切面功能的业务方法
* 作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参.
* 如果你的切面功能中需要用到方法的信息,就加入JoinPoint
* 这个JoinPoint参数的值是由框架赋予,必须是第一个位置的参数
*/
@Before(value = "execution(public void com.gavin.AspectTest.Impl.SomeServiceImpl.doSome(String))")
public void myBefore2(JoinPoint jp){
System.out.println("方法的签名(定义)=" + jp.getSignature());
System.out.println("方法的名称" + jp.getSignature().getName());
//获取方法的实参
Object[] args = jp.getArgs();
for (Object obj:args
) {
System.out.println("参数:" + obj);
}
System.out.println("前置通知切面功能2:在目标方法之前输出执行时间" + new Date());
}
//可加多个前置通知
//@Before(value = "execution(void ..SomeServiceImpl.do*(..))")
//public void myBefore3(){
// System.out.println("前置通知切面功能2:在目标方法之前输出执行时间" + new Date());
//}
}
spring.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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--声明目标对象-->
<bean id="someService" class="com.gavin.AspectTest.Impl.SomeServiceImpl"/>
<!--声明切面类对象-->
<bean id="myAspect" class="com.gavin.AspectTest.MyAspect"/>
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。创建代理对象是在内存中
实现的,修改目标对象的内存中的结构.创建为代理对象,所以目标对象就是被修改后的代理对象
aspect-autoproxy会扫描spring容器中的所有对象中带有@Aspect注解的对象,然后找打代理方法中execution中的目标对象,一次性都生成代理对象
-->
<aop:aspectj-autoproxy/>
</beans>
测试类及执行结果
package com.gavin.AspectTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void aspectTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
ISomeService someService = (ISomeService)applicationContext.getBean("someService");
someService.doSome("测试");
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wvQuCXa-1615127549348)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210227201906650.png)]
<3>@AfterReturnning后置通知,注解有returning属性
切面类
package com.gavin.AspectTest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
@Aspect
public class MyAspect {
/*
* @AfterReturning:后置通知
* 属性:1.value 切入点表达式
* 2.returning 自定义的变量,表示目标方法的返回值
* 自定义变量名必须和通知方法的形参名一样
* 位置:在方法定义的上面
* 特点:
* 1.在目标方法之后执行的.
* 2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
* 3.可以修改这个返回值
* JoinPoint可加也可不加 要获取方法的信息时可加,可参考前置方法@Before的详解
* 但是要注意:JoinPoint只能用于第一个参数,放在第二个参数位置会报错
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",
returning = "res")
//returning="res"相当于Object res = doSome();
public void myAfterReturning(JointPoint jp,Object res){
//Object res是目标方法执行后的返回值,根据返回值做你的切面的功能处理
System.out.println("后置通知,在目标方法之后执行的,获取的返回值是:"+res);
//可以将res内的属性修改来改边返回值,例如返回值是一个对象,可以修改对象的属性
Student student = (Student) res;
student.setAge(10);
}
}
代理对象类
package com.gavin.AspectTest.Impl;
import com.gavin.AspectTest.ISomeService;
import com.gavin.AspectTest.Student;
public class SomeServiceImpl implements ISomeService {
public Student doSome(Student student) {
System.out.println("doSome方法执行");
return student;
}
}
Student类
package com.gavin.AspectTest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("myStudent")
public class Student {
@Value("gavin")
private String name;
@Value("12")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
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:aop="http://www.springframework.org/schema/aop"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.gavin.AspectTest"/>
<!--声明目标对象-->
<bean id="someService" class="com.gavin.AspectTest.Impl.SomeServiceImpl"/>
<!--声明切面类对象-->
<bean id="myAspect" class="com.gavin.AspectTest.MyAspect"/>
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。创建代理对象是在内存中
实现的,修改目标对象的内存中的结构.创建为代理对象,所以目标对象就是被修改后的代理对象
aspect-autoproxy会把spring容器中的所有目标对象,一次性都生成代理对象
-->
<aop:aspectj-autoproxy/>
</beans>
测试方法:
package com.gavin.AspectTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void aspectTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
ISomeService someService = (ISomeService)applicationContext.getBean("someService");
Student res = someService.doSome((Student) applicationContext.getBean("myStudent"));
System.out.println(res);
}
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hVXCiBL8-1615127549349)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210227222405721.png)]
<4>@Around环绕通知,增强方法有ProceeedingJoinPoint参数
package com.gavin.AspectTest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* 环绕通知方法的定义格式
* 1.public
* 2.必须有一个返回值,推荐使用Object
* 3.方法名称自定义
* 4.方法有参数,固定的参数 ProceedingJointPoint
*/
/**
* @Around 环绕通知
* 属性: value 切入点表达式
* 位置:在方法的定义上面
* 特点:
* 1.他是功能最强的通知
* 2.在目标方法的前和后都能增强功能
* 3.控制目标方法是否被调用执行
* 4.修改原来的目标方法的执行结果, 影最后的调用结果
* 环绕通知,等同于jdk动态代理的InvocationHandler接口
* <p>
* 参数: ProceedingJoinPoint等同于jdk动态代理中的Method
* 作用:执行目标方法类
* 返回值: 就是目标方法的执行结果,可以被修改。
*
* 环绕通知通常用于做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后
* 提交事务
*/
@Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// ProceedingJoinPoint继承自JoinPoint
//获取参数列表
Object[] args = pjp.getArgs();
//1.目标方法调用
Object result = null;
System.out.println("环绕通知:在目标方法之前,输出时间:" + new Date());
result = pjp.proceed();//等同于method.invoke();
System.out.println("环绕通知:在目标方法之后,提交事务:");
//2.在目标方法的前或者后加入功能
//返回目标方法的执行结果,result值可修改
return result;
}
}
其他类和@AfterReturning的详解中的类一致
测试方法:
package com.gavin.AspectTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void aspectTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
ISomeService someService = (ISomeService)applicationContext.getBean("someService");
Student res = someService.doSome((Student) applicationContext.getBean("myStudent"));
System.out.println(res);
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTQF0dDD-1615127549350)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210227231909216.png)]
<5>@AfterThrowing异常通知,注解中有throwing属性
切面类:
package com.gavin.AspectTest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* 异常通知方法的定义格式
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法有一个Exception参数,如果还有就是JoinPoint
*/
/**
* @AfterThrowing:异常通知
* 属性:1.value 切入点表达式
* 2.throwing 自定义的变量,表示目标方法抛出的异常对象.
* 变量名必须和方法的参数名一样
* 特点:
* 1.在目标方法抛出异常时执行
* 2.可以作异常的监控程序,监控目标方法执行时是不是有异常
* 如果有异常,可以发送邮件,短信进行通知
*
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSome(..))", throwing = "e")
public void myAround(Exception e){
System.out.println("异常通知,方法发生异常:"+ e);
}
}
代理对象类:
package com.gavin.AspectTest.Impl;
import com.gavin.AspectTest.ISomeService;
import com.gavin.AspectTest.Student;
public class SomeServiceImpl implements ISomeService {
public Student doSome(Student student) {
System.out.println("doSome方法执行");
double res = 1/0;
return student;
}
}
测试方法:
package com.gavin.AspectTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void aspectTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
ISomeService someService = (ISomeService)applicationContext.getBean("someService");
Student res = someService.doSome((Student) applicationContext.getBean("myStudent"));
System.out.println(res);
}
}
其他需要的类与@AfterReturning中的类一致
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehuMU1W5-1615127549351)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210227233013814.png)]
<6>@After最终通知
切面类:
package com.gavin.AspectTest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* 最终通知方法的定义格式
* 1.public
* 2.没有返回值
* 3.方法名称自定义
* 4.方法没有参数,如果还有就是JoinPoint
*/
/**
* @After:最终通知
* 属性: value 切入点表达式
* 位置: 在方法的上面
* 特点:
* 1.总是会执行(就是报错也会执行)
* 2.在目标方法之后执行
*/
@After(value = "execution(* *..SomeServiceImpl.doSome(..))")
public void myAround() {
System.out.println("执行最终通知");
//一般是做资源清除工作
}
}
其他类和@AfterReturning详解中的一致
测试方法:
package com.gavin.AspectTest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void aspectTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
ISomeService someService = (ISomeService)applicationContext.getBean("someService");
Student res = someService.doSome((Student) applicationContext.getBean("myStudent"));
System.out.println(res);
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGqb8HEw-1615127549351)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210227233914132.png)]
<7>@Pointcut定义切入点
package com.gavin.AspectTest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* @Pointcut 定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,
* 可以使用Pointcut
* 属性:value 切入点表达式
* 位置:在自定义的方法上面
* 特点:
* 当使用Pointcut定义在一个方法上面,此时这个方法的名称就是切入点表达式
* 的别名,其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式
*/
@Before(value = "mypt()")
public void myBefore(){
System.out.println("调用前置方法");
}
@After(value = "mypt()")
public void myAfter() {
System.out.println("执行最终通知");
//一般是做资源清除工作
}
//一般是私有 因为无需被外界调用
@Pointcut(value = "execution(* *..SomeServiceImpl.doSome(..))")
private void mypt(){
//无需代码
}
}
3)关于AspectJ底层使用的代理方式
<1>当代理对象有接口时,使用的是jdk动态代理
<2>带代理对象无接口时,使用cglib动态代理
<3>如果需要强制款姐使用cglbi动态代理,可在xml配置文件添加如下代码
<!--
如果你期望目标类有接口使用cglib动态代理,加如下代码
-->
<aop:scoped-proxy proxy-target-class="true"/>
四.Spring整合Mybatis
2.mybatis的使用步骤和spring集成项目创建步骤
1)mybatis单独使用步骤
1).创建maven项目
2)加入maven依赖
<1>spring依赖
<2>mybatis提来
<3>mysql驱动
<4>spring的事务的依赖
<5>mybatis和spring集成的依赖:mybatis官方体用的,用来在spring项目中创建mybatis的SqlSessionFactory,dao对象
3)创建实体类
4)创建dao接口和mapper文件
5)创建mybatis主配置文件
6)创建Service接口和实现类
7)创建spring的配置文件:声明mybatis的对象交给spring创建
1.数据源
2.SqlSessionFactory
3.Dao对象
mybatis主配置文件内容(在spring框架中不用,直接使用spring的自带连接池druid配置)
<!--数据库信息-->
<environment id="mydev">
<transactionManager type="JDBC">
<dataSource type="POOLED">
<!--数据库的驱动类名-->
<property name="driver" value="com.mysql.hdbc.Driver"/>
<!--连接数据库的url字符串-->
<property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
<!--访问数据库的用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="123"/>
</dataSource>
</transactionManager>
</environment>
<!--mapper文件位置-->
<mappers>
<mapper resource="com/gavin/dao/UserDao.xml"/>
</mappers>
2)创建spring集成Mybatis项目
<1>.pom.xml中引入mybatis依赖和配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AspectDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<!--目的是把/srv/main/java目录中的xml文件包含到输出结果中,输出到classed中-->
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes><!--包括目录下的.properties.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<!--指定jdk版本-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--spring框架所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--测试单元所需依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!--mybatis依赖-->
<!--做psring事务用到的-->
<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>
</project>
<2>创建实体类
package com.gavin.domain;
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;
}
}
<3>创建dao接口和mapper文件
只需创建接口即可,druid会自动创建实现类
dao接口:StudentDao
package com.gavin.dao;
import com.gavin.domain.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.gavin.dao.StudentDao">
<insert id="insertStudent"> <!--方法名称的第一个字母小写-->
insert into student values(#{id},#{name},#{email},#{age})
</insert>
<select id="selectStudents" resultType="com.gavin.domain.Student">
select id,name,email,age from student order by id desc
</select>
</mapper>
项目结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mNk36WEz-1615127549352)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228155712752.png)]
<4>配置mybatis的主配置文件:mybatis.xml
该文件放在和spring配置文件同一级目录既resources目录下
<?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:控制mybatis全局行为-->
<!--
<settings>
设置日志输出
<setting name="logImple" value="STUOUT_LOGGING"/>
</settings>
-->
<!--设置别名-->
<typeAliases>
<!--name:实体类所在的包名-->
<package name="com.gavin.domain"/>
</typeAliases>
<!--Sql mapper(sql映射文件)的位置-->
<mappers>
<!--name:是包名,这个包中的所有mapper.xml一次都能加载-->
<package name="com.gavin.dao"/>
</mappers>
</configuration>
<5>创建Service接口和实现类
接口:
package com.gavin.service;
import com.gavin.domain.Student;
import java.util.List;
public interface IStudentService {
int addStudent(Student student);
List<Student> queryStudent();
}
实现类:
package com.gavin.service.Impl;
import com.gavin.dao.IStudentDao;
import com.gavin.domain.Student;
import com.gavin.service.IStudentService;
import java.util.List;
public class StudentService implements IStudentService {
private IStudentDao studentDao;
//使用set注入赋值
public void setStudentDao(IStudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
int nums = studentDao.insertStudent(student);
return nums;
}
@Override
public List<Student> queryStudent() {
List<Student> list = studentDao.selectStudents();
return list;
}
}
<6>创建spring的配置文件:声明mybatis的对象交给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:aop="http://www.springframework.org/schema/aop"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--druid的配置详细可参考:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE-->
<!--配置dataSource数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init"
destroy-method="close">
<!--set注入给DruidDataSource提供连接数据库信息-->
<property name="url" value="jdbc:mysql://localhost:3306/spring_test"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
<property name="maxActive" value="20"/>
</bean>
<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory类-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把数据库连接池付给了dataSource属性-->
<property name="dataSource" ref="dataSource"/>
<!--mybatis主配置文件的位置
configLocation属性是Resource类型,读取配置文件
它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--创建dao对象,使用SqlSession(来自上面的sqlSessionFactory)的getMapper(StudentDao.class)
MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--指定包名,包名是dao接口所在的包名
MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次
getMapper()方法,得到每个接口的dao对象
创建好的dao对象放入到spring的容器中,dao对象默认名称是接口名首字母小写
-->
<property name="basePackage" value="com.gavin.dao"/>
</bean>
<bean id="studentService" class="com.gavin.service.Impl.StudentService">
<property name="studentDao" ref="studentDao"/>
</bean>
</beans>
<7>测试
1.插入测试
package com.gavin;
import com.gavin.dao.StudentDao;
import com.gavin.domain.Student;
import com.gavin.service.IStudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void myBatisTest(){
String config = "spring.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
IStudentService service = (IStudentService) applicationContext.getBean("studentService");
Student student = new Student(2,"gavin","123@.com",12);
int num = service.addStudent(student);
System.out.println(num);
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ulfIzvny-1615127549353)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228160648816.png)]
2.查询测试
@Test
public void queryTest(){
String config = "spring.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
IStudentService service = (IStudentService) applicationContext.getBean("studentService");
List<Student> list = service.queryStudent();
for (Student stu:list
) {
System.out.println(stu);
}
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74nUautn-1615127549353)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228161119739.png)]
3).使用.propertied代替mybatis.xml主配置文件说明
在上面使用步骤中的<4>中的mybatis主配置文件中,datasource的配置可以换成.properties文件
spring.properties
jdbc.url=jdbc:mysql://localhost:3306/spring_test
jdbc.username=root
jdbc.passwd=123
jdbc.maxActive=20
在spring.xml中添加该行内容:
<context:property-placeholder location="classpath:spring.properties"/>
spring.xml中dataSource内容修改为如下
<!--
把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
spring知道jdbc.properties的位置
-->
<context:property-placeholder location="classpath:spring.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init"
destroy-method="close">
<!--set注入给DruidDataSource提供连接数据库信息-->
<!--
使用属性配置文件中的数据,语法
-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.passwd}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
五.Spring事务
1.事务概念和使用
<1>什么是事务
事务是指一组sql语句的集合,集合中有多条sql语句,可能是Insert,update,select,delete,我们希望这些多个sql语句都能成功,或者都失败,这些sql语句的执行时一致的,作为一个整体执行.
<2>什么时候使用事务
操作涉及多个表,或者多个sql语句的insert,update,delete,需要保证这些语句都是成功才能完成,或者都失败,保证操作是符合要求的,事务放于service类的业务方法上
<3>jdbc和mybatis,hibernate如何处理事务
jdbc处理事务:Connection conn;conn,commit();conn.rollback();
mybatis处理事务:SqlSession.commit();SqlSession.rollback();
hibernate处理事务.session.commit();session.rollback();
<4>事务的隔离级别,超时时间,传播行为
spring处理事务模型是固定的,把事务使用的信息提供给spring即可
1.事务的隔离级别,有4个值
https://www.cnblogs.com/jyroy/p/11106609.html :隔离级别详解
DEFAULT:采用DB默认的事务隔离级别,Mysql的默认为REPEATABLE_READ,Oracle默认为READ_COMMITTED
* READ_UNCOMMITTED:读未提交,未解决任何并发问题
* READ_COMMITED:读已提交,解决脏读,存在不可重复读与幻读
* REPEATAVLE_READ:可重复度.解决脏读.不可重复读,存在幻读
* SERIALIZABLE:串行化,不存在并发问题
2.事务的超时时间:表示一个方法最长的执行时间,如果方法执行时间超过,事务就回滚,单位是秒,整数值,默认是-1
3.事务的传播行为:控制业务方法是不是有事务的,是什么样的事务
7个传播行为,表示你的业务方法调用时事务在方法之间如何使用
需掌握3个:
PROPAGATION_REQUIRED:
指定方法必须在事务内执行,若当期那存在事务,就加入到当前事务;若当前没有事务,则创建一个新事务,比如该传播行为加
在doOther()方法上,若doSome()方法在调用doOther()方法时就是在事务内运行,则doOther()方法的执行也加入到该事务
内执行.若doSome()方法在调用doOther时方法没有在事务内执行,则doOther()方法会创建一个事务,并在其中执行
PROPAGATION_REQUIRES_NEW:
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕
PROPAGATION_SUPPORTS:
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行
<5>spring如何处理事务(事务的使用)
spring提供一种处理事务的统一模型,能使用统一步骤完成多种不同数据库访问技术的事务处理,既封装jdbc,mybatis,hibernate
1)使用@Transactional注解处理事务
注解方案处理事务适合中小项目使用,使用@Transactional注解增加事务,这是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务,可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等
(1)在spring配置文件声明事务管理器对象和绑定数据源
<!--1.声明事务管理器-->
<!--
mybatis访问数据库-org.springframework.jdbc.datasource.DataSourceTransactionManager
hibernate访问数据库-HibernateTransactionManager
-->
<bean id="transactionalManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
(2)开启事务注解驱动,告诉spring框架,要使用注解的方式管理事务
spring事务注解驱动原理:
使用aop机制,创建@Transactional所在类的代理对象,给方法加入事务的功能,
然后在你的业务方法执行前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
@Around("业务方法的签名")
public Object myAround(){
//spring帮你开启事务
try{
业务方法
spring的事务管理.commit()
}
catch(Exception e){
spring的事务管理.rollback()
}
}
<!--2.开始事务注解驱动,注意要选择后面是schema/tx的annotation
transactionalManager代表事务管理器对象的id
-->
<tx:annotation-driven transaction-manager="transactionalManager"/>
(3)在业务方法(必须是public)上加入@Transactional并声明其属性
@Transactional属性:
propagation:用于设置事务传播属性.该属性类型为propagation枚举,默认值为Propagation.REQUIRED
isolation:用于设置事务的隔离界别.该属性类型为Isolation枚举,默认值为Isolation.DEFAULT
readOnly:用于设置该方法对数据库的操作是否是只读,属性为boolean,默认值为false
rollbackFor:指定需要回滚的异常类,类型为class[],默认值为空数组.当然,若只有一个异常类时,可以不用数组
rollbackForClassName:指定需要回滚的异常类类名,类型为String[],默认为空数组,.当然,若只有一个异常类时,可以不用数组
noRollbackFor:指定不需要回滚的异常类.类型为class[],默认为空数组,.当然,若只有一个异常类时,可以不用数组
noRollbackForClassName:指定不需要回滚的异常类类名,类型为String[],默认为空数组,.当然,若只有一个异常类时,可以不用数组
需要注意的是:@Transactional若用在方法上,只能用于public方法上,对于其他非public方法,如果加上了注解@Transactional,虽然Spring不会报错,但不会将指定事务织入到该方法中,因为spring会忽略掉所有非public上的这个注解
2)使用AspectJ处理事务(基于xml配置)
AspectJ方法适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectJ框架功能,在spring配置文件中声明类
,方法需要的事务.这种方式业务员方法和事务配置完全分离
实现步骤:
(1)加入aspectJ的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
(2)在spring配置文件声明事务管理器对象和绑定数据源
<!--1.声明事务管理器-->
<bean id="transactionalManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
(3)声明业务方法和事务属性
<!-- 2.声明业务方法的事务属性(隔离级别,传播行为,超时时间)
id:自定义名称,表示<tx:advicce>和</tx:advice>之间的配置内容的id
transaction-manager:事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionalManager">
<!--tx:attribute:配置事务属性-->
<tx:attributes>
<!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称,
1)完整的方法名称,不带有包和类
2)方法可以使用通配符,*表示任意字符
propagation:传播行为,枚举值
isolation:隔离级别
rollback0for:你指定的异常类名,全限定类名。发生异常一定回滚
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="com.gavin.excep.NotEnoughException"/>
<!--可以使用通配符来指定很多的方法如下-->
<!--<tx:method name="add*"/>-->
</tx:attributes>
</tx:advice>
<!--如果需要指定哪些包中类需要使用事务,需要配置aop-->
<aop:config>
<!--配置切入点表达式:指定哪些包中类需要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式
比如要指定下面几个包的service
com.gavin.service
com.john.service
com.xiaoming.service
下面表达式意思是所有service包下的类及其子包的类的所有方法
-->
<aop:pointcut id="myPointcut" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器:关联advice和pointcut
advice-ref:通知,上面tx:advice的id
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/>
</aop:config>
2.使用@Transactional处理事务实践(购买商品项目)
<1>分析功能需求
实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存
<2>创建数据库表
创建两个数据库表sale,goods
sale销售表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsWydJ0k-1615127549354)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228220933334.png)]
goods表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13jccidf-1615127549354)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221001369.png)]
goods表数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIRXNd4E-1615127549355)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221120569.png)]
<3>配置pom.xml文件的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AspectDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<!--目的是把/srv/main/java目录中的xml文件包含到输出结果中,输出到classed中-->
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes><!--包括目录下的.properties.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<!--指定jdk版本-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--spring框架所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--测试单元所需依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!--mybatis依赖-->
<!--做psring事务用到的-->
<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>
</project>
<4>创建实体类
Sale实体类:
package com.gavin.domain;
import java.util.Date;
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getGid() {
return gid;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public Integer getNums() {
return nums;
}
public void setNums(Integer nums) {
this.nums = nums;
}
}
Goods实体类:
package com.gavin.domain;
import java.util.Date;
public class Goods {
private Integer id;
private String name;
private Integer amount;
private Float price;
private Date update_time;
public Date getUpdate_time() {
return update_time;
}
public void setUpdate_time(Date update_time) {
this.update_time = update_time;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
}
<5>创建dao和mapper文件
SaleDao:
package com.gavin.dao;
import com.gavin.domain.Sale;
public interface SaleDao {
int insertSale(Sale sale);
}
<?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.gavin.dao.SaleDao">
<insert id="insertSale">
insert into sale(gid,nums,date) values(#{gid},#{nums},#{date})
</insert>
</mapper>
GoodsDao:
package com.gavin.dao;
import com.gavin.domain.Goods;
public interface GoodsDao {
int updateGoods(Goods goods);
Goods selectGoods(Integer id);
}
<?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.gavin.dao.GoodsDao">
<select id="selectGoods" resultType="com.gavin.domain.Goods">
select id,name,amount,price,update_time from goods where id=#{gid}
</select>
<update id="updateGoods">
update goods set amount = amount - #{amount},update_time = #{update_time} where id=#{id}
</update>
</mapper>
<6>创建spring的配置文件
spring.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: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">
<context:property-placeholder location="classpath:spring.properties"/>
<bean id="dataSource" 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.passwd}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.gavin.dao"/>
</bean>
<bean id="buyGoodsServiceImpl" class="com.gavin.service.Impl.BuyGoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
<!--使用spring的事务处理-->
<!--1.声明事务管理器-->
<bean id="transactionalManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.开始事务注解驱动,注意要选择后面是schema/tx的annotation
transactionalManager代表事务管理器对象的id
使用@Transactional注解方式才需要下面这段
-->
<tx:annotation-driven transaction-manager="transactionalManager"/>
</beans>
spring.properties
jdbc.url=jdbc:mysql://localhost:3306/spring_test
jdbc.username=root
jdbc.passwd=123
jdbc.maxActive=20
<7>创建service业务类
接口:
package com.gavin.service;
public interface BuyGoodsService {
void buy(Integer goodsId, Integer nums);
}
实现:
package com.gavin.service.Impl;
import com.gavin.dao.GoodsDao;
import com.gavin.dao.SaleDao;
import com.gavin.domain.Goods;
import com.gavin.domain.Sale;
import com.gavin.excep.NotEnoughException;
import com.gavin.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private GoodsDao goodsDao;
private SaleDao saleDao;
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class, NotEnoughException.class
}
)
//@Transactional 也可直接使用这个,等效的,默认是抛出运行时异常回滚
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("buy方法开始");
//记录销售信息,想sale表添加记录
Date time_cur = new Date();
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
sale.setDate(time_cur);
saleDao.insertSale(sale);
//更新库存
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NotEnoughException("商品不存在");
}else if(goods.getAmount() < nums){
throw new NotEnoughException("编号L" + goodsId + "商品库存不足");
}
//修改库存
Goods buyGoods = new Goods();
buyGoods.setAmount(nums);
buyGoods.setId(goodsId);
buyGoods.setUpdate_time(time_cur);
goodsDao.updateGoods(buyGoods);
System.out.println("buy方法完成");
}
}
<8>进行测试
package com.gavin;
import com.gavin.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void buyTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BuyGoodsService buyGoodsService =(BuyGoodsService)applicationContext.getBean("buyGoodsServiceImpl");
buyGoodsService.buy(1001,1000);
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YBeXVLFo-1615127549355)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221148161.png)]
3.使用AspectJ方式处理事务实践
<1>分析功能需求
实现购买商品,模拟用户下订单,向订单表添加销售记录,从商品表减少库存
<2>创建数据库表
创建两个数据库表sale,goods
sale销售表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYUemi08-1615127549356)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228220933334.png)]
goods表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A97wFlap-1615127549356)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221001369.png)]
goods表数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ESriFKzN-1615127549357)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221120569.png)]
<3>配置pom.xml文件的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AspectDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<!--目的是把/srv/main/java目录中的xml文件包含到输出结果中,输出到classed中-->
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes><!--包括目录下的.properties.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<!--指定jdk版本-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--spring框架所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring-AOP所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--测试单元所需依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!--mybatis依赖-->
<!--做psring事务用到的-->
<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>
</project>
<4>创建实体类
Sale实体类:
package com.gavin.domain;
import java.util.Date;
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getGid() {
return gid;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public Integer getNums() {
return nums;
}
public void setNums(Integer nums) {
this.nums = nums;
}
}
Goods实体类:
package com.gavin.domain;
import java.util.Date;
public class Goods {
private Integer id;
private String name;
private Integer amount;
private Float price;
private Date update_time;
public Date getUpdate_time() {
return update_time;
}
public void setUpdate_time(Date update_time) {
this.update_time = update_time;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
}
<5>创建dao和mapper文件
SaleDao:
package com.gavin.dao;
import com.gavin.domain.Sale;
public interface SaleDao {
int insertSale(Sale sale);
}
<?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.gavin.dao.SaleDao">
<insert id="insertSale">
insert into sale(gid,nums,date) values(#{gid},#{nums},#{date})
</insert>
</mapper>
GoodsDao:
package com.gavin.dao;
import com.gavin.domain.Goods;
public interface GoodsDao {
int updateGoods(Goods goods);
Goods selectGoods(Integer id);
}
<?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.gavin.dao.GoodsDao">
<select id="selectGoods" resultType="com.gavin.domain.Goods">
select id,name,amount,price,update_time from goods where id=#{gid}
</select>
<update id="updateGoods">
update goods set amount = amount - #{amount},update_time = #{update_time} where id=#{id}
</update>
</mapper>
<6>创建spring的配置文件
spring.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: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">
<context:property-placeholder location="classpath:spring.properties"/>
<bean id="dataSource" 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.passwd}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.gavin.dao"/>
</bean>
<bean id="buyGoodsServiceImpl" class="com.gavin.service.Impl.BuyGoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
<!--使用spring的事务处理-->
<!--1.声明事务管理器-->
<bean id="transactionalManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2.声明业务方法的事务属性(隔离级别,传播行为,超时时间)
id:自定义名称,表示<tx:advicce>和</tx:advice>之间的配置内容的id
transaction-manager:事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionalManager">
<!--tx:attribute:配置事务属性-->
<tx:attributes>
<!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称,
1)完整的方法名称,不带有包和类
2)方法可以使用通配符,*表示任意字符
propagation:传播行为,枚举值
isolation:隔离级别
rollback0for:你指定的异常类名,全限定类名。发生异常一定回滚
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="com.gavin.excep.NotEnoughException"/>
<!--可以使用通配符来指定很多的方法如下-->
<!--<tx:method name="add*"/>-->
</tx:attributes>
</tx:advice>
<!--如果需要指定哪些包中类需要使用事务,需要配置aop-->
<aop:config>
<!--配置切入点表达式:指定哪些包中类需要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式
比如要指定下面几个包的service
com.gavin.service
com.john.service
com.xiaoming.service
下面表达式意思是所有service包下的类及其子包的类的所有方法
-->
<aop:pointcut id="myPointcut" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器:关联advice和pointcut
advice-ref:通知,上面tx:advice的id
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/>
</aop:config>
</beans>
spring.properties
jdbc.url=jdbc:mysql://localhost:3306/spring_test
jdbc.username=root
jdbc.passwd=123
jdbc.maxActive=20
<7>创建service业务类
接口:
package com.gavin.service;
public interface BuyGoodsService {
void buy(Integer goodsId, Integer nums);
}
实现:
package com.gavin.service.Impl;
import com.gavin.dao.GoodsDao;
import com.gavin.dao.SaleDao;
import com.gavin.domain.Goods;
import com.gavin.domain.Sale;
import com.gavin.excep.NotEnoughException;
import com.gavin.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private GoodsDao goodsDao;
private SaleDao saleDao;
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("buy方法开始");
//记录销售信息,想sale表添加记录
Date time_cur = new Date();
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
sale.setDate(time_cur);
saleDao.insertSale(sale);
//更新库存
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NotEnoughException("商品不存在");
}else if(goods.getAmount() < nums){
throw new NotEnoughException("编号L" + goodsId + "商品库存不足");
}
//修改库存
Goods buyGoods = new Goods();
buyGoods.setAmount(nums);
buyGoods.setId(goodsId);
buyGoods.setUpdate_time(time_cur);
goodsDao.updateGoods(buyGoods);
System.out.println("buy方法完成");
}
}
<8>进行测试
package com.gavin;
import com.gavin.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void buyTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
BuyGoodsService buyGoodsService =(BuyGoodsService)applicationContext.getBean("buyGoodsServiceImpl");
buyGoodsService.buy(1001,1000);
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NIUsSOb6-1615127549357)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221148161.png)]
六.Web项目中使用spring
1.使用步骤
(1)创建maven项目并选择webapp
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTE0b537-1615127549358)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228235024452.png)]
(2)加入相关依赖
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes><!--包括目录下的.properties.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<!--指定jdk版本-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
依赖:
<!--spring框架所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring-AOP所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!--jsp依赖-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
<!--mybatis依赖-->
<!--做psring事务用到的-->
<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>
(3)整合mybatis配置和相关代码
<1>创建数据库表
创建两个数据库表sale,goods
sale销售表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQTVOXPb-1615127549358)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228220933334.png)]
goods表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LXN9foj-1615127549359)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221001369.png)]
goods表数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhZNzwaP-1615127549359)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210228221120569.png)]
<2>配置pom.xml文件的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AspectDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<!--目的是把/srv/main/java目录中的xml文件包含到输出结果中,输出到classed中-->
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes><!--包括目录下的.properties.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<!--指定jdk版本-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--spring框架所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring-AOP所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--测试单元所需依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!--mybatis依赖-->
<!--做psring事务用到的-->
<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>
</project>
<3>创建实体类
Sale实体类:
package com.gavin.domain;
import java.util.Date;
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getGid() {
return gid;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public Integer getNums() {
return nums;
}
public void setNums(Integer nums) {
this.nums = nums;
}
}
Goods实体类:
package com.gavin.domain;
import java.util.Date;
public class Goods {
private Integer id;
private String name;
private Integer amount;
private Float price;
private Date update_time;
public Date getUpdate_time() {
return update_time;
}
public void setUpdate_time(Date update_time) {
this.update_time = update_time;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
}
<4>创建dao和mapper文件
SaleDao:
package com.gavin.dao;
import com.gavin.domain.Sale;
public interface SaleDao {
int insertSale(Sale sale);
}
<?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.gavin.dao.SaleDao">
<insert id="insertSale">
insert into sale(gid,nums,date) values(#{gid},#{nums},#{date})
</insert>
</mapper>
GoodsDao:
package com.gavin.dao;
import com.gavin.domain.Goods;
public interface GoodsDao {
int updateGoods(Goods goods);
Goods selectGoods(Integer id);
}
<?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.gavin.dao.GoodsDao">
<select id="selectGoods" resultType="com.gavin.domain.Goods">
select id,name,amount,price,update_time from goods where id=#{gid}
</select>
<update id="updateGoods">
update goods set amount = amount - #{amount},update_time = #{update_time} where id=#{id}
</update>
</mapper>
<5>创建spring的配置文件
spring.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: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">
<context:property-placeholder location="classpath:spring.properties"/>
<bean id="dataSource" 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.passwd}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.gavin.dao"/>
</bean>
<bean id="buyGoodsServiceImpl" class="com.gavin.service.Impl.BuyGoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="saleDao" ref="saleDao"/>
</bean>
<!--使用spring的事务处理-->
<!--1.声明事务管理器-->
<bean id="transactionalManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2.声明业务方法的事务属性(隔离级别,传播行为,超时时间)
id:自定义名称,表示<tx:advicce>和</tx:advice>之间的配置内容的id
transaction-manager:事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionalManager">
<!--tx:attribute:配置事务属性-->
<tx:attributes>
<!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称,
1)完整的方法名称,不带有包和类
2)方法可以使用通配符,*表示任意字符
propagation:传播行为,枚举值
isolation:隔离级别
rollback0for:你指定的异常类名,全限定类名。发生异常一定回滚
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="com.gavin.excep.NotEnoughException"/>
<!--可以使用通配符来指定很多的方法如下-->
<!--<tx:method name="add*"/>-->
</tx:attributes>
</tx:advice>
<!--如果需要指定哪些包中类需要使用事务,需要配置aop-->
<aop:config>
<!--配置切入点表达式:指定哪些包中类需要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式
比如要指定下面几个包的service
com.gavin.service
com.john.service
com.xiaoming.service
下面表达式意思是所有service包下的类及其子包的类的所有方法
-->
<aop:pointcut id="myPointcut" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器:关联advice和pointcut
advice-ref:通知,上面tx:advice的id
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/>
</aop:config>
</beans>
spring.properties
jdbc.url=jdbc:mysql://localhost:3306/spring_test
jdbc.username=root
jdbc.passwd=123
jdbc.maxActive=20
<6>创建service业务类
接口:
package com.gavin.service;
public interface BuyGoodsService {
void buy(Integer goodsId, Integer nums);
}
实现:
package com.gavin.service.Impl;
import com.gavin.dao.GoodsDao;
import com.gavin.dao.SaleDao;
import com.gavin.domain.Goods;
import com.gavin.domain.Sale;
import com.gavin.excep.NotEnoughException;
import com.gavin.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
public class BuyGoodsServiceImpl implements BuyGoodsService {
private GoodsDao goodsDao;
private SaleDao saleDao;
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("buy方法开始");
//记录销售信息,想sale表添加记录
Date time_cur = new Date();
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
sale.setDate(time_cur);
saleDao.insertSale(sale);
//更新库存
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null){
throw new NotEnoughException("商品不存在");
}else if(goods.getAmount() < nums){
throw new NotEnoughException("编号L" + goodsId + "商品库存不足");
}
//修改库存
Goods buyGoods = new Goods();
buyGoods.setAmount(nums);
buyGoods.setId(goodsId);
buyGoods.setUpdate_time(time_cur);
goodsDao.updateGoods(buyGoods);
System.out.println("buy方法完成");
}
}
(4)创建一个jsp发起请求
index.jsp
<%@page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>购买商品</p>
<!--注意这里的action一定要和下面的servlet配置(web.xml)中的url-pattern一致, action不加/
<servlet-mapping>
<servlet-name>BuyGoodsServlet</servlet-name>
<url-pattern>/BuyGoods</url-pattern>
</servlet-mapping>
-->
<form action="BugGoods" method="post">
<table>
<tr>
<td>商品id</td>
<td><input type="text" name="goodsId"></td>
</tr>
<tr>
<td>购买数量</td>
<td><input type="text" name="nums"></td>
</tr>
</table>
</form>
</body>
</html>
(5)检查web.xml版本是否过低
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uoNm9x8u-1615127549360)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301223842159.png)]
使用idea创建的web项目中web.xml中的servlet版本很低(web-app_2_3是最低版本),需重新替换
步骤:
1.点开Project Structure
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0hOykHN-1615127549360)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301223955383.png)]
2.删除自带的web.xml
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Fs3tmjJ-1615127549361)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301224118582.png)]
3.增加4.0版本的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afS9NYmQ-1615127549361)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301224213031.png)]
新的配置文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
</web-app>
(5)创建Servlet,接收请求参数,调用Service,调用dao
右键在Controller目录创建servlet
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dn7fVUk-1615127549361)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210302132134626.png)]
如果右键无servlet,需在projet Structure勾选如下配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaCQTLiM-1615127549362)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301234202944.png)]
创建完后项目中多了个:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lMmB9d3J-1615127549362)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301225223171.png)]
web.xml中修改配置,增加如下几行
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>BuyGoodsServlet</servlet-name>
<servlet-class>com.gavin.BuyGoodsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BuyGoodsServlet</servlet-name>
<url-pattern>/web</url-pattern>
</servlet-mapping>
</web-app>
增加的是这几行:
<servlet-mapping>
<servlet-name>BuyGoodsServlet</servlet-name>
<url-pattern>/web</url-pattern>
</servlet-mapping>
(6)创建一个jsp作为显示结果页面
result.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
result.jsp 购买商品成功
</body>
</html>
<7>配置tomcat服务器
Server项:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9S5SKI7-1615127549363)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301234445000.png)]
Deployment项:
右侧点击+号添加:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tO17NqNH-1615127549363)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210301234612431.png)]
Deployment底下的Application context和初始访问路径有关,可更改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1YdbTD6U-1615127549364)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210302225043150.png)]
<8>启动tomcat
2.项目配置log4j
(1)引入log4j依赖
<!--log4j所需依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.10.0</version>
</dependency>
(2)web.xml中引入log4j配置
<!-- log4j2-begin -->
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
<filter>
<filter-name>log4jServletFilter</filter-name>
<filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log4jServletFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<!-- log4j2-end -->
(3)在Resources下创建log4j2.xml(必须是该名字)
放入以下内容
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF" monitorInterval="1800">
<properties>
<property name="LOG_HOME">/WEB-INF/logs</property>
<property name="FILE_NAME">finance-pay</property>
</properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<RollingFile name="running-log" fileName="${LOG_HOME}/${FILE_NAME}.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log.gz"
immediateFlush="true">
<PatternLayout
pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level [%thread][%file:%line] - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="20" />
</RollingFile>
</Appenders>
<Loggers>
<!-- <Logger name="com.cssweb.test.app" level="trace" additivity="true">
<AppenderRef ref="running-log" /> </Logger> -->
<Root level="info">
<!-- 这里是输入到文件,很重要-->
<AppenderRef ref="running-log" />
<!-- 这里是输入到控制台-->
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
(4)创建测试类进行测试
package com.gavin;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class MyTest {
static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
@Test
public void logTest(){
logger.info("info msg");
}
}
3.启动tomcat常见问题
(1)idea配置问题导致tomcat启动信息中文乱码
现象:若出现乱码如下:
解决方法: 进入idea
在Help-- custom vm options 添加-Dfile.encoding=UTF-8
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0a3tVpd3-1615127549364)(C:\Users\hasee\AppData\Roaming\Typora\typora-user-images\image-20210302131654677.png)]。
重启idea,再次启动tomcat,乱码问题应该就解决了
(2)tomcat配置问题导致日志打印中文乱码
现象: tomcat导致的乱码常常表现为代码日志中的中文乱码,截图如下
解决方法:
在tomcat的启动参数加上-Dfile.encoding=UTF-8
。
七.spring源码解析
1.ContextLoaderListener解析
该类包在:
import org.springframework.web.context.ContextLoaderListener;
需要引入如下依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
- ContextLoaderListener是一个监听器
- 由Spring编写并提供
我们搭建SSM框架时,需要做的仅仅是在web.xml中配置它,一般是这样:
我们常说的监听器一般是指具体的监听器对象,比如ContextLoaderListener。但这个对象怎么来的?它其实实现一个监听器接口。我们来看看ContextLoaderListener实现了哪个接口:
先不看ContextLoader,我们发现ContextLoaderListener实现了ServletContextListener(三大生命周期监听之一)
所以,很明显Spring实现了Tomcat提供的ServletContextListener接口,写了一个监听器来监听项目启动。一旦项目启动,会触发ContextLoaderListener中的特定方法。
大致模拟一下ServletContext对象的创建,以及ServletContextListener的监听过程:
也就是说Tomcat的ServletContext创建时,会调用ContextLoaderListener的contextInitialized(),这个方法内部的initWebApplicationContext()就是用来初始化Spring的IOC容器的。再强调一遍:
- ServletContext对象是Tomcat的
- ServletContextListener是Tomcat提供的接口
- ContextLoaderListener是Spring写的,实现了ServletContextListener
- Spring自己写的监听器,用来创建Spring IOC容器天经地义
initWebApplicationContext内容: