spring
spring
Spring官网https://spring.io/
- 特性
- 非侵入式:使用
Spring Framework开发应用程序时,对应用程序本身的结构影响非常小 - 控制反转
IoC:Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好。spring通过IoC容器来管理所有对象的实例化和初始化,控制对象间的依赖关系 - 面向切面编程
AOP:Aspect Oriented Programming,在不修改源代码的基础上增强代码功能 - 容器:容器:
Spring IoC是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率 - 组件化:
Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象 - 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现
- 非侵入式:使用
1. 创建项目
1.1 依赖
- 使用maven进行包管理,引入spring
<!-- 引入spring IoC相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.3</version>
</dependency>
<!-- 引入spring AOP相关依赖 -->
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring对junit的支持相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.2</version>
</dependency>
<!-- 事务相关 -->
<!--spring jdbc Spring 持久化层支持jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<!-- validation相关依赖 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.5.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.1</version>
</dependency>1.2 集成
1.2.1 集成日志
spring中已经集成日志门面slf4j,只需要引入日志实现log4j2或logback即可<!-- Logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.5.18</version> </dependency>使用时,在需要日志输出的类上,添加注解
@Slf4j(基于Lombok),就会自动生成private static final Logger log字段,可以使用其中的日志输出方法,对应在不同级别实现日志输出,比如log.info()如果不使用
Lombok,则需要手动创建日志对象,比如Logger log = LoggerFactory.getLogger(当前类.class);,传入的类会输出在消息开头,此时需要引入日志门面api<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.17</version> </dependency>
1.2.2 集成单元测试
整合Junit框架,让Junit知道我们使用了spring,自动配置容器,无需使用以下代码创建容器,方法介绍具体见从配置文件读取bean
// 用于创建容器,然后获取需要使用的对象 ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");需要提前引入spring-test依赖,提供支持
<!--spring对junit的支持相关依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>6.0.2</version> </dependency>引入
JUnit5依赖,配合支持依赖spring-test使用<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.13.4</version> </dependency>在
test下创建测试类,支持直接利用注解,注入需要测试的类import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; // 指定 Spring 配置类或 XML 配置文件 @SpringJUnitConfig(AppConfig.class) @SpringJUnitConfig(locations = "classpath:spring.xml") // 也可以 // @ExtendWith(SpringExtension.class) // @ContextConfiguration(classes = AppConfig.class) // 如果是配置文件 // @ContextConfiguration(locations = "classpath:spring.xml") public class SpringBaseTest { @Autowired private MyBean myBean; @Test public void testBeanInjection() { System.out.println("MyBean number = " + myBean.getNumber()); } }
JUnit4
如果是
JUnit4,对应需要使用@RunWith使用JUnit4ClassRunner@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AppConfig.class) public class MyTest { @Autowired private User user; }
1.2.3 Lombok
直接导入
Lombok即可使用,Lombok中包含Data等注解,方便生成常用的构造方法、getter、setter等<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.38</version> <scope>provided</scope> </dependency>
2. IoC
IoC:就是反转控制设计思想,spring使用IoC思想的一种常见实现DI(依赖注入),将原本在程序中手动创建对象的控制权交给第三方(IoC容器),让容器把需要的资源推送到想要的组件- 通过
IoC容器管理对象,这些对象被称为spring beanxml文件定义bean信息- 使用
BeanDefinitionReader抽象这些信息 IoC容器使用BeanFactory子接口比如ApplicationContext接口内的反射机制读取配置信息,实例化初始化创建对象
- 依赖注入常见的实现方式包括两种
- 构造函数注入
set方法注入
- 通过
IoC特点- 我们丧失了一个权力 (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)
- 对象之间的耦合度或者说依赖程度降低,资源变的容易管理,比如用
IoC容器提供对象的话很容易就可以实现一个单例
2.1 使用xml配置IoC
xml文件是spring最早最基础的配置方式,比如配置IoC,设置复杂但支持动态修改配置,不用修改代码- 设置
bean- 作用域:
singleton:单例模式prototype:多例模式- 如果是在
WebApplicationContext环境下还会有另外几个作用域:request一个请求范围内有效、session一个会话范围内有效
- 作用域:
<?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">
<!-- 最基础版本配置,默认为单例singleton,使用scope="prototype"修改创建方式 -->
<bean name="user" class="com.IocTest.User"></bean>
<!-- 可通过getBean获取多个不同对象 -->
<bean name="user" class="com.IocTest.bean.Student" scope="prototype"></bean>
</beans>2.1.1 对象赋值
- 通过构造方法赋值
- 默认调用无参构造方法
- 可以使用
constructor-arg使用有参方法 name为参数名,value/ref为值定义值或对象,ref应该对应一个配置的bean的id
<bean id="student" class="com.IocTest.bean.Student">
<constructor-arg value="1001"></constructor-arg>
<constructor-arg value="张三"></constructor-arg>
<constructor-arg value="18"></constructor-arg>
<constructor-arg value="男"></constructor-arg>
</bean>通过
set方法赋值:对应property标签一般类型
string、int等直接使用value赋值<!-- 为其中属性在xml文件中赋值,需要对应属性实现public的set方法 --> <!-- property 标签是使用 set 方法设置值的 --> <bean name="user" class="com.IocTest.User"> <!-- String,int等赋值使用value字段 --> <property name="uid" value="123"></property> </bean>对象类型使用
ref引用、子bean标签赋值或级联属性赋值<bean name="user" class="com.IocTest.User"> <!-- 类赋值使用ref字段,引用对应 id 的 bean --> <property name="dep1" ref="dep1"></property> <!-- 也可以使用子标签bean赋值 --> <bean name="dep2"> <property name="name" value="研发部"></property> </bean> <!-- 还可以使用name为dep3.name方式级联赋值 --> <property name="dep3.name" value="测试部"></property> </bean> <!-- 另一个对象,用于ref引用 --> <bean name="dep1" class="com.IocTest.dep"></bean>集合类型使用
list、map、set标签,引用其他的bean<?xml version="1.0" encoding="UTF-8"?> <!-- 使用util:list、util:map标签必须引入相应的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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="user" class="com.IocTest.User"> <!-- list赋值使用list --> <property name="list"> <list> <ref bean="One"></ref> <ref bean="Two"></ref> <ref bean="Three"></ref> </list> </property> <!-- map赋值使用map --> <property name="map"> <map> <entry> <key> <value>1001</value> </key> <ref bean="o"></ref> </entry> <entry> <key> <value>1002</value> </key> <ref bean="t"></ref> </entry> </map> </property> </bean> </beans>数组赋值使用
array标签<bean name="user" class="com.IocTest.User"> <!-- 数组赋值使用array --> <property name="array"> <array> <value>103</value> <value>123</value> </array> </property> </bean>空值使用
null标签<bean name="user" class="com.IocTest.User"> <property name="name"> <null/> </property> </bean>xml中部分特殊符号需要转义,如<转<,也可以使用CDATA节<bean name="user" class="com.IocTest.User"> <!-- 特殊字符需要进行处理 --> <!-- <,>使用转译<和&rt;或使用子value标签后用![CDATA[...]]包裹 --> <property name="desc"><value><![CDATA[<,>]]></value></property> </bean>自动装配:通过
autowire属性实现autowire="byName"按名称装配autowire="byType"按类型装配
<!-- 自动装配 --> <!-- 如果只能找到一个匹配项即可使用自动装配 --> <!-- 有byType和byName两种方式 --> <bean id="userController" class="com.controller.UserController" autowire="byType"></bean> <bean id="userService" class="com.service.impl.UserServiceImpl" autowire="byType"></bean> <bean id="userDao" class="com.dao.impl.UserDaoImpl"></bean>
p命名空间:引入p命名空间后,可以方便地为属性赋值p空间允许在bean标签上直接为属性赋值p:属性名为属性赋值,p:属性名-ref引用其他对象<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="studentSix" class="com.IocTest.bean.Student" p:id="1006" p:name="小明" p:clazz-ref="clazzOne" p:teacherMap-ref="teacherMap"></bean>::: import 使用便捷的命名空间 一般为添加对应xmlns地址和命名空间地址
<!-- 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"> <!-- 使用util命名空间,为list等命名,使用ref即可为类赋值 --> <!-- xmlns:util="http://www.springframework.org/schema/util" --> <!-- xsi:schemaLocation添加"http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" --> <!-- 同理也可以使用p命名空间,便捷赋值 --> <!-- xmlns:p="http://www.springframework.org/schema/p" --> <!-- xsi:schemaLocation添加"http://www.springframework.org/schema/p http://www.springframework.org/schema/p/spring-p.xsd" --> </beans>- 如果有很多的命名空间,为了防止命名空间冲突,可以指定标签所在的命名空间,比如
<util:list>指定在util命名空间下的list标签
:::
- 如果有很多的命名空间,为了防止命名空间冲突,可以指定标签所在的命名空间,比如
2.1.2 引入资源包
- 引入
properties文件,便于以后修改添加
context命名空间约束使用
context引入文件使用
${key}引用值引入properties文件jdbc.user=root jdbc.password=atguigu jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 引入外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 使用${}方式使用对应值 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
2.2 从xml获取bean
使用xml文件
- 建立spring的xml文件,名称随意
- 使用ApplicationContext接口,一般创建该接口的实现类
new ClassPathXmlApplicationContext(classpath)完成引入 - 使用接口中的getBean方法完成类获取
IoC读取接口的主要继承结构

