Spring
Spring 简介
- Spring 是由 Rod Johnson 创建的一个开源容器框架,目的是为了解决企业开发的复杂性问题
- 优点:
- 是一个轻量级、非侵入式的框架
- 支持 IOC 和 AOP
- 支持事务处理、支持对框架的整合
- 缺点:配置过于繁琐
Spring 组件说明
- Spring Core:核心模块,利用 IOC 容器来管理类的依赖关系
- Spring AOP:面向切面,提供对面向切面的支持,采用纯 Java 实现,AOP 代理的生成、管理、依赖关系也一并交由 IOC 容器管理
- Spring ORM:对象关系映射,提供了与第三方持久层框架的良好整合
- Spring DAO:持久层模块,对 JDBC 的抽象,简化了 DAO 的开发,以统一的方式使用数据库访问技术
- Spring Context:应用上下文,它是一个配置文件,提供框架式的 Bean 访问方式,及企业级功能 (JNDI、定时任务、国际化等)
- Spring Web:Web 模块,提供了针对 Web 开发的集成特性
- Spring MVC:MVC 模块,提供面向 Web 应用的 Model - View - Controller 实现
Spring 使用步骤
-
搭建 Maven 工程
-
引入 依赖
spring-webmvc
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.19</version> </dependency>
-
编写 applicationContext.xml 放在 resource 目录下
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- p 标签使用 --> <bean id="addressTwo" class="com.spring.entity.Address" p:address="蜀国*荆州"/> <!-- c 标签使用 --> <bean id="user" class="com.spring.entity.UserThree" c:name="朱六" c:age="20"/> <!-- 定义引用类 --> <bean id="address" class="com.spring.entity.Address"> <property name="address" value="蜀国*汉中"/> </bean> <!-- 复杂对象装配 --> <bean id="student" class="com.spring.entity.Student"> <!-- 普通属性注入 --> <property name="name" value="赵云"/> <!-- 引用属性注入 --> <property name="address" ref="address"/> <!-- 数组注入 --> <property name="books"> <array> <value>红楼梦</value> <value>西游记</value> <value>水浒传</value> <value>三国演义</value> </array> </property> <!-- List 注入 --> <property name="hobby"> <list> <value>听歌</value> <value>写字</value> <value>Code</value> </list> </property> <!-- Map 注入 --> <property name="tag"> <map> <entry key="姓" value="赵"/> <entry key="名" value="云"/> <entry key="字" value="子龙"/> </map> </property> <!-- Set 注入 --> <property name="games"> <set> <value>澄海3C</value> <value>魔兽争霸</value> <value>红色警戒</value> <value>真三国无双</value> </set> </property> <!-- Properties 注入 --> <property name="info"> <props> <prop key="学号">蜀007</prop> <prop key="姓名">赵云</prop> <prop key="性别">男</prop> </props> </property> </bean> </beans>
-
加载容器:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
-
从容器中获取 Bean:
Student student = context.getBean("student", Student.class);
Spring 核心功能
IOC 控制反转
- IOC 即:控制反转,是指控制权限的转移,程序不再负责对象的创建和维护,而是交由容器负责。
- IOC 内部使用一个单例对象的 Map 对 Bean 进行管理,====> map(BeanName, 该 Bean 的实例)
DI 依赖注入
- DI 即:依赖注入,是 IOC 思想的一种实现
- 依赖是指:Bean 对象的创建依赖于容器
- 注入是指:Bean 依赖的资源由容器类进行设置和装配
- 所谓依赖注入:就是由 IOC 容器在运行期间,动态的将依赖关系注入到对象中
依赖注入方式:
- 构造器注入:将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象时注入
- 优点:对象初始化完成后即可获得可用对象
- 缺点:当需要注入的对象很多时,构造器的参数列表将会很长,不够灵活;若有多重注入方式,每种方式只需注入几个依赖,就需要提供多个重载的构造函数,非常麻烦。
- Setter 方法注入:IOC 通过调用成员变量的 set 方法将被依赖对象注入给依赖类
- 优点:灵活,可以选择性的注入所需的对象
- 缺点:依赖对象初始化完成后由于还尚未注入被依赖对象,因此还不能使用
- 接口注入:依赖类必须要实现指定接口,然后实现该接口的一个函数,该函数就是用于依赖注入,该函数的参数就是要注入的对象
- 优点:接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可
- 缺点:侵入性太强,不建议使用
依赖注入配置
<!-- 通过该方式创建 Bean,默认使用无参构造方法 -->
<bean id="userOne" class="com.spring.entity.UserOne">
<!-- 通过该方式注入,则默认使用 Setter 方法,即实体中必须定义 Setter 方法 -->
<property name="name" value="张三"/>
</bean>
<!-- 通过有参构造方法注入对象 -->
<bean id="userTwo" class="com.spring.entity.UserTwo">
<!-- 该方式注入,参数个数与构造函数中的参数个数保持一致,且值的类型要与实体中定义的类型保持一致 -->
<constructor-arg index="0" value="李四"/>
<constructor-arg index="1" value="18"/>
</bean>
<!-- 通过有参构造方法注入对象 -->
<bean id="userThree" class="com.spring.entity.UserThree">
<!-- 该方式注入,参数个数与构造函数中的参数个数保持一致,并指定属性所对应的类型当两个属性的类型一样时,容易出错,故不建议使用 -->
<constructor-arg type="java.lang.String" value="王五"/>
<constructor-arg type="java.lang.Integer" value="19"/>
</bean>
p 命名空间和 c 命名空间
即:在 <beans> 标签引入 xmlns:p 和 xmlns:c 的约束
- p 标签:对应 Setter 方法注入
- c 标签:对应构造器注入
p/c 标签使用
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 原 Setter 注入 -->
<bean id="address" class="com.spring.entity.Address">
<property name="address" value="蜀国*汉中"/>
</bean>
<!-- p 标签使用 -->
<bean id="addressTwo" class="com.spring.entity.Address" p:address="蜀国*荆州"/>
<!-- 原构造器注入 -->
<bean id="userTwo" class="com.spring.entity.UserTwo">
<constructor-arg index="0" value="李四"/>
<constructor-arg index="1" value="18"/>
</bean>
<!-- c 标签使用 -->
<bean id="user" class="com.spring.entity.UserThree" c:name="朱六" c:age="20"/>
</beans>
Java 配置类实现自动装配
-
定义一个 Java 类
-
添加 @Configuration 注解
-
使用 @Bean 注解需要装配的 Bean
RestTemplate 示例
@Configuration public class ConfigBean { @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
-
通过注解容器加载配置
获取资源
// 通过容器读取配置 ApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class); // 通过 ConfigBean 中的方法名获取需要装配的类 RestTemplate restTemplate = context.getBean("getRestTemplate", RestTemplate.class);
xml 实现自动装配
-
byName:需保证所有 bean 的 id 唯一,且要装配的 bean (Cat、Dog) 要与 set 方法的值一致
byName
<!-- 定义 Cat、Dog --> <bean id="cat" class="com.spring.entity.autowired.Cat"/> <bean id="dog" class="com.spring.entity.autowired.Dog"/> <!-- 定义 Person 并自动装配 Cat 和 Dog --> <bean id="person" class="com.spring.entity.autowired.Person" autowire="byName"> <property name="name" value="张飞"/> </bean>
-
byType:可以不定义 id 属性,但需保证 bean 的 class 唯一,且要装配的 bean (Cat、Dog) 的类型要与 set 方法的类型保持一致
byType
<!-- 定义 Cat、Dog --> <bean class="com.spring.entity.autowired.Cat"/> <bean class="com.spring.entity.autowired.Dog"/> <!-- 定义 Person 并自动装配 Cat 和 Dog --> <bean id="person" class="com.spring.entity.autowired.Person" autowire="byType"> <property name="name" value="张飞"/> </bean>
注解实现自动装配
-
引入 context 约束并开启自动装配
xmlns:context="http://www.springframework.org/schema/context"
引入 context 并开启自动装配
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 定义 Cat、Dog --> <bean id="cat" class="com.spring.entity.autowired.Cat"/> <bean id="dog" class="com.spring.entity.autowired.Dog"/> <!-- region 引入 context 通过注解实现自动装配 --> <!-- 该方式需引入 context 并在 Person 的属性中添加 @Autowired 注解 --> <!-- 开启自动配置 --> <context:annotation-config/> <bean id="personTwo" class="com.spring.entity.autowired.PersonTwo"> <property name="name" value="赵云"/> </bean> </beans>
-
属性中添加 @Autowired 注解;此方式基于反射实现,无需 set 方法 (该注解也可加在 set 方法上)
实体上注解的使用
public class PersonTwo { private String name; @Autowired private Cat cat; @Autowired private Dog dog; ...... }
AOP 面向切面编程
- 在不影响原有业务的情况下实现动态增强
静态代理
角色:
- 抽象角色:接口或者抽象类
- 真实角色:需要被代理的角色
- 代理角色:代理对象,拥有真实角色的能力,且具备扩展能力
- 访问角色:访问代理对象的角色
优点:
- 可以使真实角色的操作更加纯粹,只做自己该做的事
- 实现业务的分工
- 公共业务发生扩展时,方便集中管理
缺点:一个真实角色需要对应一个代理角色,代码量会翻倍 -- 使用动态代理解决
动态代理
角色:同静态代理一样
区别:在于动态代理的代理类是动态生成的,而不是由程序员直接写死的
实现方式:
- 基于接口的实现:JDK 动态代理
- 基于类的实现:cglib
- 基于 Java 字节码的实现:javassist
动态代理相关类:Proxy、InvocationHandler
核心概念
- 横切关注点:跨越多个模块的方法或功能,即与业务功能无关的,但又是我们关注的部分
- 切面 (Aspect):将关注点抽象成一个类,这个类就叫切面
- 通知 (Advice):切面里的方法就叫通知
- 目标 (Target):被通知对象,接口或方法
- 代理 (Proxy):向目标对象应用通知之后创建的对象,即生成的代理类
- 切入点 (PointCut):切面通知执行的地点,即在哪执行
- 连接点 (JointPoint):与切入点匹配的执行点
通知类型
- 前置通知:在方法执行之前执行
- 后置通知:在方法执行之后执行
- 返回通知:在方法返回结果中执行
- 异常通知:在方法抛出异常时执行
- 环绕通知:围绕着方法执行
Jdk 动态代理 实现 AOP
-
新建接口及实现类
-
新建切面类
定义切面
public class MyAspect { public void start() { System.out.println("事务开始~~"); } public void end() { System.out.println("事务结束~~"); } }
-
新建代理类实现 InvocationHandler 接口;重写 invoke 方法;并定义一个方法用于返回代理后对象
JdkProxy 代理类
public class JdkProxy implements InvocationHandler { private Object object; /** * 创建代理 * @param object 被代理对象 * @return 返回增强后对象 */ public Object createProxy(Object object) { this.object = object; // 类加载器 ClassLoader classLoader = JdkProxy.class.getClassLoader(); // 被代理对象实现的所有接口 Class<?>[] clazz = object.getClass().getInterfaces(); // 使用代理进行增强,返回的是代理后的对象 return Proxy.newProxyInstance(classLoader, clazz, this); } /** * 所有动态代理类的方法调用,都会交由 invoke() 方法去处理 * @param proxy 被代理后的对象 * @param method 将要执行的方法 * @param args 方法时需要的入参 * @return 返回 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 声明切面 MyAspect myAspect = new MyAspect(); // 切入到程序执行前 myAspect.start(); // 在目标上调用 Object invoke = method.invoke(object, args); // 切入到程序执行后 myAspect.end(); return invoke; } }
-
新建测试类
测试方法
@Test public void test() { // 创建代理对象 JdkProxy jdkProxy = new JdkProxy(); // 创建目标对象 UserDao userDao = new UserDaoImpl(); // 从代理对象中获取增强后的目标对象 UserDao proxy = (UserDao) jdkProxy.createProxy(userDao); // 执行添加用户方法 proxy.addUser(); proxy.deleteUser(); proxy.updateUser(); proxy.queryUser(); }
Spring 实现 AOP
-
引入织入包
引入织入依赖
<!-- aspect aop 织入 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.9.1</version> </dependency>
-
新建切面,并实现相应的 Advice
编写切面
public class BeforeLoggerAspect implements MethodBeforeAdvice { /** * 前置通知 * @param method 要执行的目标方法 * @param args 参数 * @param target 目标对象 * @throws Throwable 抛出异常 */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + " 的 " + method.getName() + " 方法被执行了"); } } /** * 提供返回结果的后置通知 */ public class AfterLoggerAspect implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了 " + method.getName() + " 方法;返回了 " + returnValue); } }
-
配置 AOP,需引入 aop 的约束
AOP 配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 注册 Bean --> <bean id="userDaoImplTwo" class="com.spring.aop.dao.UserDaoImplTwo"/> <bean id="beforeLoggerAspect" class="com.spring.aop.aspect.BeforeLoggerAspect"/> <bean id="afterLoggerAspect" class="com.spring.aop.aspect.AfterLoggerAspect"/> <!-- region 方式一 --> <!-- 配置 AOP --> <aop:config> <!-- 切入点配置 expression 表达式: * : 任意位置执行 com.spring.aop.dao.UserDaoImplTwo.* : UserDaoImplTwo 类下的所有方法 (..) : 任意参数 --> <aop:pointcut id="pointcut" expression="execution(* com.spring.aop.dao.UserDaoImplTwo.*(..))"/> <!-- 执行环绕增强 --> <!-- 将 beforeLoggerAspect 类,切入到 pointcut 方法上 --> <aop:advisor advice-ref="beforeLoggerAspect" pointcut-ref="pointcut"/> <!-- 将 afterLoggerAspect 类,切入到 pointcut 方法上 --> <aop:advisor advice-ref="afterLoggerAspect" pointcut-ref="pointcut"/> </aop:config> <!-- endregion --> <!-- region 方式二 --> <bean id="myAspect" class="com.spring.aop.aspect.MyAspect"/> <aop:config> <!-- 自定义切面 --> <aop:aspect ref="myAspect"> <!-- 切入点 --> <aop:pointcut id="point" expression="execution(* com.spring.aop.dao.UserDaoImplTwo.*(..))"/> <!-- 通知 --> <aop:before method="start" pointcut-ref="point"/> <aop:after method="end" pointcut-ref="point"/> </aop:aspect> </aop:config> <!-- endregion --> </beans>
-
编写测试类
测试方法
@Test public void springImplAopTest() { ApplicationContext context = new ClassPathXmlApplicationContext("aspect.xml"); // 注册时要配置 Bean 的实现类,而此处代理的要指向接口 UserDao userDao = context.getBean("userDaoImplTwo", UserDao.class); userDao.addUser(); }
注解实现 AOP
-
新建自定义类,添加
@Aspect
注解;自定义方法并开启注解自定义切面
@Aspect public class AnnotationAspect { @Before("execution(* com.spring.aop.dao.UserDaoImplThree.*(..))") public void before() { System.out.println("======== 方法执行前执行 ========"); } @After("execution(* com.spring.aop.dao.UserDaoImplThree.*(..))") public void after() { System.out.println("======== 方法执行后执行 ========"); } @Around("execution(* com.spring.aop.dao.UserDaoImplThree.*(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕前:" + proceedingJoinPoint); Object proceed = proceedingJoinPoint.proceed(); System.out.println("环绕后:" + proceed); return proceed; } }
-
开启 AOP 配置
AOP 配置
<!-- region 方式三 --> <bean id="userDaoImplThree" class="com.spring.aop.dao.UserDaoImplThree"/> <bean id="annotationAspect" class="com.spring.aop.aspect.AnnotationAspect"/> <!-- 开启切面自动配置 --> <aop:aspectj-autoproxy/> <!-- endregion -->
注意:<aop:aspectj-autoproxy proxy-target-class="true" /> 中 proxy-target-class 的属性默认为 false,即 JDK 实现;若改为 true 则为 cglib 实现
-
编写测试类
测试方法
@Test public void annotationImplAopTest() { ApplicationContext context = new ClassPathXmlApplicationContext("aspect.xml"); UserDao userDao = context.getBean("userDaoImplThree", UserDao.class); userDao.addUser(); }
Spring 事务管理
-
事务:即作为单个逻辑工作单元执行的一系列操作
-
分为:编程式事务管理和声明式事务管理
-
编程式事务:在代码中采用 try catch 方式处理
编程式事务管理
public void createUser() { TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { userMapper.insertUser(user); } catch (Exception e) { transactionManager.rollback(txStatus); throw e; } transactionManager.commit(txStatus); }
-
声明式事务:采用声明的方式来处理事务,即:在配置文件中声明
声明式事务管理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 注册 DataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/pro?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!-- 配置声明式事务;即开启事务 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean> <!-- region 结合 AOP 实现事务的织入 --> <!-- 配置事务通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 给哪些方法配置事务 --> <tx:attributes> <!-- 可在此直接配置隔离级别和传播行为 --> <tx:method name="update" isolation="DEFAULT" propagation="REQUIRED"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 配置事务切入 --> <aop:config> <!-- 切入点 --> <aop:pointcut id="txPointCut" expression="execution(* com.spring.aop.dao.*.*(..))"/> <!-- 通知 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> <!-- endregion --> </beans>
事务的 ACID 特性
- 原子性:要么都执行,要么都不执行
- 一致性:事务开始前和事务结束后,数据库的完整性不能被破坏
- 隔离性:与其他并发事务所做的修改保持隔离、互不干扰
- 持久性:事务完成后对系统的影响是永久的
Spring 事务的隔离级别
@Transactional(isolation=Isolation.DEFAULT)
- DEFAULT:默认,使用数据库的隔离级别
- MySQL 默认:可重复读;Oracle 默认:读已提交
- READ_UNCOMMITTED (读-未提交):会出现脏读、幻读、不可重复读问题
- READ_COMMITTED (读-已提交):避免脏读,但可能出现幻读和不可重复读
- REPEATABLE_READ (可重复读):避免脏读和不可重复读,但可能出现幻读
- SERIALIZABLE (串行化):避免以上所有问题,最安全、性能影响极大
Spring 事务的传播行为
@Transaction(propagation=Propagation.REQUIRED)
- 事务传播是指:多个事务方法进行调用时,事务是如何传播的;即:方法 A 是一个事务方法,A 在执行过程中调用了方法 B,那么方法 B 要不要事务?要的话又该怎么执行?
保证在同一事务中
- REQUIRED; // 支持当前事务,若不存在则新建一个 (即:若 A 存在事务,则 B 加入到该事务中;若 A 不存在事务,则 B 另起事务执行)
- SUPPORTS; // 支持当前事务,若不存在则不使用事务
- MANDATORY; // 支持当前事务,若不存在则抛出异常
- NESTED; // 支持当前事务,若 A 存在事务,则执行一个嵌套事务 (即 A 为父事务,B 为子事务);若不存在则新建事务
保证不在同一事务中
- REQUIRES_NEW; // 总是创建新事务 (不管 A 是否有事务,B 都另起事务执行)
- NOT_SUPPORTED; // 存在事务则挂起,一直执行非事务操作
- NEVER; // 总是执行非事务,若当前存在事务则抛出异常
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)