Spring学习总结---适合初学者的学习总结
Spring学习总结
01_IOC_DI
1.1 spring介绍
1.1.1 spring核心理念
这部分初看会看不太懂,这里只是初步介绍,大致了解即可
Spring 围绕两大核心思想设计,贯穿所有功能模块:
- 控制反转(IoC,Inversion of Control)
- 传统开发中,对象的创建、依赖管理由开发者手动控制(如
new UserService()); - IoC 反转了这一过程:对象的创建、依赖注入由 Spring 容器(IoC 容器)统一管理,开发者只需定义 “需要什么对象”,无需关心 “对象如何创建”。
- 例:开发者只需在类上标注
@Component,Spring 会自动扫描并创建该类的实例,存入容器;若需要依赖其他对象,用@Autowired标注即可,Spring 会自动从容器中 “注入” 依赖。
- 传统开发中,对象的创建、依赖管理由开发者手动控制(如
- 面向切面编程(AOP,Aspect-Oriented Programming)
- 解决 “横切关注点” 问题:如日志记录、事务管理、权限校验等功能,若在每个业务方法中重复编写,会导致代码冗余、维护困难;
- AOP 允许将这些 “横切逻辑” 抽离为独立的 “切面(Aspect)”,通过配置指定 “在哪些方法执行前后执行切面逻辑”,实现业务代码与非业务代码的解耦。
- 例:用
@Transactional标注业务方法,Spring 会通过 AOP 自动在方法执行前开启事务、执行后提交 / 回滚事务,开发者无需手动编写事务控制代码。
1.1.2 Spring 的优势
- 低耦合:通过 IoC 容器管理对象依赖,避免硬编码创建对象,降低模块间耦合;
- 高扩展性:AOP 支持横切逻辑复用,模块新增功能无需修改原有代码;
- 简化开发:封装底层 API(如 JDBC、事务),开发者专注业务逻辑,减少冗余代码;
- 生态完善:从单体应用(Spring MVC)到微服务(Spring Cloud),覆盖全场景开发需求;
- 社区活跃:作为 Java 主流框架,文档丰富、问题解决方案多,版本更新稳定。
1.2 IOC/DI定义理解
- IOC(控制反转),是一种编程思想
- 使用IOC之前,用户每次对需求进行变更之后,需要程序员手动去改程序源代码
- 使用IOC之后,用户每次变更,不需要再改变源代码,程序会自适应用户需求,本质是将对象的创建、依赖管理的主动权,从程序员编写的业务代码中,转移到 Spring 容器(IOC 容器)中
- IOC 是思想,DI(依赖注入)是实现 IOC 的具体手段——Spring 通过 DI 完成对象依赖的注入,从而实现了 IOC 的核心思想
1.3 IOC思想理解
场景说明
假设我们要开发一个简单的用户服务:UserService 需要依赖 UserDao 来操作数据,需求可能会变更(比如从 MySQLDao 切换到 OracleDao)。
1.3.1 传统开发方式(无 IOC)
- 定义 Dao 接口和实现类
// Dao 接口
public interface UserDao {
void query(); // 查询用户数据
}
// MySQL 实现
public class MySQLDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("用 MySQL 查询用户数据");
}
}
// Oracle 实现(未来可能需要切换)
public class OracleDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("用 Oracle 查询用户数据");
}
}
- 定义 Service 类(依赖 Dao)
public class UserServiceImpl {
// 1. 手动创建依赖对象
private UserDao userDao = new MySQLDao(); // 直接依赖 MySQLDao 实现类
// 业务方法:调用 Dao 的查询
public void getUser() {
userDao.getUser;
}
}
- 测试运行
public class MyTest {
public void test(){
UserService userService = new UserServiceImpl(); // 手动创建 Service 对象
userService.getUser(); // 输出:用 MySQL 查询用户数据
}
}
问题分析
- 控制权在代码中:
UserService自己创建MySQLDao(new MySQLDao()),一旦需求变更(要切换到OracleDao),必须修改UserService的源代码(把new MySQLDao()改成new OracleDao())。 - 耦合度高:
Service与具体的Dao实现类强绑定,扩展或修改时需要改动多处代码。
1.3.2 Spring IOC 开发方式(控制反转)
-
保持 Dao 接口和实现类不变(同上)
-
修改 Service 类
public class UserServiceImpl {
// 1. 只定义依赖接口,不手动创建对象(无 new 关键字)
private UserDao userDao;
// 2. 提供 Set 方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// 业务方法:调用 Dao 的查询
public void getUser() {
userDao.getUser();
}
}
- 测试
@Test
public void test(){
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new MySqlDaoImpl() );
service.getUser();
//那我们现在又想用Oracle去实现呢,只需要调用set方法时传入Oracle
service.setUserDao( new UserDaoOracleImpl() );
service.getUser();
1.4 Spring的配置说明与应用
1.4.1 Spirng的配置说明
- 别名alias:
用以给bean起别名,可以用多次起多个别名
<alias name="user" alias="user1"/>
<alias name="user" alias="user2"/>
- bean:
id:bean的唯一标识符
class:bean对象所对应的全限定名:包名+类型
name:也是别名,可以取多个别名,别名之间用空格,逗号,分号隔开皆可
<bean id="oracleImpl" name="o o1,o2;o3" class="com.pp.dao.OracleDaoImpl"/>
- import:
可以将多个配置文件导入合并为一个
1.4.2 Spring的应用---HelloSpring
- 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
- 创建实体类
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
- 创建spring配置文件,命名为
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象 , 由Spring创建和管理
id为对象名,class为类属性-->
<bean id="hello" class="com.pp.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
</beans>
- 通过ioc容器获取对象
- 创建ioc容器
ApplicationContext context = new classPathXmlApplicationContext("beans.xml"); - 从ioc容器中获取其已创建的对象
Hello hello = (Hello) context.getBean("hello"); - 调用
hello.show();测试
1.4.3 1.3案例的代码Spring版
-
保持 Dao 接口和实现类不变(同上)
-
保持UserService接口和实现类不变(同上)
-
通过 Spring 配置文件定义对象和依赖关系(从new对象转为从spring容器获取对象)
创建 applicationContext.xml 配置文件,声明所有对象和依赖:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. 定义 Dao 对象 -->
<bean id="mysqlImpl" class="com.pp.dao.MySQLDaoImpl"/>
<bean id="oracleImpl" class="com.pp.dao.OracleDaoImpl"/>
<!-- 2. 定义 Service 对象,并注入 Dao 依赖 -->
<bean id="serviceImpl" class="com.pp.dao.UserServiceImpl">
<!--引用另外一个bean , 不是用value 而是用 ref-->
<property name="UserDao" ref="oracleImpl"/> <!-- 想切换到 Oracle 只需改这里为 ref="oracleImpl" -->
</bean>
</beans>
- 从 Spring 容器获取对象并使用
public void test(){
// 1. 创建 Spring IOC 容器(加载配置文件,容器会自动创建所有对象并注入依赖)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从容器中获取 UserService 对象(无需手动 new)
UserServiceImpl serviceImpl = (UserServiceImpl)context.getBean("serviceImpl");
// 3. 调用业务方法
serviceImpl.getUser(); // 输出:用 MySQL 查询用户数据(若配置 ref="oracleDao" 则输出 Oracle)
}
1.5 ID(依赖注入)的三种核心方式
1.5.1 DI(依赖注入)说明:
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
DI 是 IOC 的实现手段,核心是“容器为 Bean 注入所需依赖”,主要分为 3 类:
1.5.2 构造器注入,三种赋值方式:
- 下标赋值:按构造方法参数的下标匹配(不推荐,参数顺序变了会出错)
<bean id="user" class="com.pp.pojo.User">
<!-- 下标 0 对应构造方法第一个参数,1 对应第二个 -->
<constructor-arg index="0" value="pp"/>
<constructor-arg index="1" value="18"/>
</bean>
- 通过类型创建:按参数类型匹配(不推荐,若有多个同类型参数会冲突)
<bean id="user" class="com.pp.pojo.User">
<constructor-arg type="java.lang.String" value="pp"/>
<constructor-arg type="int" value="18"/>
</bean>
- 通过参数名设置(推荐)
<bean id="user" class="com.pp.pojo.User">
<constructor-arg name="name" value="pp"/>
</bean>
1.5.3 setter注入(通过set方法注入):
适合 “非必填属性”,需满足两个前提:
- Bean 类必须提供属性的 Set 方法(如
setName(String name)); - 无参构造方法(Spring 默认调用无参构造创建 Bean,若自定义了有参构造,需显式定义无参构造)。
<!-- 先定义依赖的 Bean(Address) -->
<bean id="address" class="com.pp.pojo.Address">
<property name="detail" value="北京市海淀区"/>
</bean>
<bean id="student" class="com.pp-pojo.student">
<!--普通值注入(String,int等),用value-->
<property name="name" value="pp"/>
<!--Bean注入,用ref关联bean的id-->
<property name="address" ref="address"/>
<!--数组类型-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒转</value>
<value>三国演义</value>
</array>
</property>
<!--map类型(key-value 键值对)-->
<property name="card">
<map>
<entry key="身份证" value="123123"/>
<entry key="银行卡" value="132132"/>
</map>
</property>
<!--Set类型(无重复元素)-->
<property name="games">
<set>
<value>LoL</value>
<value>CoC</value>
<value>BoB</value>
</set>
</property>
<!--null(若不配值,属性默认是null)-->
<property name="wife">
<null/>
</property>
<!--Properties类型(常用于配置键值对,如数据库连接信息)-->
<property name="info">
<props>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
1.5.4 命名空间注入(构造器/setter注入简化版)
通过 p/c 命名空间简化 XML 配置,本质是 “语法糖”,底层仍是 Setter / 构造器注入。
- 前提:导入命名空间
需在 Spring 配置文件的 <beans> 标签中添加命名空间声明:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!-- 导入 p 命名空间(对应 Setter 注入) -->
xmlns:p="http://www.springframework.org/schema/p"
<!-- 导入 c 命名空间(对应构造器注入) -->
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
- p 命名空间注入(简化 Setter 注入)
格式:p:属性名="普通值" 或 p:属性名-ref="引用 Bean 的 id"
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.pp.pojo.User"
p:name="pp"
p:age="18"
p:address-ref="address"/>
- c命名空间注入(简化构造器注入)
格式:c:属性名="普通值" 或 c:属性名-ref="引用 Bean 的 id"
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="com.pp.pojo.User"
c:name="pp"
c:age="18"
c:address-ref="address"/>
补充:
从容器获取 Bean:通过 Bean 的 id 或 class 类型从容器中获取实例
// 方式 1:通过 id 获取(需强转类型)
Book book1 = (Book) context.getBean("book");
// 方式 2:通过 id + 类型获取(无需强转,推荐)(注意:必须是接口.class,不能是其实现类)
Book book2 = context.getBean("book", Book.class);
// 方式 3:通过类型获取(若该类型只有一个 Bean 实例可用)
Book book3 = context.getBean(Book.class);
02_bean的自动装配
2.1 bean的作用域

