Spring 基础框架,可以视为Spring 基础设施,基本上任何其他 Spring 项目都是以 SpringFramework 为基础的。
lOC: Inversion of Control,翻译过来是反转控制。把对象创建和对象之间的调用过程,交给 Spring 进行管理
Spring 的IOC 容器就是IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IOC容器。
Spring 提供了IOC容器的两种实现方式:
(1) BeanFactory:IOC容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
加载配置文件时候不会创建对象,只有在使用(获取)对象的时候才会创建
(2) ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用,加载配置文件时候就会把配置文件中的对象进行创建
IOC底层原理:xml解析、工厂模式、反射
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.19version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
dependencies>
package com.fd.spring.pojo;
public interface Person {
}
package com.fd.spring.pojo;
/**
* SSM
*
* @author lucky_fd
* @since 2023-06-05
*/
public class Student implements Person{
private Integer id;
private String name;
private Integer age;
private String gender;
public Student() {
}
public Student(Integer id, String name, Integer age, String gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
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 getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
<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="studentOne" class="com.fd.spring.pojo.Student">bean>
beans>
@Test
public void studentTest() {
/*
* 获取bean的三种方式:
* 1、根bean的id获取
* 2、根bean的类型获取
* 注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean
* 若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
* 若有多个类型匹配的bean,此时抛出异常:NoUniqueBeanDefinitionException
* 3、根据bean的id和类型获取
* 结论:根据类型来获取bean时,在满足bean唯一性的前提下其实只是看:[对象 instanceof 指定的类型]的返回结果只要返回的是true就可以认定为和类型匹配,能够获取到。
* 即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
*
* */
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
// 根据bean的id获取bean
Student studentOne = (Student)applicationContext.getBean("studentOne");
System.out.println(studentOne);
// 根据bean的类型获取bean, 注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean
Student bean = applicationContext.getBean(Student.class);
System.out.println(bean);
// 根据bean的id和类型来获取bean
Student one = applicationContext.getBean("studentOne", Student.class);
System.out.println(one);
// 通过接口获取
Person person = applicationContext.getBean(Person.class);
System.out.println(person);
}
获取bean的三种方式:
* 1、根bean的id获取
* 2、根bean的类型获取
* 注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean
* 若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
* 若有多个类型匹配的bean,此时抛出异常:NoUniqueBeanDefinitionException
* 3、根据bean的id和类型获取
* 结论:根据类型来获取bean时,在满足bean唯一性的前提下其实只是看:[对象 instanceof 指定的类型]的返回结果只要返回的是true就可以认定为和类型匹配,能够获取到。
* 即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
Spring配置文件
<bean id="studentOne" class="com.fd.spring.pojo.Student">
<property name="id" value="1001">property>
<property name="name" value="张三">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
bean>
测试方法:
@Test
public void DiTest() {
// 获取IOC容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring_ioc.xml");
Student studentOne = (Student)ioc.getBean("studentOne");
System.out.println(studentOne);
}
Spring配置文件
<bean id="studentTwo" class="com.fd.spring.pojo.Student">
<constructor-arg name="id" value="1002" type="int">constructor-arg>
<constructor-arg name="age" value="28">constructor-arg>
<constructor-arg name="gender" value="女">constructor-arg>
<constructor-arg name="name" value="丽丽">constructor-arg>
bean>
测试方法:
@Test
public void DiConstructorTest() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
Student studentTwo = applicationContext.getBean("studentTwo", Student.class);
System.out.println(studentTwo);
}
什么是字面量?
int a = 10:
声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。
而如果a是带引号的:‘a’,那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面最没有引申含义,就是我们看到的这个数据本身。
<constructor-arg name="name" value="丽丽"></constructor-arg>
<bean id="studentThree" class="com.fd.spring.pojo.Student">
<constructor-arg name="age">
<null/>
constructor-arg>
bean>
CDATA节是xml中一个特殊的标签,因此不能写在一个属性中。
<bean id="studentFour" class="com.fd.spring.pojo.Student">
<property name="id" value="1004">property>
<property name="name">
<value>]]>value>
property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
bean>
测试结果:
1.引用外部的Bean的id
<bean id="studentFive" class="com.fd.spring.pojo.Student">
<property name="id" value="1005">property>
<property name="name" value="赵六">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
<property name="dept" ref="deptOne">property>
bean>
<bean id="deptOne" class="com.fd.spring.pojo.Dept">
<property name="deptId" value="1">property>
<property name="deptName" value="1班">property>
bean>
<bean id="studentFive" class="com.fd.spring.pojo.Student">
<property name="id" value="1005">property>
<property name="name" value="赵六">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
<property name="dept" ref="deptOne">property>
<property name="dept.deptId" value="2">property>
<property name="dept.deptName" value="2班">property>
bean>
<bean id="deptOne" class="com.fd.spring.pojo.Dept">
<property name="deptId" value="1">property>
<property name="deptName" value="1班">property>
bean>
<bean id="studentFive" class="com.fd.spring.pojo.Student">
<property name="id" value="1005">property>
<property name="name" value="赵六">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
<property name="dept">
<bean id="deptTwo" class="com.fd.spring.pojo.Dept">
<property name="deptId" value="3">property>
<property name="deptName" value="3班">property>
bean>
property>
bean>
<bean id="studentSix" class="com.fd.spring.pojo.Student">
<property name="id" value="1005">property>
<property name="name" value="赵六">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
<property name="hobby">
<array>
<value>学习value>
<value>吃饭value>
array>
property>
bean>
测试方法:
@Test
public void DiTest1() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
Student studentSix = applicationContext.getBean("studentSix", Student.class);
System.out.println(studentSix);
}
1.级联赋值
<bean id="deptTwo" class="com.fd.spring.pojo.Dept">
<property name="deptId" value="2">property>
<property name="deptName" value="2班">property>
<property name="students">
<list>
<ref bean="studentOne">ref>
<ref bean="studentTwo">ref>
<ref bean="studentThree">ref>
list>
property>
bean>
测试方法:
@Test
public void DiTest2() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
Dept deptTwo = applicationContext.getBean("deptTwo", Dept.class);
System.out.println(deptTwo);
}
2.引用赋值(需要用到util命名空间)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<bean id="deptTwo" class="com.fd.spring.pojo.Dept">
<property name="deptId" value="2">property>
<property name="deptName" value="2班">property>
<property name="students" ref="studentList">property>
bean>
<util:list id="studentList">
<ref bean="studentOne">ref>
<ref bean="studentTwo">ref>
<ref bean="studentThree">ref>
util:list>
beans>
1.级联赋值
<bean id="studentSeven" class="com.fd.spring.pojo.Student">
<property name="id" value="1006">property>
<property name="name" value="王五">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
<property name="hobby">
<array>
<value>学习value>
<value>吃饭value>
array>
property>
<property name="teacherMap">
<map>
<entry key="10086" value-ref="teacherOne"/>
<entry key="10087" value-ref="teacherTwo"/>
map>
property>
bean>
<bean id="teacherOne" class="com.fd.spring.pojo.Teacher">
<property name="id" value="10086">property>
<property name="name" value="小红">property>
bean>
<bean id="teacherTwo" class="com.fd.spring.pojo.Teacher">
<property name="id" value="10087">property>
<property name="name" value="小王">property>
bean>
测试方法:
@Test
public void DiTest3() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
Student studentSeven = applicationContext.getBean("studentSeven", Student.class);
System.out.println(studentSeven);
}
2.引用赋值
<bean id="studentSeven" class="com.fd.spring.pojo.Student">
<property name="id" value="1006">property>
<property name="name" value="王五">property>
<property name="age" value="25">property>
<property name="gender" value="男">property>
<property name="hobby">
<array>
<value>学习value>
<value>吃饭value>
array>
property>
<property name="teacherMap" ref="map">property>
bean>
<util:map id="map">
<entry key="10086" value-ref="teacherOne"/>
<entry key="10087" value-ref="teacherTwo"/>
util:map>
<bean id="teacherOne" class="com.fd.spring.pojo.Teacher">
<property name="id" value="10086">property>
<property name="name" value="小红">property>
bean>
<bean id="teacherTwo" class="com.fd.spring.pojo.Teacher">
<property name="id" value="10087">property>
<property name="name" value="小王">property>
bean>
引入约束
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<bean id="studentEight" class="com.fd.spring.pojo.Student"
p:id="1007" p:age="35" p:name="老王" p:dept-ref="deptOne">
bean>
beans>
测试方法:
@Test
public void DiTest4() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_ioc.xml");
Student studentEight = applicationContext.getBean("studentEight", Student.class);
System.out.println(studentEight);
}
引入依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.6version>
dependency>
配置spring配置文件
<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="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC"/>
<property name="password" value="mysql123."/>
<property name="username" value="admin"/>
bean>
beans>
或者:引入properties配置文件,需添加context约束
测试方法:
@Test
public void dataSourceTest() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_datasource.xml");
DruidDataSource bean = applicationContext.getBean(DruidDataSource.class);
System.out.println(bean);
}
spring配置文件,可以通过bean标签的scope属性设置bean的作用域范围
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.fd.spring.pojo.Student" scope="singleton">
<property name="id" value="1001"/>
<property name="name" value="张三"/>
bean>
beans>
@Test
public void scopeTest() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
Student bean1 = applicationContext.getBean(Student.class);
Student bean2 = applicationContext.getBean(Student.class);
System.out.println(bean1 == bean2);
}
<bean id="student" class="com.fd.spring.pojo.Student" scope="prototype">
<property name="id" value="1001"/>
<property name="name" value="张三"/>
bean>
测试方法:
@Test
public void scopeTest() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
Student bean1 = applicationContext.getBean(Student.class);
Student bean2 = applicationContext.getBean(Student.class);
System.out.println(bean1 == bean2);
}
package com.fd.spring.pojo;
/**
* SSM
*
* @author lucky_fd
* @since 2023-06-07
*/
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
System.out.println("生命周期2:依赖注入");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
System.out.println("生命周期1:实例化");
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public void initMethod() {
System.out.println("生命周期3:初始化");
}
public void destroyMethod() {
System.out.println("生命周期4:销毁");
}
}
<bean id="user" class="com.fd.spring.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
<property name="id" value="1"/>
<property name="name" value="张三"/>
bean>
<bean id="beanPostProcessor" class="com.fd.spring.process.MyBeanPostProcessor">bean>
@Test
public void test() {
/*
* 1、实例化
* 2、依赖注入
* 3、bean对象初始化之前操作
* 4、初始化,需要通过bean的init-method属性指定初始化的方法
* 5、bean对象初始化之后操作
* 6、IOC容器关闭时销毁,需要通过bean的destroy-method属性指定销毁的方法
*
* */
//ConfigurableApplicationContext是ApplicationContext的子接口,其中扩展了刷新和关闭容器的方法
ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
User bean = applicationContext.getBean(User.class);
System.out.println(bean);
applicationContext.close();
}
bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到I0C容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对I0C容器中所有bean都会执行
package com.fd.spring.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* SSM
*
* @author lucky_fd
* @since 2023-06-09
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 此方法在bean的生命周期初始化之前执行
System.out.println("MyBeanPostProcessor -> 前置处理器执行postProcessBeforeInitialization");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 此方法在bean的生命周期初始化之后执行
System.out.println("MyBeanPostProcessor -> 后置处理器执行postProcessAfterInitialization");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
FactoryBean是一个接口,需要创建一个类实现该接口其中有三个方法:
当把FactoryBean的实现类配置为bean时,会将当前类中getObject()所返回的对象交给IOC容器管理,就可以直接通过IOC容器getBena获取工厂getObject()所返回的对象
package com.fd.spring.factory;
import com.fd.spring.pojo.User;
import org.springframework.beans.factory.FactoryBean;
/**
* SSM
*
* @author lucky_fd
* @since 2023-06-09
*/
public class UserFactoryBean implements FactoryBean<User> {
/*
* FactoryBean是一个接口,需要创建一个类实现该接口其中有三个方法:
* getObject():通过一个对象交给IOC容器管理
* getObjectType(): 设置所提供对象的类型
* isSingleton(): 所提供的对象是否单例
* 当把FactoryBean的实现类配置为bean时,会将当前类中getObject()所返回的对象交给IOC容器管理
*
* */
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
FactoryBean接口
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
<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 class="com.fd.spring.factory.UserFactoryBean">bean>
beans>
@Test
public void factoryBeanTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-factory.xml");
// 没有配置User类的bean,这里通过UserFactoryBean也获取到了User类的bean对象
User bean = applicationContext.getBean(User.class);
System.out.println(bean);
}
测试结果:
自动装配:
根据指定的策略,在IOC容器中匹配某个bean,自动为bean 中的类类型的属性或接口类型的属性赋值
场景模拟:三层架构:controller层->service层->dao层(mapper层)
// 控制层
public class UserController {
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void saveUser() {
userService.save();
}
}
// 业务层
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
// 持久层
public interface UserDao {
void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存成功");
}
}
spring配置文件:
通过配置property进行bean装配,需要我们在配置文件中手动配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.fd.spring.controller.UserController" id="userController">
<property name="userService" ref="userService"/>
bean>
<bean class="com.fd.spring.service.impl.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"/>
bean>
<bean class="com.fd.spring.dao.impl.UserDaoImpl" id="userDao">bean>
beans>
测试方法:
@Test
public void autowireByXmlTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire-xml.xml");
UserController userController = applicationContext.getBean(UserController.class);
userController.saveUser();
}
自动装配的策略 autowire:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.fd.spring.controller.UserController" id="userController" autowire="byType">
bean>
<bean class="com.fd.spring.service.impl.UserServiceImpl" id="userService" autowire="byType">
bean>
<bean class="com.fd.spring.dao.impl.UserDaoImpl" id="userDao">bean>
beans>
和 XML配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。
本质上: 所有一切的操作都是java代码来完成的,XML和注解只是告诉框架中的java代码如何执行
举例:元旦联欢会要布置教室,蓝色的地方贴上元旦快乐四个字,红色的地方贴上拉花,黄色的地方贴上气球。
班长做了所有标记,同学们来完成具体工作。墙上的标记相当于我们在代码中便用的注解,后面同学们做的工作相当于框架的具体操作。
Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后
续操作。
spring配置文件开启组件扫描:
<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 http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.fd.spring">context:component-scan>
beans>
@Component: 将类标识为普通组件
@Controller: 将类标识为控制层组件
@Service: 将类标识为业务层组件
@Repository: 将类标识为持久层组件
通过注解+扫描所配置的bean的id,默认值为类的小驼峰,即类名的首字母为小写的结果,可以通过标识组件的注解的value属性值设置bean的自定义的id
以上四个注解的联系与区别?
通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、 @Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
@Controller
public class UserController {
}
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
public interface UserDao {
}
@Repository
public class UserDaoImpl implements UserDao {
}
@Test
public void iocByAnnotationTest() {
/*
* 通过注解+扫描所配置的bean的id,默认值为类的小驼峰,即类名的首字母为小写的结果,
* 可以通过标识组件的注解的value属性值设置bean的自定义的id
*/
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
UserController userController = applicationContext.getBean(UserController.class);
System.out.println(userController);
UserService userService = applicationContext.getBean(UserService.class);
System.out.println(userService);
UserDao userDao = applicationContext.getBean(UserDao.class);
System.out.println(userDao);
}
context:exclude-filter:排除扫描
context:include-filter:包含扫描
注意:需要在context:component-scan标签中设置use-default-filters=“false”
排除扫描:
<context:component-scan base-package="com.fd.spring">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="assignable" expression="com.fd.spring.controller.UserController"/>
context:component-scan>
包含扫描:
<context:component-scan base-package="com.fd.spring" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="assignable" expression="com.fd.spring.controller.UserController"/>
context:component-scan>
@Controller("controller")
public class UserController {
/*autowire注解放在成员变量上,此时不需要设置成员变量的set方法*/
@Autowired
private UserService userService;
public void saveUser() {
userService.saveUser();
}
}
public interface UserService {
void saveUser();
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void saveUser() {
userDao.saveUser();
}
}
public interface UserDao {
void saveUser();
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
测试方法:
@Test
public void iocByAnnotationTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
UserController userController = applicationContext.getBean("controller", UserController.class);
userController.saveUser();
}
@Autowired注解能够标识的位置
a、标识在成员变量上,此时不需要设置成员变量的set方法
//autowire注解放在成员变量上,此时不需要设置成员变量的set方法
@Autowired
private UserService userService;
b、标识在set方法上
/*autowire注解放在成员变量的set方法上*/
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
c、标识在为当前成员变量赋值的有参构造上
/*autowire注解放在当前成员变量的有参构造上*/
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@Autowired注解的原理
a> 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
b> 若有多个类型匹配的bean,此时会自动转换为byName的方式实现自动装配的效果,即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值
c> byType和byName的方式都无法实现自动装配,即IOC容器中有多个类型匹配的bean且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NOUniqueBeanDefinitionException
d> 在c的基础上此时可以在要赋值的属性上,添加一个注解Qualifier通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
注意:
IOC容器中没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException。在@Autowired注解中有个属性required,默认值为true,要求必须完成自动装配可以将required设置为false,此时能装配则装配,无法装配则使用属性的默认值
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来一解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护!
相关术语:
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
public class CalculatorImpl implements Calculator{
@Override
public int add(int i, int j) {
System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
int result = i + j;
System.out.println("打印日志,方法执行后,结果:" + result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
int result = i - j;
System.out.println("打印日志,方法执行后,参数:" + i + "," +j);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
int result = i * j;
System.out.println("打印日志,方法执行后,结果:" + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("打印日志,方法执行前,参数:" + i + "," +j);
int result = i / j;
System.out.println("打印日志,方法执行后,结果:" + result);
return result;
}
}
@Test
public void proxyTest() {
CalculatorImpl calculator = new CalculatorImpl();
CalculatorStaticProxy proxy = new CalculatorStaticProxy(calculator);
int result = proxy.add(10, 5);
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现这就需要使用动态代理技术了。
动态代理有两种:
1、jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口在com.sun.proxy包下,类名为$proxy+数字
2、cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下
public class ProxyFactory {
private final Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
/*
classLoader Loader: 指定加载动态生成的代理类的类加载器
Class[] interfaces:获取目标对象实现的所有接口的class对象的数组
InvocationHandler h:设置代理中的抽象方法如何重写
*/
ClassLoader classLoader = this.getClass().getClassLoader(); // 先获取类的Class实例,再获取类的加载器
Class<?>[] interfaces = this.target.getClass().getInterfaces(); // 先获取类的Class实例,再获取接口
// 执行代理方法最终会调用此方法,执行被被代理类的方法
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy表示代理对象,method表示要执行的方法,args表示要执行的方法到的参数列表
System.out.println("打印日志,方法执行之前, 参数:" + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("打印日志,方法执行之后,结果:" + result);
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, h);
}
}
@Test
public void proxyTest1() {
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
Calculator proxy = (Calculator)proxyFactory.getProxy();
int result = proxy.add(5, 5);
}
AOP (Aspect Oriented Programming) 是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
前置通知: 在被代理的目标方法前执行
返回通知: 在被代理的目标方法成功结束后执行(寿终正寝)
异常通知: 在被代理的目标方法异常结束后执行(死于非命)
后置通知: 在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知: 使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
各种通知的执行顺序:
Spring版本5.3.x以前:
前置通知
目标操作
后置通知
返回通知或异常通知。
Spring版本5.3.x以后:
前置通知
目标操作
返回通知或异常通知
后置通知
封装通知方法的类。
被代理的目标对象
为目标对象应用通知之后创建的代理对象
这是一个纯逻辑的语法概念
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。
定位连接点的方式
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物 (从逻辑上来说)。
如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。
Spring的AOP 技术可以通过切入点定位到特定的连接点。
切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件
简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.1version>
dependency>
或者
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<version>2.6.7version>
dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.fd.spring"/>
<aop:aspectj-autoproxy/>
beans>
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
1.在切面中,需要通过指定的注解将方法标识为通知方法
@Before():前置通知,在目标对象方法执行之前执行
@After():后置通知,在目标对象方法的finally字句中执行
@AfterReturning():返回通知,在目标对象获取返回值之后执行
2.切入点表达式:设置在标识通知的注解的value属性中
execution(* com.fd.spring.annotation.CalculatorImpl.(…))
第一个表示任意的访问修饰符和返回值类型
第二个表示类中任意的方法
…表示任意的参数列表
类的地方也可以使用,表示包下所有的类
3.重用切入点表达式
@Pointcut声明一个公共的切入点表达式
@Pointcut(“execution(* com.fd.spring.annotation.CalculatorImpl.*(…))”)
public void pointCut() {}
使用方法:@After(“pointCut()”) //使用的是重用切入点表达式方法名
4.获取连接点信息
在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
package com.fd.spring.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* SSM
*
* @author lucky_fd
* @since 2023-06-17
*
* 切面类必须通过@Aspect注解标识为一个切面
*/
@Component
@Aspect // 将当前组件标记为切面
public class LoggerAspect {
/*
1.在切面中,需要通过指定的注解将方法标识为通知方法
@Before():前置通知,在目标对象方法执行之前执行
@After():后置通知,在目标对象方法的finally字句中执行
@AfterReturning():返回通知,在目标对象获取返回值之后执行
2.切入点表达式:设置在标识通知的注解的value属性中
execution(* com.fd.spring.annotation.CalculatorImpl.*(..))
第一个*表示任意的访问修饰符和返回值类型
第二个*表示类中任意的方法
..表示任意的参数列表
类的地方也可以使用*,表示包下所有的类
3.重用切入点表达式
@Pointcut声明一个公共的切入点表达式
@Pointcut("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
public void pointCut() {}
使用方法:@After("pointCut()") //使用的是重用切入点表达式方法名
4.获取连接点信息
在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
*/
// 切入点表达式的重用
@Pointcut("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
public void pointCut() {}
//@Before("execution(public int com.fd.spring.annotation.CalculatorImpl.add(int, int))") //切入点表达式
@Before("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
public void beforeNotice(JoinPoint joinPoint) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法:" + signature.getName() + ", 参数:" + Arrays.toString(args));
}
@After("pointCut()")
public void AfterNotice(JoinPoint joinPoint) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
System.out.println("后置通知,方法:" + signature.getName());
}
/**
在返回通知中若要获取目标对象方法的返回值
只需要通过@AfterReturning注解的returning属性
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
* */
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningNotice(JoinPoint joinPoint, Object result) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
System.out.println("返回通知, 方法:" + signature.getName() + ", 返回值:" + result);
}
/**
在返回通知中若要获取目标对象方法的返回值
只需要通过AfterThrowing注解的throwing属性
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
* */
@AfterThrowing(value = "pointCut()", throwing = "result")
public void afterThrowNotice(JoinPoint joinPoint, Exception result) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
System.out.println("异常通知, 方法:" + signature.getName() + ", 异常:" + result);
}
/*
环绕通知的方法的返回值一定要和目标对象方法的返回值一致
* */
@Around("pointCut()")
public Object aroundNotice(ProceedingJoinPoint joinPoint) {
Object result;
try {
System.out.println("环绕通知-->前置通知");
// 目标对象的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable e) {
System.out.println("环绕通知-->异常通知");
throw new RuntimeException(e);
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
}
在spring中通过AOP代理后原目标对象就被隐藏了就不能再通过IOC获取原目标对象,只能通过接口去获取目标的代理对象
@Test
public void aopTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop-annotation.xml");
// 在spring中通过AOP代理后原目标对象就被隐藏了就不能再通过IOC获取原目标对象,只能通过获取接口去获取代理对象
Calculator bean = applicationContext.getBean(Calculator.class);
// int add = bean.add(10, 5);
// int div = bean.div(10, 0);
int mul = bean.mul(2, 5);
System.out.println(mul);
}
可以通过@order注解的value属性设置优先级,默认值Integer的最大值
@order注解的value属性值越小,优先级越高
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
/**
* The order value.
* Default is {@link Ordered#LOWEST_PRECEDENCE}.
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;
}
@Component
@Aspect
@Order(1)
public class ValidateAspect {
// @Before("execution(* com.fd.spring.annotation.CalculatorImpl.*(..))")
@Before("com.fd.spring.annotation.LoggerAspect.pointCut()")
public void beforeMethod() {
System.out.println("前置通知,校验");
}
}
测试结果:
@Component
public class LoggerAspect {
public void beforeNotice(JoinPoint joinPoint) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
// 获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法:" + signature.getName() + ", 参数:" + Arrays.toString(args));
}
public void afterNotice(JoinPoint joinPoint) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
System.out.println("后置通知,方法:" + signature.getName());
}
/**
在返回通知中若要获取目标对象方法的返回值
只需要通过@AfterReturning注解的returning属性
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
* */
public void afterReturningNotice(JoinPoint joinPoint, Object result) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
System.out.println("返回通知, 方法:" + signature.getName() + ", 返回值:" + result);
}
/**
在返回通知中若要获取目标对象方法的返回值
只需要通过AfterThrowing注解的throwing属性
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
* */
public void afterThrowNotice(JoinPoint joinPoint, Exception result) {
// 获取连接点所对应的方法名
Signature signature = joinPoint.getSignature();
System.out.println("异常通知, 方法:" + signature.getName() + ", 异常:" + result);
}
/*
环绕通知的方法的返回值一定要和目标对象方法的返回值一致
* */
public Object aroundNotice(ProceedingJoinPoint joinPoint) {
Object result;
try {
System.out.println("环绕通知-->前置通知");
// 目标对象的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable e) {
System.out.println("环绕通知-->异常通知");
throw new RuntimeException(e);
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.fd.spring.xml"/>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.fd.spring.xml.CalculatorImpl.*(..))"/>
<aop:aspect ref="loggerAspect">
<aop:before method="beforeNotice" pointcut-ref="pointCut"/>
<aop:after method="afterNotice" pointcut-ref="pointCut"/>
<aop:after-returning method="afterReturningNotice" pointcut-ref="pointCut" returning="result"/>
<aop:after-throwing method="afterThrowNotice" pointcut-ref="pointCut" throwing="result"/>
<aop:around method="aroundNotice" pointcut-ref="pointCut"/>
aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
aop:aspect>
aop:config>
beans>
@Test
public void xmlTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop-xml.xml");
com.fd.spring.xml.Calculator bean = applicationContext.getBean(com.fd.spring.xml.Calculator.class);
int add = bean.add(5, 5);
}
测试结果:
Spring 框架对JDBC进行封装,使用JdbcTemplate 方便实现对数据库操作
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.19version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.19version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.19version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
jdbc.username=root
jdbc.password=mysql123.
<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:property-placeholder location="classpath:jdbc.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
beans>
//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
// 设置Spring测试环境的配置文件
@ContextConfiguration(“classpath:spring-jdbc.xml”)
//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
// 设置Spring测试环境的配置文件
@ContextConfiguration("classpath:spring-jdbc.xml")
public class AppTest
{
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void insertTest() {
String sql = "insert into t_user values (null, ?, ?, ?, ?, ?)";
jdbcTemplate.update(sql, "付东", "123456", "28", "nan", "[email protected]");
}
@Test
public void selectTest() {
String sql = "select * from t_user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), "1");
System.out.println(user);
}
@Test
public void selectAllTest() {
String sql = "select * from t_user";
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
users.forEach(System.out::println);
}
}
(1) 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
(2) 典型场景: 银行转账
luy 转账 100元给 mary
lucy少 100,mary多100
事务四个特性(ACID)
事务功能的相关操作全部通过自己编写代码来实现
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
所以,我们可以总结下面两个概念:
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<context:component-scan base-package="com.fd.spring">context:component-scan>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager">tx:annotation-driven>
POJO层:
@Component
public class User {
private String id;
private String name;
private String password;
private Integer age;
private String gender;
private String email;
private Double balance;
...
}
@Component
public class Book {
private String bookId;
private String bookName;
private Double price;
private Integer stock;
...
}
Controller层
@Controller
public class BookController {
@Autowired
private IBookService bookService;
@Autowired
private ICheckoutService checkoutService;
public void BuyBook(String userId, String bookId) {
bookService.buyBook(userId, bookId);
}
public void checkout(String userId, String[] bookIds) {
checkoutService.checkout(userId,bookIds);
}
}
Service层
public interface IBookService {
void buyBook(String userId, String bookId);
}
@Service
public class BookServiceImpl implements IBookService {
@Autowired
private IBookDao bookDao;
@Override
@Transactional()
public void buyBook(String userId, String bookId) {
// 查询图书的价格
Double price = bookDao.getPriceById(bookId);
// 更新图书的库存
bookDao.updateStock(bookId);
// 更新用户的余额
bookDao.updateBalance(userId,price);
}
}
public interface ICheckoutService {
void checkout(String userId, String[] bookIds);
}
@Service
public class CheckoutServiceImpl implements ICheckoutService {
@Autowired
private IBookService bookService;
@Override
@Transactional
public void checkout(String userId, String[] bookIds) {
for (int i = 0; i < bookIds.length; i++) {
bookService.buyBook(userId, bookIds[i]);
}
}
}
Dao层
public interface IBookDao {
Double getPriceById(String bookId);
void updateStock(String bookId);
void updateBalance(String userId, Double price);
}
@Repository
public class BookDaoImpl implements IBookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Double getPriceById(String bookId) {
String sql = "select price from t_book where book_id = ?";
return jdbcTemplate.queryForObject(sql, Double.class, bookId);
}
@Override
public void updateStock(String bookId) {
String sql = "update t_book set stock = stock -1 where book_id = ?";
jdbcTemplate.update(sql, bookId);
}
@Override
public void updateBalance(String userId, Double price) {
String sql = "update t_user set balance = balance - ? where id = ?";
jdbcTemplate.update(sql, price, userId);
}
}
@Test
public void buyBookTest() {
/*
声明式事务的配置步骤:
1、在Spring的配置文件中配置事务管理器
2、开启事务的注解驱动
在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
@Transactional注解标识的位置:
1.标识在方法上
2、标识在类上,则类中所有的方法都全被事务管理
* */
bookController.BuyBook("1", "1");
// bookController.checkout("1", new String[] {"1", "2"});
}
如果用户的余额不足报错,则图书的sql执行也会进行回滚。
SQL [update t_user set balance = balance - ? where id = ?]; Data truncation: Out of range value for column ‘balance’ at row 1; nested exception is com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Out of range value for column ‘balance’ at row 1
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
注意:如果对增删改设置只读会抛出以下异常:
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源大概率是因为程序运行出现了问题(可能是]ava程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执,概括来说就是一句话:超时回滚,释放资源。
@Override
@Transactional(timeout = 3)
public void buyBook(String userId, String bookId) {
try {
TimeUnit.SECONDS.sleep(5);
}catch (Exception e) {
e.printStackTrace();
}
// 查询图书的价格
Double price = bookDao.getPriceById(bookId);
// 更新图书的库存
bookDao.updateStock(bookId);
// 跟新用户的余额
bookDao.updateBalance(userId,price);
}
执行过程抛出异常:
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略。
@Transactional(
rollbackFor = Exception.class,
rollbackForClassName = "java.lang.Exception"
)
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
各个隔离级别解决并发问题的能力见下表:
各种数据库产品对事务隔离级别的支持程度:
事务隔离级别默认为:可重复读
@Transactional(
isolation = Isolation.SERIALIZABLE
)
// 枚举对象
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
可以通过@Transactional中的propagation属性设置事务传播行为。
修改BookServicelmpl中buyBook()上,注解@Transactional的propagation属性
@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEN),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场暴,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook0中回滚,购买第一本图书不受影响,即能买几本就买几本
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<version>2.6.7version>
dependency>
注意:基于xml的声明式事务必须引入Aspects的依赖
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="tx" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="buyBook" timeout="3"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:advisor advice-ref="tx" pointcut="execution(* com.fd.spring.service.impl.*.*(..))">aop:advisor>
aop:config>
@Test
public void buyBookTest() {
/*
声明式事务的配置步骤:
1、在Spring的配置文件中配置事务管理器
2、开启事务的注解驱动
在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理
@Transactional注解标识的位置:
1.标识在方法上
2、标识在类上,则类中所有的方法都全被事务管理
* */
bookController.BuyBook("1", "1");
// bookController.checkout("1", new String[] {"1", "2"});
}