@Test public void test(){ ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); // 通过类型,要求只有一个满足的bean // 可以使用接口类型,如果有多个实现类,会报错 HelloWorld bean = ac.getBean(HelloWorld.class); bean.sayHello(); // 通过id和类型 HelloWorld bean1 = ac.getBean("helloworld", HelloWorld.class); bean1.sayHello(); }基于正则批量读取文件
//与上方不同的是,classpath:指定使用类路径访问 //在此基础上,能够使用*正则表达式加载配置文件bean.xml //会找到找到所有与文件名匹配的文件 //分别加载文件中的配置定义,最后合并成一个ApplicationContext ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean*.xml");
2.3 其他细节
2.3.1 对象创建生命周期
生命周期
bean对象创建(调用无参构造器)
给bean对象设置属性
bean的后置处理器(初始化之前)
bean对象初始化(需在配置bean时指定初始化方法):使用init-method提供方法名
bean的后置处理器(初始化之后)
bean对象就绪可以使用
bean对象销毁(需在配置bean时指定销毁方法):使用destroy-method提供方法名
IoC容器关闭:调用ClassPathXmlApplicationContext的close方法
<!-- 使用init-method属性指定初始化方法 --> <!-- 使用destroy-method属性指定销毁方法 --> <!-- 方法名应指定对象有的一个方法 --> <bean class="com.spring6.bean.User" scope="prototype" init-method="initMethod" destroy-method="destroyMethod"> <property name="id" value="1001"></property> <property name="username" value="admin"></property> <property name="password" value="123456"></property> <property name="age" value="23"></property> </bean>
后置处理器:是spring中实现了BeanPostProcessor接口的类,并且通过
bean标签配置到容器中,bean后置处理器不是单独针对某一个bean生效,而是针对IoC容器中所有bean都会执行import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("before:" + beanName + " = " + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("after:" + beanName + " = " + bean); return bean; } }
2.3.2 FactoryBean整合其他框架
FactoryBean:是Spring提供的一种整合第三方框架的常用机制
- 和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值
- 通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们
所有的对象创建都经过了
getObjectForBeanInstance方法,方法内对FactoryBean进行判断,如果是FactoryBean则调用FactoryBeanRegistrySupport中的getObjectFromFactoryBean方法,实现从工厂生产实例的过程,生产过程中调用了实现的getObject方法返回结果对象使用FactoryBeanpublic class UserFactoryBean implements FactoryBean<User> { @Override public User getObject() throws Exception { return new User(); } @Override public Class<?> getObjectType() { return User.class; } }<bean id="userFactoryBean" class="com.example.demo.UserFactoryBean"/>@Test public void testUserFactoryBean(){ //获取IOC容器 ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml"); User user = (User) ac.getBean("user"); System.out.println(user); }
2.4 spring注解配置IoC
- spring使用注解配置方式,首先要配置组件扫描功能,以开启注解读入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启基本组件扫描功能,base-package为扫描的包目录-->
<context:component-scan base-package="com.spring6"></context:component-scan>
<!--过滤扫描-->
<context:component-scan base-package="com.spring6">
<!-- context:exclude-filter标签:指定排除规则 -->
<!--
type:设置排除的依据
type="annotation",根据注解排除,expression中设置要排除的注解的全类名
type="assignable",根据类型排除,expression中设置要排除的类型的全类名
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="assignable" expression="com.spring6.controller.UserController"/>
</context:component-scan>
<!-- 扫描指定 -->
<context:component-scan base-package="com.spring6" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
<!--
type:设置包含的依据
type="annotation",根据注解包含,expression中设置要包含的注解的全类名
type="assignable",根据类型包含,expression中设置要包含的类型的全类名
-->
<!-- 例: 扫描Controller注解 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!-- 例: 扫描UserController类 -->
<context:include-filter type="assignable" expression="com.spring6.controller.UserController"/>
</context:component-scan>
</beans>注解的类别和添加方法
类注解:定义在类上,四种注解效果相同,仅为人为辨别设计,对应不同层
注解 说明 @Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 @Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean @Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean @Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean 定义在属性或变量上:单独使用
@Autowired注解,默认根据类型装配:该注解可以标注在构造方法上、方法上、形参上、属性上、注解上
该注解
@Autowired(required = false)有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,不存在不报错注解会根据依赖关系顺序创建,如果是使用非构造方法注入单例模式时产生了循环依赖,会考虑注入半成品解决,其他情况会报错
import com.spring6.dao.UserDao; import com.spring6.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { //下列方法均可注入,选择其一 //默认根据属性装配,若有多个匹配项,抛出异常 //1.可通过在属性上写出注解注入 @Autowired private UserDao userDao; //2.可通过set方法注入属性中 @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } //3.可通过在构造方法上使用注解注入属性中 @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } //4.可通过在构造方法属性上使用注解注入 public UserServiceImpl(@Autowired UserDao userDao) { this.userDao = userDao; } //5.单一构造方法,不写注解也会注入,需要所有方法字段均可注入 public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } }单例模式
可以配合
@scope作用域标签,默认单例模式,@scope("prototype")多例模式@Component @Scope("prototype") public class UserServiceImpl implements UserService { }
定义在属性或变量上,依变量名称注入,使用
@Autowired和@Qualifier注解package com.spring6.service.impl; import com.spring6.dao.UserDao; import com.spring6.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired // 有多个实现类时,使用@Qualifier指定bean的名字 @Qualifier("userDaoImpl") // 指定bean的名字 private UserDao userDao; @Override public void out() { userDao.print(); System.out.println("Service层执行结束"); } }使用@Source注解注入
@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配@Resource注解属于JDK扩展包,如果是JDK8不需要额外引入依赖。JDK11+或低于JDK8需要引入以下依赖<dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>使用
package com.spring6.service.impl; import com.spring6.dao.UserDao; import com.spring6.service.UserService; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { //根据name字段注入,可省略name字段,根据属性本身名称注入 //若未找到对应名称,根据字段注入,出现多个匹配项时报错 @Resource(name = "myUserDao") private UserDao myUserDao; @Override public void out() { myUserDao.print(); System.out.println("Service层执行结束"); } }
使用
@value注入具体值默认直接注入,会将字符串转为对应的字段类型
${}可以设置读取配置文件中的值,可以在properties文件中定义变量,然后在配置类中使用@PropertySource引入文件,在其他Bean中通过@value("${变量名}")注入变量#{}可以设置一个单行表达式,最终返回值会注入到字段中config.propertiesapp.name=MySpringApp app.maxUsers=100配置类import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @PropertySource("classpath:config.properties") // 引入配置文件 public class AppConfig { }Beanimport org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class MyBean { // 直接注入具体值 @Value("Hello Spring") private String greeting; @Value("1") private int id; // 从配置文件中读取字符串 @Value("${app.name}") private String appName; // 从配置文件中读取整数 @Value("${app.maxUsers}") private int maxUsers; // 使用 SpEL 表达式进行计算 @Value("#{${app.maxUsers} * 2}") private int doubleMaxUsers; }
2.5 注解配置类
使用配置类可以代替xml配置文件
Configuration:声明当前类为配置类ComponentScan:扫描指定包下的带Component注解或相同功能注解的类,并注册为beanBean:声明当前方法返回的实例为bean,交给容器管理import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; //标识为一个配置类 @Configuration //组件扫描某些类 //@ComponentScan({"com.spring6.controller", "com.spring6.service","com.spring6.dao"}) //组件扫描包 @ComponentScan("com.spring6") public class Spring6Config { @Bean //通过方法创建对象,返回值交给容器管理,默认id是方法名 public UserService userService(){ return new UserService(); } @Bean("dao") //指定id public UserDao userDao(){} }
使用该配置类对应的IoC容器
@Test public void testAllAnnotation(){ ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class); UserController userController = context.getBean("userController", UserController.class); userController.out(); logger.info("执行成功"); }
2.6 实现机制
- 通过
ApplicationContext对象创建IoC容器- 初始化时,获取需要管理的包或类,通过专门的扫描类完成
- 扫描类:将包名字符串转换为文件路径,遍历得到其中的所有
.class文件,拼接得到类的全限定名 - 注册:对每个得到的全限定名进行解析
Class.forName(name),将非接口!clazz.isInterface()且需要管理(有@Component这样的注解或有配置文件)的类标记描述信息BeanDefinition创建出来
注解可以通过
clazz.isAnnotationPresent(Component.class)方法判断,注解的属性可以通过clazz.getAnnotation(Component.class)获取 - 扫描类:将包名字符串转换为文件路径,遍历得到其中的所有
- 返回
Bean的方法getBean:利用BeanDefinition信息,使用反射创建实例化对象,如果是单例,放入单例池- 如果在单例池中存在实例,则直接返回
- 存在循环依赖,则在二级缓存中获取提前暴露的
Bean
- 注入:获取注解或
Bean.xml文件,利用BeanDefinition信息,将Bean的属性值注入到实例对象中(遍历字段和方法,判断字段或方法上是否有注解,有则通过反射注入)
- 初始化时,获取需要管理的包或类,通过专门的扫描类完成
2.6.1 三级缓存
- 在单例
Bean创建过程中DefaultSingletonBeanRegistry使用了三级缓存来解决构造完成前的循环依赖(非构造器注入方式,类还没有被创建出来,没有引用。通过类构造器赋值出现循环依赖无法解决)一级缓存
Map<String, SingletonObject>:完整初始化好的成品Bean- 存储可正常使用的
Bean - 在
Bean创建完成后放入一级缓存
- 存储可正常使用的
二级缓存
Map<String, earlySingletonObject:提前暴露的Bean- 为了解决循环依赖产生的机制,此过程中的
Bean是正在注入属性的早期实例,可以用于属性赋值,同时可以解决动态代理重复创建的问题
- 为了解决循环依赖产生的机制,此过程中的
三级缓存
Map<String, ObjectFactory<?>>:Bean的早期引用ObjectFactoryObjectFactory存储的是一个lambda表达式,对应通过factory.getObject()调用- 在
lambda表达式逻辑中,函数getEarlyBeanReference可用于获取Bean的原实例对象(正常情况)或创建动态代理(存在AOP等需要代理的注解),调用完成后,生成的对象会放入二级缓存,同时从三级缓存中移除内容 - 存在三级缓存的目的是同步生命周期,正常
Bean创建代理的时机应该在初始化后,而不是在属性赋值前,对于正常的无循环依赖的Bean动态代理创建应该延后
/** * 获取指定 Bean 的一个“提前访问”的引用, * 通常用于解决循环依赖问题。 * * @param beanName Bean 的名称(主要用于错误处理) * @param mbd 该 Bean 合并后的 BeanDefinition * @param bean 原始的 Bean 实例(刚 new 出来的,还未完成初始化) * @return 作为 Bean 引用对外暴露的对象 */ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { // 遍历后处理器 // 如果没有重写bp的默认实现,得到的是原始对象 // 否则得到重写后的方法生成的代理对象 exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }springboot自2.6之后默认关闭了循环依赖的自动处理,但三级缓存机制仍存在
3. AOP
AOP(Aspect Oriented Programming)是一种设计思想,它是面向对象编程OOP的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术- 利用
AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率 AOP使用代理模式,能够将切面相关的代码和业务层功能代码分离,方便代码的维护和管理
- 利用
3.1 相关术语
- 横切关注点:分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点
- 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强

- 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强
- 通知(增强)
- 增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用
try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
- 切面:封装通知方法的类

- 目标:被代理的目标对象
- 代理:向目标对象应用通知之后创建的代理对象
- 连接点:这也是一个纯逻辑概念,不是语法定义的
- 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方

- 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方
- 切入点:定位连接点的方式,一个切入点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切入点可以通过注解、正则表达式、逻辑运算等方式来定义
- 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
- 如果把连接点看作数据库中的记录,那么切入点就是查询记录的
SQL语句Spring的AOP技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法 - 切点通过
org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件 - 切入点表达式:

- 织入:织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入和运行期织入
3.2 AOP实现方式
spring中的AOP主要是通过代理完成的,动态代理分为JDK动态代理和cglib动态代理,只能拦截容器中的Bean当目标类有接口的情况可以使用
JDK动态代理(默认)和cglib动态代理,没有接口时只能使用cglib动态代理JDK动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口,只代理接口中的方法。因为这个技术要求代理对象和目标对象实现同样的接口动态代理动态生成的代理类会在
com.sun.proxy包下,类名为$proxy1(编号随生成增加),和目标类实现相同的接口,继承java.lang.reflect.Proxy当调用代理对象中的方法时,可以调用
invoke()方法转到目标对象中执行MyService target = new MyServiceImpl(); MyService proxy = (MyService) Proxy.newProxyInstance( target.getClass().getClassLoader(), new Class[]{MyService.class}, (proxyObj, method, arg) -> { System.out.println("代理逻辑:方法调用前"); Object result = method.invoke(target, arg); System.out.println("代理逻辑:方法调用后"); return result; } ); proxy.a();
cglib:通过继承被代理的目标类重写方法实现代理,所以不需要目标类实现接口cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
动态代理失效的情况
由于动态代理是通过继承,然后生成代理类或实例类完成的,因此存在失效的情况
- 方法无法被继承,由于类中的
private私有方法不会被继承,无法通过cglib进行代理 - 方法调用必须经过对象,否则切面失效,比如对象内部调用、
static静态方法都是不能被拦截的 - 通过
final修饰的只能定义在类里,不能出现在接口方法上,不支持JDK动态代理,由于类中的final方法无法被覆写,而cglib需要重写方法,因此无法代理final方法 - 构造函数在生成代理实例之前,因此无法调用代理
也支持使用
AspectJ,已经集成到spring中,当切面太多的话,最好选择AspectJ,它比Spring AOP快很多AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解配置使用AspectJ
前置修改:可以不需要将切入类交给
spring管理可以将切入类
@Component注解删除可以取消
@EnableAspectJAutoProxy注解引入
AspectJ<!-- 包含AspectJrt依赖 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.22.1</version> </dependency>
静态织入:
导入
AspectJ静态织入编译插件<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.15.0</version> <configuration> <!-- 解决与 Lombok 的冲突 --> <forceAjcCompile>true</forceAjcCompile> <sources/> <weaveDirectories> <weaveDirectory>${project.build.directory}/classes</weaveDirectory> </weaveDirectories> <!-- 展示织入信息 --> <showWeaveInfo>true</showWeaveInfo> <encoding>UTF-8</encoding> <!-- 配置要织入的 maven 依赖 --> <weaveDependencies> <!--<weaveDependency>--> <!-- <groupId>org.apache.ibatis</groupId>--> <!-- <artifactId>ibatis-sqlmap</artifactId>--> <!--</weaveDependency>--> </weaveDependencies> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin>idea似乎不支持切面类修改后直接执行,每次修改切面类都需要使用mvn compile命令重新编译项目,不然会出现NoSuchMethodError: aspectOf()异常
加载时织入:在
spring未成功,显示Found Spring's JVM agent for instrumentation即动态织入启用,但切面方法不存在NoSuchMethodError: aspectOf()在
springboot成功了,但无法实现Final方法切面导入与
spring同版本的instrument依赖,用于实现动态织入<dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>6.0.2</version> </dependency>配置
spring配置类,开启动态织入@EnableLoadTimeWeaving添加
resource/META-INF/aop.xml配置文件,此配置文件是aspectj的配置文件,aspectj会在运行时解析此文件,springboot不需要新增此文件<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <weaver options="-verbose -showWeaveInfo"> <!-- 指定需要织入的包或类 --> <include within="com.IocTest.service.*"/> </weaver> <aspects> <!-- 声明你的 Aspect 类 --> <aspect name="com.IocTest.TestAspect"/> </aspects> </aspectj>动态添加依赖
-javaagent:lib\spring-instrument-6.0.2.jar- 模块化导致
final方法无法被织入,添加--add-opens java.base/java.lang=ALL-UNNAMED
- 模块化导致
3.3 注解实现
JoinPoint是AspectJ提供的接口,用来在切面方法中获取被增强方法的上下文信息,可以使用这个接口作为切面方法的一个参数- 获取方法签名:
joinPoint.getSignature(),签名中有方法名getName()、参数列表等信息 - 获取方法参数数组:
joinPoint.getArgs() - 获取代理对象:
joinPoint.getThis() - 获取代理目标对象:
joinPoint.getTarget() - 获取连接点类型:
joinPoint.getKind() - 获取描述信息字符串:
joinPoint.toShortString()、joinPoint.toLongString()
- 获取方法签名:
- 切面类编写
添加
@Aspect的类是切面类,如果是通过注解配置,在配置类上需要开启@EnableAspectJAutoProxy注解,可以使用注解内的proxyTargetClass=true属性来指定强制使用CGLIB代理@Configuration @EnableAspectJAutoProxy(proxyTargetClass=true) public class AppConfig {}相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序,优先级高的切面在最外面,最早拦截进入的通知,但最后拦截退出的通知
// @Aspect表示这个类是一个切面类 @Aspect // @Component注解保证这个切面类能够放入IoC容器 @Component // 优先级数值越小,优先级越高 @Order(1) public class LogAspect { //execution( ... )称为表达式,表示需要切入哪些方法 //内需要填入[方法类型 返回值类型] 方法所属类.方法名称(参数列表) //支持使用*表示全类型 ...表示任意参数函数 @Before("execution(public int com.service.*(..))") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); } @After("execution(* com.service.*(..))") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->后置通知,方法名:"+methodName); } //返回通知能够获取返回值 @AfterReturning(value = "execution(* com.service.*(..))", returning = "result") public void afterReturningMethod(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result); } //异常通知能够获取异常信息 @AfterThrowing(value = "execution(* com.service.*(..))", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex); } @Around("execution(* com.service.*(..))") public Object aroundMethod(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); Object result = null; try { System.out.println("环绕通知-->目标对象方法执行之前"); //目标对象(连接点)方法的执行 result = joinPoint.proceed(); System.out.println("环绕通知-->目标对象方法返回值之后"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("环绕通知-->目标对象方法出现异常时"); } finally { System.out.println("环绕通知-->目标对象方法执行完毕"); } return result; } // 切入点表达式可以重用 @Pointcut("execution(* com.service.impl.UserServiceImpl.*(..))") public void pointcut() {} // 本类内可以直接写方法名 // 其他类需要写全类名.方法名 com.aspect.LogAspect.pointcut() @Before("pointcut()") public void before() { System.out.println("前置通知-->目标对象方法执行之前"); } }
3.3 配置文件实现
使用xml文件配置自动注解
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 基于注解的AOP的实现: 1、将目标对象和切面交给IoC容器管理(注解+扫描) 2、开启AspectJ的自动代理,为目标对象自动生成代理 3、将切面类通过注解@Aspect标识 --> <context:component-scan base-package="com.aop.annotation"></context:component-scan> <aop:aspectj-autoproxy /> </beans>使用xml文件自行配置代理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.aop.xml"></context:component-scan> <aop:config> <!--配置切面类--> <aop:aspect ref="loggerAspect"> <aop:pointcut id="pointCut" expression="execution(* com.service.*(..))"/> <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before> <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after> <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning> <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing> <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around> </aop:aspect> </aop:config> </beans>
4. 事务
spring对jdbc操作封装,需要引入依赖
<dependencies> <!--spring jdbc Spring 持久化层支持jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.2</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.15</version> </dependency> </dependencies>
4.1 JdbcTemplate
spring提供了jdbc封装类JdbcTemplate创建,直接注入
public class JDBCTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; }功能大全
函数名 参数列表 功能 update sql, ...args 执行sql语句完成增删改操作, args为填入sql的参数, 返回操作影响行数 queryForObject sql, requiredType, rowMapper, ...args 执行sql语句,将结果返回,非常灵活,有多种重载方式,参数requireType和rowMapper均可选, requireType为将查询结果强转为此类型, 如Integer; rowMapper为一个接口,返回不同类型数据,使用这个接口里面的实现类完成数据的封装,可使用子类BeanPropertyRowMapper<T>,它首先将这个类实例化,然后通过名称匹配的方式,映射到属性中去,也可以自己实现rowMapper接口 query sql,rowMapper, ...args 查询返回List,在rowMapper完成封装 batchUpdate sql,list 批量更新,list为List<Object[]>类型 测试增删改功能
@Test //测试增删改功能 public void testUpdate(){ //添加功能 String sql = "insert into t_emp values(null,?,?,?)"; int result = jdbcTemplate.update(sql, "张三", 23, "男"); //修改功能 //String sql = "update t_emp set name=? where id=?"; //int result = jdbcTemplate.update(sql, "张三atguigu", 1); //删除功能 //String sql = "delete from t_emp where id=?"; //int result = jdbcTemplate.update(sql, 1); }查询数据返回对象
@Data public class Emp { private Integer id; private String name; private Integer age; private String sex; }//查询:返回对象 @Test public void testSelectObject() { //写法一 // String sql = "select * from t_emp where id=?"; // Emp empResult = jdbcTemplate.queryForObject(sql, // (rs, rowNum) -> { // Emp emp = new Emp(); // emp.setId(rs.getInt("id")); // emp.setName(rs.getString("name")); // emp.setAge(rs.getInt("age")); // emp.setSex(rs.getString("sex")); // return emp; // }, 1); // System.out.println(empResult); //写法二 String sql = "select * from t_emp where id=?"; Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),1); System.out.println(emp); }查询数据返回list集合
@Test //查询多条数据为一个list集合 public void testSelectList(){ String sql = "select * from t_emp"; List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class)); System.out.println(list); }查询返回单个的值
@Test //查询单行单列的值 public void selectCount(){ String sql = "select count(id) from t_emp"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(count); }
4.2 事务声明和操作
- 使用
tx命名空间配置bean.xml文件数据源:事务使用的数据源,作为事务管理器的参数
事务管理器
TransactionManager:提供给spring事务机制,不需要直接调用JdbcTemplate:方便在任何类中使用此工具开启事务扫描
tx:annotation-driven<?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 http://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:jdbc.properties" /> <!-- 配置数据源 --> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 配置 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 装配数据源 --> <property name="dataSource" ref="druidDataSource"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean> <!-- 开启事务的注解驱动 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 --> <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>使用注解配置事务,可以写在类上表示所有类的方法都使用事务,也可以写在方法上,表示当前方法使用事务
//事务注解@Transactional @Transactional public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); //System.out.println(1/0); }使用注解配置类代替
xml文件package com.spring6.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; @Configuration @ComponentScan("com.spring6") @EnableTransactionManagement public class SpringConfig { @Bean public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } @Bean(name = "jdbcTemplate") public JdbcTemplate getJdbcTemplate(DataSource dataSource){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(dataSource); return jdbcTemplate; } @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
4.3 事务的注解属性
只读:设置事务为只读,方便优化数据库性能
@Transactional(readOnly = true) public void doSomething(){ //... 不能写入和修改数据库表 }
超时:设置事务的超时时间,超过指定时间则回滚事务,让其他正常的程序可以执行
// 超过10秒则回滚事务,释放资源 @Transactional(timeout = 10) public void doSomething(){}
- 异常处理:声明式事务默认只针对运行时异常和错误回滚,编译时异常不回滚,可以通过
@Transactional中相关属性设置回滚策略rollbackFor属性:在某个编译时异常发生时回滚,需要设置一个Class类型的对象rollbackForClassName属性:在某个编译时异常发生时回滚,需要设置一个字符串类型的全类名noRollbackFor属性:不在发生某个运行时异常或错误时回滚,需要设置一个Class类型的对象noRollbackForClassName属性:不在发生某个运行时异常或错误时回滚,需要设置一个字符串类型的全类名//虽然购买图书功能中出现了数学运算异常(ArithmeticException) //但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚 //因此购买图书的操作正常执行 @Transactional(noRollbackFor = ArithmeticException.class) //@Transactional(noRollbackForClassName = "java.lang.ArithmeticException") public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); System.out.println(1/0); }
隔离级别一共有四种:与数据库设计的隔离级别一致,隔离级别介绍见:数据库隔离级别
- 读未提交:
READ UNCOMMITTED - 读已提交:
READ COMMITTED - 可重复读:
REPEATABLE READ - 串行化:
SERIALIZABLE
- 读未提交:
使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别,mysql是可重复读、oracle是读已提交 @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交 @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交 @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读 @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
事务传播行为:在
service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为一共有七种传播行为:
REQUIRED:支持当前事务,如果不存在就新建一个(默认),【没有就新建,有就加入】SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行,【有就加入,没有就不管了】MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常,【有就加入,没有就抛异常】REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起,【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务,【不支持事务,存在就挂起】NEVER:以非事务方式运行,如果有事务存在,抛出异常,【不支持事务,存在就抛异常】NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样,【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
使用
@Transactional(propagation = Propagation.REQUIRED)配置事务传播行为
4.4 事务通知
批量管理需要事务通过事务通知实现,需要通过
xml配置
xml配置- 正常配置事务管理器
TransactionManager tx:advice使用切入点表达式配置具体需要事务的部分,代替@Transactional
- 正常配置事务管理器
<?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
http://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">
<!-- 提前引入aspect依赖 -->
<aop:config>
<!-- 配置事务通知和切入点表达式 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
<!-- TransactionManager 配置 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
</beans>5. 资源操作
5.1 Resource 接口
Resource接口:Spring的Resource接口位于org.springframework.core.io中,旨在成为一个更强大的接口,用于抽象对低级资源的访问Resource接口继承了InputStreamSource接口,提供了很多InputStreamSource所没有的方法。InputStreamSource接口,只有一个方法:public interface InputStreamSource { InputStream getInputStream() throws IOException; }
其中一些重要的方法
getInputStream(): 找到并打开资源,返回一个InputStream以从资源中读取。预计每次调用都会返回一个新的InputStream(),调用者有责任关闭每个流exists(): 返回一个布尔值,表明某个资源是否以物理形式存在isOpen: 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为true,InputStream就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回false,但是InputStreamResource除外getDescription(): 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际URL
其他方法
isReadable(): 表明资源的目录读取是否通过getInputStream()进行读取。isFile(): 表明这个资源是否代表了一个文件系统的文件。getURL(): 返回一个URL句柄,如果资源不能够被解析为URL,将抛出IOExceptiongetURI(): 返回一个资源的URI句柄getFile(): 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出FileNotFoundExceptionlastModified(): 资源最后一次修改的时间戳createRelative(): 创建此资源的相关资源getFilename(): 资源的文件名是什么 例如:最后一部分的文件名myfile.txt
5.1.1 实现类
UrlResource类用来访问网络资源,它支持URL的绝对路径http该前缀用于访问基于HTTP协议的网络资源ftp该前缀用于访问基于FTP协议的网络资源file该前缀用于从文件系统中读取资源
ClassPathResource类用来访问类加载路径下的资源,其主要优势是方便访问类加载路径里的资源FileSystemResource类用于访问文件系统资源,使用FileSystemResource来访问文件系统资源并没有太大的优势,因为Java提供的File类也可用于访问文件系统资源ServletContextResource类是ServletContext资源的Resource实现,它解释相关Web应用程序根目录中的相对路径。它始终支持流(stream)访问和URL访问,但只有在扩展Web应用程序存档且资源实际位于文件系统上时才允许java.io.File访问InputStreamResource类是给定的输入流(InputStream)的Resource实现ByteArrayResource类字节数组的Resource实现类,通过给定的数组创建了一个ByteArrayInputStream创建实现类实例
可以直接提供字符串创建对应的实例,这些类都有
Resource的接口方法,可以用于解析文件内容//需要时,构建对应对象 //UrlResource类创建 UrlResource url = null; try { url = new UrlResource("www.baidu.com"); } catch (Exception e) { throw new RuntimeException(e); } //ClassPathResource类创建 ClassPathResource resource = new ClassPathResource("a.txt"); //FileSystemResource类创建 FileSystemResource resource = new FileSystemResource("a.txt");
5.1.2 获取Resource
Spring提供如下两个标志性接口:ResourceLoader:该接口实现类的实例可以获得一个Resource实例,AbstractApplicationContext继承的DefaultResourceLoader实现了该接口ResourceLoaderAware:该接口实现类的实例将获得一个ResourceLoader的引用,提供了获取RourceLoader实例的快捷方式
- 在
ResourceLoader接口里仅有一个方法:Resource getResource(String location):用于返回一个Resource实例。ApplicationContext实现类都实现ResourceLoader接口,因此ApplicationContext可直接获取Resource实例package com.spring6.resouceloader; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.Resource; public class Demo1 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext(); // 通过ApplicationContext访问资源 // ApplicationContext实例获取Resource实例时, // 默认采用与ApplicationContext相同的资源访问策略 Resource res = ctx.getResource("atguigu.txt"); System.out.println(res.getFilename()); } }Resource的实现类选择策略
- 当
Spring应用需要进行资源访问时,实际上并不需要直接使用Resource实现类,而是调用ResourceLoader实例的getResource()方法来获得资源 Spring将默认采用和ApplicationContext相同的策略来访问资源如果
ApplicationContext是FileSystemXmlApplicationContext,结果就是FileSystemResource实例;如果ApplicationContext是ClassPathXmlApplicationContext,结果就是ClassPathResource实例ReosurceLoader将会负责选择Reosurce实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,可通过不同前缀指定强制使用指定的ClassPathResource、FileSystemResource等实现类// 强制指定使用某个特定的实现类 Resource res = ctx.getResource("classpath:bean.xml"); Resrouce res = ctx.getResource("file:bean.xml"); Resource res = ctx.getResource("http://localhost:8080/beans.xml");
- 当
- 在
ResourceLoaderAware接口里只有一个方法:void setResourceLoader(ResourceLoader resourceLoader):在创建bean时用于自动设置ResourceLoader实例,通过ReasourceLoaderAware获取资源加载器,方便获取资源在使用
ApplicationContext时,resourceLoader就是ApplicationContextpublic class ResourceManager implements ResourceLoaderAware { private ResourceLoader resourceLoader; @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public ResourceLoader getResourceLoader() { return resourceLoader; } }
5.2 Resource注入
- 实际上可以直接用字符串类型实现
Resource的注入注解:
public class ResourceManager { @Value("classpath:i18n/message.properties") private Resource resource; }配置文件:
<bean id="resourceManager" class="com.imooc.ioc.ResourceManager"> <property name="resource" value="classpath:i18n/message.properties"/> </bean>
6. 国际化i18n
- java本身是支持国际化的,能够使用
ResourceBundle.getBundle("messages",new Locale("en","GB"))获取properties文件,使用对应getString()方法得到对应字段的值 spring中国际化通过MessageSource接口支持配置
- 设置
ResourceBundleMessageSource类型的对象 - 添加为
list类型的basename属性(实际上是String[]类型),每个元素值对应一个properties文件名,支持占位符 - 添加
defaultEncoding属性,指定编码格式
如果不同文件中有相同的字段,则后面的文件会覆盖前面的文件
配置文件<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <!-- 其中填入对应的properties文件基本名称 --> <!-- 该文件中有:www.com=welcome {0},时间:{1} --> <property name="basenames"> <list> <value>common</value> </list> </property> <property name="defaultEncoding"> <value>utf-8</value> </property> </bean> </beans>注解配置类@Configuration @ComponentScan(basePackages = "com.spring6") public class AppConfig { @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasenames("messages", "validation"); // 支持多个国际化文件 messageSource.setDefaultEncoding("UTF-8"); return messageSource; } }- 设置
使用 *
package com.spring6.javai18n; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.Date; import java.util.Locale; public class I18nDemo { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //传递动态参数,使用数组形式对应{0} {1}顺序 Object[] objs = new Object[]{"Tom",new Date().toString()}; //www.com为资源文件的key值, //objs为资源文件value值所需要的参数,Local.CHINA为国际化为语言 String str=context.getMessage("www.com", objs, Locale.CHINA); System.out.println(str); } }
7. 数据校验Validation
7.1 使用Validator接口
- 通过实现
org.springframework.validation.Validator接口,然后在代码中调用这个类实现此接口
supports方法用来表示此校验用在哪个类型上validate是设置校验逻辑的地点,其中ValidationUtils,是Spring封装的校验工具类,帮助快速实现校验
package com.spring6.validation; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; public class PersonValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Person.class.equals(clazz); } @Override public void validate(Object object, Errors errors) { //如果为空写入异常 ValidationUtils.rejectIfEmpty(errors, "name", "name.empty"); Person p = (Person) object; if (p.getAge() < 0) { errors.rejectValue("age", "error value < 0"); } else if (p.getAge() > 110) { errors.rejectValue("age", "error value too old"); } } }调用该接口,完成检验
- 创建
DataBinder对象,初始化时传入数据对象 - 使用
setValidator()方法,传入数据对象的检验类 - 调用
DataBinder对象的validate()方法,完成数据对象的检验
package com.spring6; import org.springframework.validation.BindingResult; import org.springframework.validation.DataBinder; public class TestValidator { public static void main(String[] args) { //创建person对象 Person person = new Person(); person.setName("lucy"); person.setAge(-1); // 创建Person对应的DataBinder DataBinder binder = new DataBinder(person); // 设置校验 binder.setValidator(new PersonValidator()); // 由于Person对象中的属性为空,所以校验不通过 binder.validate(); //输出结果 BindingResult results = binder.getBindingResult(); System.out.println(results.getAllErrors()); } }- 创建
7.2 使用注解方式校验
Spring整合了Bean validation校验注解规范,按照Bean Validation方式来进行校验,需要导入Bean Validation的实现包,而且需要手动调用实现包中的校验方法进行校验Bean Validation:是一套由Java官方定义的标准化对象验证规范,由于验证数据是否合法引入
Bean Validation相关依赖hibernate-validator:来自Hibernate的校验实现jakarta.el:提供表达式解析机制,可以用于解析消息{key}:解析来自注解的值,比如@Min(value = 1, message = "不能小于{value}")${el}:使用EL表达式,一行能返回值且没有副作用的语句,可以通过内置的${validatedValue}获取需要验证的值
<dependencies> <!-- Bean validation 校验实现包 --> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>7.0.5.Final</version> </dependency> <!-- 消息渲染表达式解析支持 --> <dependency> <groupId>org.glassfish</groupId> <artifactId>jakarta.el</artifactId> <version>4.0.1</version> </dependency> </dependencies>创建配置类,配置中创建
LocalValidatorFactoryBean容器对象Spring Validator的一个实现类,把Bean Validation实现(javax.validation/jakarta.validation)整合到Spring让
Spring的国际化资源(messageSource)能参与错误信息解析可以直接注入到
Controller和Service中,或在SpringMVC中自动配置由于@Valid参数校验@Configuration @ComponentScan("com.spring6.validation") public class ValidationConfig { @Bean public LocalValidatorFactoryBean validator(MessageSource messageSource) { LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); // 把国际化消息源绑定进去 bean.setValidationMessageSource(messageSource); return bean; } @Bean public LocalValidatorFactoryBean validator() { // 默认配置 // return new LocalValidatorFactoryBean(); // 设置消息源 LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean(); // 把国际化消息源绑定进去 bean.setValidationMessageSource(messageSource); return bean; } }
创建实体类,使用
Bean Validation注解定义校验规则规则接口目前在
java EE中,也就是jakarta中在需要约束的字段上添加校验注解
package com.spring6.validation; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; public class User { @NotNull private String name; @Min(0) @Max(120) 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; } }常用的校验注解
@NotNull限制必须不为null@NotEmpty只作用于字符串类型,字符串不为空,并且长度不为0@NotBlank只作用于字符串类型,字符串不为空,并且trim()后不为空串@DecimalMax(value)限制必须为一个不大于指定值的数字@DecimalMin(value)限制必须为一个不小于指定值的数字@Max(value)限制必须为一个不大于指定值的数字@Min(value)限制必须为一个不小于指定值的数字@Pattern(value)限制必须符合指定的正则表达式@Size(max,min)限制字符长度必须在min到max之间@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
使用满足
Bean Validator规范的校验器实现,提供了两种校验器的例子使用
jakarta.validation.Validator标准校验,也就是Bean Validator的标准package com.spring6.validation; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Set; @Service public class MyService1 { // 注入 Validator,也就是配置类中的LocalValidatorFactoryBean @Autowired private Validator validator; public boolean validator(User user){ Set<ConstraintViolation<User>> sets = validator.validate(user); return sets.isEmpty(); } }使用
org.springframework.validation.Validator标准校验package com.spring6.validation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.validation.BindException; import org.springframework.validation.Validator; @Service public class MyService2 { @Autowired private Validator validator; public boolean validaPersonByValidator(User user) { BindException bindException = new BindException(user, user.getName()); validator.validate(user, bindException); return bindException.hasErrors(); } }测试
package com.spring6.validation; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestBeanValidation { @Test public void testMyService1() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService1 myService = context.getBean(MyService1.class); User user = new User(); user.setAge(-1); boolean validator = myService.validator(user); System.out.println(validator); } @Test public void testMyService2() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService2 myService = context.getBean(MyService2.class); User user = new User(); user.setName("lucy"); user.setAge(130); user.setAge(-1); boolean validator = myService.validaPersonByValidator(user); System.out.println(validator); } }
7.3 对方法参数的校验
- 对方法参数校验
创建配置类,配置
MethodValidationPostProcessor,开启方法级别校验在
SpringMVC中,参数解析器会对Controller方法参数进行校验,如果使用springMVC,仅需要Controller方法的校验,可以不配置package com.spring6.validation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; @Configuration @ComponentScan("com.spring6.validation") public class ValidationConfig { @Bean public MethodValidationPostProcessor validationPostProcessor() { return new MethodValidationPostProcessor(); } }创建实体类,使用注解设置校验规则
package com.spring6.validation; import jakarta.validation.constraints.*; public class User { @NotNull private String name; @Min(0) @Max(120) private int age; // message支持从i18n资源文件中读取 @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误") @NotBlank(message = "手机号码不能为空") private String phone; 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; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }定义
Service类,在类上添加@Validated注解,并在方法参数上通过注解操作对象package com.spring6.validation; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @Service @Validated public class MyService { public String testParams(@NotNull @Valid User user) { return user.toString(); } }方法参数校验注解
注解 含义 示例 @Valid用类定义时的注解校验实体类参数 @Valid User user@NotNull不能为 null @NotNull Long id@NotEmpty不能为 null 或空(用于字符串、集合) @NotEmpty String name@NotBlank不能为 null 或空白(只用于字符串) @NotBlank String username@Size(min, max)字符串或集合大小范围 @Size(min=2, max=20)@Min,@Max数值范围 @Min(1) @Max(100)@Positive,@Negative,@PositiveOrZero,@NegativeOrZero正负数校验 @Positive int age@Email邮箱格式 @Email String email@Pattern(regexp = ...)正则表达式校验 @Pattern(regexp="\\d{11}")@Past,@PastOrPresent,@Future,@FutureOrPresent时间相关 @Past LocalDate birth@DecimalMin,@DecimalMax,@Digits小数范围 @DecimalMin("0.01")@AssertTrue,@AssertFalse必须为 true/false @AssertTrue boolean active测试
package com.spring6.validation; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestMethod3 { @Test public void testMyService1() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService myService = context.getBean(MyService.class); User user = new User(); user.setAge(-1); myService.testParams(user); } }
7.4 自定义校验
- 自定义校验:通过
jakarta.validation.Constraint和jakarta.validation.ConstraintValidator来实现自定义校验,也就是自己实现需要的校验逻辑注解自定义校验注解
package com.spring6.validation; import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = {CannotBlankValidator.class}) public @interface CannotBlank { //默认错误消息 String message() default "不能包含空格"; //分组 Class<?>[] groups() default {}; //负载 Class<? extends Payload>[] payload() default {}; //指定多个时使用 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { CannotBlank[] value(); } }编写真正的校验类
package com.spring6.validation; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public class CannotBlankValidator implements ConstraintValidator<CannotBlank, String> { @Override public void initialize(CannotBlank constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { //null时不进行校验,如果有空格校验失败 if (value != null && value.contains(" ")) { //获取默认提示信息 String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); System.out.println("default message :" + defaultConstraintMessageTemplate); //禁用默认提示信息 context.disableDefaultConstraintViolation(); //设置提示语 context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); return false; } return true; } }
8. 提前编译AOT
在代码运行前预先编译成机器码,减少代码运行时间
AOT与JIT:
AOT:在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗- 可以在程序运行初期就达到最高性能,程序启动速度快
- 运行产物只有机器码,打包体积小
- 不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如
JIT - 没有动态能力,同一份产物不能跨平台运行
JIT:在程序运行时,根据算法计算出热点代码,然后进行JIT实时编译- 这种方式吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等
- 但是相对启动速度较慢,并需要一定时间和调用频率才能触发
JIT的分层机制 JIT缺点就是编译需要占用运行时资源,会导致进程卡顿

AOT与JIT AOT实现:使用
GraalVM提供的native-image和Visual Studio的Native Tools命令行工具(提供C/C++编译链)完成提前编译和运行,在此命令行工具中执行以下命令# 编译成 .class 文件 javac HelloWorld.java # HelloWorld.class 文件 native-image -cp . HelloWorld在项目中可以导入
GraalVM的native-image编译插件,然后使用mvn -Pnative native:compile编译为可执行文件,需要有C/C++编译链支持
mvn -Pnative spring-boot:build-image打包成Docker镜像,无需GraalVM<plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> </plugin>具体信息见
Spring boot文档advanced native images topics