2.1.1 singleton(单例模式,spring默认模式):
<bean id="user" class="com.pp.pojo.User" c:age="18" c:name="pp" scope="singleton"/>
2.1.2 prototype(原型模式):
每次从容器get对象时,都会产出一个新的对象
<bean id="accountservice" class="com.something.DefaultAccountservice" scope="prototype"/>
2.1.3 其余的模式只能在web开发中使用,不作介绍
2.2 xml自动装配bean
2.2.1 搭建环境
- 新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
- 新建一个用户类 User
public class User {
private Cat cat;
private Dog dog;
// 编写set/get/tostring
}
- 编写Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.pp.pojo.Dog"/>
<bean id="cat" class="com.pp.pojo.Cat"/>
<bean id="user" class="com.pp.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
- 测试
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
user.getCat().shout();
user.getDog().shout();
}
2.2.2 autowire byName(按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
此时通过使用byName
<bean id="user" class="com.pp.pojo.User" autowire="byName"/>
再次测试,结果依旧成功输出,这时如果将cat的bean id改为catXXX
再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结
当一个bean节点带有 autowire byName的属性时。
- 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
- 去spring容器中寻找是否有此字符串名称id的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
2.2.3 autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常:NoUniqueBeanDefinitionException
测试:
-
将user的bean配置修改一下 : autowire="byType"
-
测试,正常输出
-
在注册一个cat 的bean对象!
<bean id="dog" class="com.pp.pojo.Dog"/>
<bean id="cat" class="com.pp.pojo.Cat"/>
<bean id="cat2" class="com.pp.pojo.Cat"/>
<bean id="user" class="com.pp.pojo.User" autowire="byType"/>
-
测试,报错:NoUniqueBeanDefinitionException
-
删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
2.3 使用注解进行自动装配
准备工作:利用注解的方式注入属性。
- 在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
- 开启属性注解支持!
<context:annotation-config/>
2.3.1 @Autowired
-
@Autowired是按类型自动转配的,不支持id匹配,也就是说id叫啥都一样。 -
需要导入
spring-aop的包!
测试:
- User类里,cat和dog属性加上
@Autowired
@Autowired
private Cat cat;
@Autowired
private Dog dog;
- 配置文件
<context:annotation-config/>
<bean id="dog" class="com.pp.pojo.Dog"/>
<bean id="cat" class="com.pp.pojo.Cat"/>
<bean id="user" class="com.pp.pojo.User"/>
- 测试,成功输出结果
补充:
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
2.3.2 @Qualifier
-
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配 -
@Qualifier不能单独使用
- 配置文件修改内容,保证类型存在对象。且名字不为类的默认名字
<bean id="dog1" class="com.pp.pojo.Dog"/>
<bean id="dog2" class="com.pp.pojo.Dog"/>
<bean id="cat1" class="com.pp.pojo.Cat"/>
<bean id="cat2" class="com.pp.pojo.Cat"/>
-
没有加
@Qualifier测试,直接报错 -
在属性上添加
@Qualifier注解
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog1")
private Dog dog;
- 测试,成功输出结果
2.3.3 @Resource
@Resource如有指定的name属性,先按该属性进行byName方式查找装配;- 其次再进行默认的
byName方式进行装配; - 如果以上都不成功,则按
byType的方式自动装配。 - 都不成功,则报异常
测试一:
- User类
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
- xml文件
<bean id="dog" class="com.pp.pojo.Dog"/>
<bean id="cat1" class="com.pp.pojo.Cat"/>
<bean id="cat2" class="com.pp.pojo.Cat"/>
- 测试,成功输出结果
测试二:
- User类
@Resource
private Cat cat;
@Resource
private Dog dog;
- xml文件
<bean id="dog" class="com.pp.pojo.Dog"/>
<bean id="cat1" class="com.pp.pojo.Cat"/>
- 测试,成功输出结果,其先进行
byName查找,失败;再进行byType查找,成功。
小结:
@Resource兼具@Autowired和@Qualifier的效果
03_使用注解开发
在spring4之后,想要使用注解形式,必须得要引入aop的包(引入spring-webmvc依赖时已导入)
在配置文件当中,还得要引入一个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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
3.1 bean的实现
- 配置扫描哪些包下的注解
<!--指定注解扫描包-->
<context:component-scan base-package="com.pp.pojo"/>
- 在指定包下编写类,增加注解
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "pp";
}
- 测试
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
3.2 属性注入
使用注解注入属性
- 可以不用提供set方法,直接在直接名上添加@value("值")
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("pp")
// 相当于配置文件中 <property name="name" value="pp"/>
public String name;
}
2、如果提供了set方法,在set方法上添加@value("值");
@Component("user")
public class User {
public String name;
@Value("pp")
public void setName(String name) {
this.name = name;
}
}
3.3 一些衍生注解
3.3.1 @Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
3.3.2 自动装配注解
在2.3中有讲解
3.3.3 作用域
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂,所有的对象都会销毁。
- prototype:多例模式。关闭工厂,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("pp")
public String name;
}
小结
-
XML与注解比较
-
XML可以适用任何场景 ,结构清晰,维护方便
-
注解不是自己提供的类使用不了,开发简单方便
-
-
xml与注解整合开发:推荐最佳实践
-
xml管理Bean
-
注解完成属性注入
-
使用过程中, 可以不用扫描,扫描是为了类上的注解
-
-
<context:annotation-config/>作用:-
进行注解驱动注册,从而使注解生效
-
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
-
如果不扫描包,就需要手动配置bean
-
如果不加注解驱动,则注入的值为null
-
3.4 基于Java类进行配置
步骤:
1、编写一个实体类,Dog
@Component //将这个类标注为Spring的一个组件,放到容器中
public class Dog {
public String name = "dog";
}
2、新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类
@Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();
}
}
3、测试
@Test
public void test2(){
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);
}
04_静态/动态代理模式
4.1 静态代理模式理解
- 意义:
让角色专注于自己的事情,本质上是一种分工合作,降低代码耦合性
- 角色分析(以租房为例):
- 抽象角色:一般会使用接口或者抽象类来解决,例如租房这一行为
- 真实角色:被代理的角色,如房东
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作(签合同,收钱),如中介
- 客户:访问代理对象的人,如要租房的人
除了租房,日志记录也是很经典的例子:
- 日志记录:比如某个接口(抽象角色)的实现类(真实角色)是 “用户登录”,代理角色可以在 “登录” 前后自动打印 “请求参数日志”“响应结果日志”,真实角色无需关注日志逻辑;
- 静态代理模式的好处:
-
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
-
公共事务交给代理角色,实现了业务的分工
-
公共业务发生扩展的时候,方便集中管理
-
本质是遵循了开闭原则(对扩展开放、对修改关闭)和单一职责原则
- 缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
4.2 静态代理代码实现
Rent类,即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host类,即真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy类,即代理角色
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client类,即客户
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//你去找中介
proxy.rent();
}
}
4.3 动态代理
Proxy(代理)和 InvocationHandler(调用处理程序)是两个核心组件,它们共同实现了动态代理功能
4.3.1 InvocationHandler(调用处理程序)
InvocationHandler 是一个接口,它定义了代理对象方法被调用时的处理逻辑。当我们通过代理对象调用任何方法时,这个调用都会被转发到 InvocationHandler 的 invoke 方法进行处理。
核心方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
proxy:生成的代理对象本身。method:被调用的目标方法(Method对象)。args:调用方法时传入的参数数组。- 返回值:方法调用的返回结果(需与目标方法的返回类型匹配)。
作用:
在 invoke 方法中,我们可以自定义处理逻辑,例如:
- 调用目标方法前的预处理(如日志记录)。
- 调用目标方法(通过
method.invoke(target, args))。 - 调用目标方法后的后续处理(如结果缓存)。
4.3.2 Proxy(代理类)
Proxy 是 Java 提供的一个工具类,用于在运行时动态生成接口的代理实例。它不能直接实例化,而是通过静态方法 newProxyInstance 创建代理对象。
核心方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException;
loader:类加载器(通常使用目标对象的类加载器)。interfaces:代理对象需要实现的接口数组(必须是目标对象实现的接口)。h:关联的InvocationHandler实例(代理方法调用的处理器)。- 返回值:动态生成的代理对象(实现了
interfaces中的所有接口)。
作用:
- 动态生成代理类的字节码并加载到 JVM 中。
- 确保代理对象实现指定的接口,从而可以替代目标对象进行调用。
- 将代理对象的所有方法调用转发给关联的
InvocationHandler处理。
4.3.3 实例代码
- 定义一个接口
interface UserService {
void login(String username);
}
- 接口实现类(目标对象)
class UserServiceImpl implements UserService {
@Override
public void login(String username) {
System.out.println(username + " 登录成功");
}
}
- 自定义 InvocationHandler
class ProxyInvocationHandler implements InvocationHandler {
private Object target; // 目标对象(被代理的对象)
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用前:记录日志
System.out.println("方法 " + method.getName() + " 开始执行,参数:" + args[0]);
// 调用目标方法
Object result = method.invoke(target, args);
// 调用后:记录日志
System.out.println("方法 " + method.getName() + " 执行结束");
return result;
}
}
- demo
public class ProxyDemo {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建 InvocationHandler(关联目标对象)
InvocationHandler pih = new ProxyInvocationHandler(target);
// 生成代理对象(通过 Proxy 动态创建)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 实现的接口
handler // 调用处理器
);
// 通过代理对象调用方法
proxy.login("pp");
}
}
小结:
InvocationHandler负责定义代理逻辑(如何处理方法调用)。Proxy负责生成代理对象,并将方法调用转发给InvocationHandler。- 两者结合实现了动态代理,常用于 AOP(面向切面编程)、日志记录、事务管理等场景,无需手动编写代理类,提高了代码的灵活性和可维护性。
- 动态代理想要理清楚原理会比较困难,最好阅读多一点文章/视频
05_AOP实现方式
5.1 AOP理解
5.1.1 什么是AOP

在原本一个纵向开发的整个业务中,某一段业务里进行横向拓展开发,就是aop,其作用:
- 保持核心业务逻辑(纵向流程)的纯净性
- 将那些横向的、跨多个业务的功能抽取出来,形成独立的 "切面"
- 通过配置的方式,让这些切面在合适的时机(如方法执行前 / 后)自动介入业务流程
5.1.2 Aop在Spring中的作用
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
- 前置通知:在方法执行之前触发,实现接口是
org.springframework.aop.MethodBeforeAdvice,可用于在方法执行前做一些准备工作,如权限校验等。 - 后置通知:在方法正常执行完成之后触发,实现接口是
org.springframework.aop.AfterReturningAdvice,能用于方法执行后的一些处理,像记录方法执行结果等。 - 环绕通知:围绕方法的前后进行触发,实现接口是
org.aopalliance.intercept.MethodInterceptor,可以在方法执行前后添加自定义逻辑,甚至控制方法是否执行。 - 异常抛出通知:在方法抛出异常时触发,实现接口是
org.springframework.aop.ThrowsAdvice,主要用于处理方法执行过程中出现的异常情况,如异常日志记录等。 - 引介通知:用于在类中增加新的方法或属性,实现接口是
org.springframework.aop.IntroductionInterceptor,能对现有类进行增强,添加新的功能。
5.2 使用Spring实现Aop
使用AOP需要先导入一个依赖包:
<!--https://mvnrepository.com/artifact/org.aspectj/aspectjweaver--> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Spring 中实现 AOP 的三种方式,包括使用原生 Spring API 接口、自定义类和注解方式
5.2.1 使用原生 Spring API 接口
- 需要实现特定的接口(如 MethodBeforeAdvice、AfterReturningAdvice)
- 通过 XML 配置将通知与切入点关联
- 适合需要利用 Spring 提供的标准通知类型的场景
举例:
增加前置日志:
public class Log implements MethodBeforeAdvice{
public void before(Method method, Object[] objects, Object target) throws Throwable {}
}
增加后置日志:
public class AfterLog implements AfterReturningAdvice{
//returnValue;返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {}
}
之后把这些类注册到Spring中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.pp.service.UserServiceImpl"/>
<bean id="log" class="com.pp.log.Log"/>
<bean id="afterLog" class="com.pp.log.AfterLog"/>
<!--方式一:使用原生SpringAPI接口-->
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点:expression:表达式,execution(要执行的位置!* * * * *)-->
<aop:pointcut id="pointcut" expression="execution(* com.pp.service.UserServiceImpl.*(..))"/>
<!--热行环绕增加!-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
5.2.2 自定义类实现 AOP
- 不需要实现特定接口,更加灵活
- 在 XML 配置中定义切面、切入点和通知
- 适合简单的 AOP 需求,代码侵入性低
举例:
写一个自定义的切入类
public class DiyPointcut {
public void before(){
System.out.println("方法前");
}
public void after(){
System.out.println("方法后");
}
}
配置xml
<bean id="diy" class="com.pp.diy.DiyPointcut"/>
<aop:config>
<!--自定义切面,ref要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(*com.pp.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
5.2.3 注解方式实现 AOP
- 使用 @Aspect、@Before、@After、@Around 等注解
- 需要在配置文件中添加
aop:aspectj-autoproxy/开启支持 - 代码简洁,配置量少,是目前最常用的方式
步骤
在自定义的类前加 @Aspect
在方法前加如@Before("execution(*com.pp.service.UserServiceImpl.*(..))")
自定义类:
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.pp.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前");
}
@After("execution(* com.pp.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行后");
}
@Around("execution(* com.pp.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+ jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
之后在配置文件中写入bean,以让Test类扫描:
<!--开启注解支持!-->
<aop:aspectj-autoproxy/>
<bean id="annotationPointCut" class="com.pp.diy.AnnotationPointCut"/>
执行顺序:
环绕前 -> before -> 执行 -> 环绕后 -> after
06_整合Mybatis
6.1 了解MyBatis-Spring
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中
依赖:
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!--aspectJ AOP 织入器-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--mybatis-spring整合包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
<!--配置Maven静态资源过滤问题-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
6.2 整合实现一
在 MyBatis-Spring 中
- dataSource使用Spring创建,替换mybaits的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/mybatis01?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
- 使用SqlSessionFactoryBean来创建 SqlSessionFactory,关联MyBatis
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/pp/dao/*.xml"/>
</bean>
- 注册sqlSessionTemplate,关联sqlSessionFactory
<!-- sqlSessionTemplate(等同于mybatis的sqlSession) -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 仅能使用构造器注入,因为其没有set方法 -->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
- 增加Dao接口的实现类;私有化sqlSessionTemplate
public class UserDaoImpl implements UserMapper {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
- 注册bean实现
<bean id="userDao" class="com.pp.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
- 测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = (UserMapper) context.getBean("userDao");
List<User> user = mapper.selectUser();
System.out.println(user);
}
6.3 整合实现二
dao继承SqlSessionDaoSupport类 , 直接利用 getSqlSession() 获得SqlSessionTemplate , 然后直接注入SqlSessionFactory . 比起实现一 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好
相较于实现一,其实就是删去第3步,修改第4步的UserDaoImpl类(继承了SqlSessionDaoSupport,可以直接利用 getSqlSession()获取SqlSessionTemplate ):
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
return getSqlSession().getMapper(UserMapper.class)r.selectUser();
}
}
修改第五步bean的配置:
<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
- 测试
07_声明式事务
7.1 事务回顾
7.1.1 事务的特点
- 把一组业务当成一个总的业务来做,要么都成功要么都失败
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,用来确保数据的完整性和一致性
7.1.2 事务ACID原则
- 原子性
- 一致性
- 隔离性:每个事务都应该与其他事务隔离开来,防止数据损坏
- 持久性:事务一旦完成,事务的结果被写到持久化存储器中
7.1.3 例子理解
比如一个方法里调用了两个方法,add和delete,add成功了,delete出问题没成功,但add却成功修改了数据,这种只完成了一半是不符合原则的
7.2 Spring中的事务管理
- 编程式事务管理:将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 声明式事务管理:将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
- 使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
2. 配置事务管理器
- 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的,它负责与底层数据库交互,实现事务的提交、回滚等操作。
- 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
- Spring 为不同的数据访问技术提供了对应的事务管理器:
DataSourceTransactionManager:用于 JDBC 或 MyBatis(基于 JDBC)HibernateTransactionManager:用于 HibernateJpaTransactionManager:用于 JPA
如JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置事务的通知
事务通知(Transaction Advice)用于定义哪些方法需要事务支持以及事务的具体规则(如传播特性、隔离级别、超时时间等)。
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
关键属性说明:
id="txAdvice":事务通知的唯一标识,用于后续 AOP 织入。transaction-manager="transactionManager":关联前面定义的事务管理器。<tx:attributes>:包含多个<tx:method>,每个<tx:method>对应一组方法的事务规则。
<tx:method> 常用属性:
name:指定方法名,可以使用通配符(*表示任意字符)。例如:name="add":匹配所有名为add的方法。name="search*":匹配所有以search开头的方法(如searchById、searchAll)。name="*":匹配所有方法(通常作为默认规则放在最后)。
propagation:事务传播特性(核心),定义了多个事务方法相互调用时,事务如何传递。常用值:REQUIRED(默认):如果当前没有事务,就创建一个新事务;如果当前已有事务,就加入该事务(最常用)。REQUIRES_NEW:无论当前是否有事务,都创建一个新事务(新事务独立于原事务)。SUPPORTS:如果当前有事务,就加入;如果没有,就以非事务方式执行。MANDATORY:必须在事务中执行,否则抛出异常。
read-only:是否为只读事务。read-only="true"表示该方法只查询数据,不修改数据,Spring 会进行优化(如关闭事务自动提交)。通常查询方法设置为true,增删改方法设置为false(默认)。isolation:事务隔离级别,解决并发问题(如脏读、不可重复读、幻读)。默认使用数据库的隔离级别,常用值:DEFAULT(默认):使用数据库的默认隔离级别。READ_COMMITTED:允许读取已提交的数据,避免脏读。
timeout:事务超时时间(秒),如果事务执行超过该时间,自动回滚。默认值为-1(永不超时)。rollback-for/no-rollback-for:指定哪些异常触发回滚 / 不触发回滚。默认情况下,运行时异常(RuntimeException)会回滚,受检异常(如IOException)不会回滚。
- AOP织入事务
事务通知定义了事务规则,但需要通过 AOP 织入到目标方法中才能生效。AOP 负责将事务逻辑与业务方法关联起来。
<!--配置aop织入事务-->
<aop:config>
<!-- 定义切入点:哪些类的哪些方法需要应用事务 -->
<aop:pointcut id="txPointcut" expression="execution(* com.pp.dao.*.*(..))"/>
<!-- 将事务通知织入到切入点 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
现在所有方法都配置了事务了
浙公网安备 33010602011771号