24:Spring详解 || 总有焦虑惶惶但对生活要保持热爱
Spring详解
一、Spring
1. 简介
-
Spring:春天 ---> 给软件行业带来了春天
-
2002年,首次推出了Spring框架的雏形:interface21框架!
-
Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。
-
RodJohnson,Spring Framework创始人,著名作者。很难想象他的学历,它是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
-
Spring理念:目的是解决企业应用开发的复杂性,使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
-
SSH:Struts2 + Spring + Hibernate
-
SSM:SpringMVC + Spring + Mybatis
-
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/
-
中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/overview.html
-
究极下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/
-
GitHub托管地址:https://github.com/spring-projects/spring-framework
-
Maven仓库:
搜索Spring,找到Spring Web,导入这个包,会自动导入其他的相关依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.18</version> </dependency>
2. 优点
- Spring 是一个开源的,免费的框架(容器)!
- Spring 是一个轻量级的、非入侵式的(导入Spring后并不会对原来的项目产生任何影响)框架!
- 控制反转(IOC)、面向切面编程(AOP)
- 支持事务的处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
3. 组成
Spring 的七大模块:

4. 拓展
在Spring的官网有这样一个介绍:现代化的开发,其实就是基于Spring的开发。

- Spring Boot
- 快速开发的脚手架
- 基于SpringBoot可以快速的开发单个微服务
- 约定大于配置
- Spring Cloud
- SpringCloud是基于SpringBoot实现的
因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring和SpringMVC!承上启下的作用!
Spring的弊端:发展了太久之后,违背了原来的理念,因为是一个大杂烩,所以配置十分繁琐,人称:“配置地狱!”
二、IOC理论推导和本质
1. IOC 的理论推导
以前我们实现一个项目:
- UserDao 接口
- UserDaoImpl 实现类
- UserService 业务接口
- UserServiceImpl 业务员实现类
在IDEA中进行演示:新建Maven项目,导入Spring依赖,删除src目录(方便建立子项目)
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.laxsilence</groupId>
<artifactId>spring-study</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
</dependencies>
</project>
目录结构:

新建一个子项目:new module
项目结构:

UserDao接口,用户数据相关操作:
package com.laxsilence.dao;
public interface UserDao {
void getUser();
}
UserDao实现类1,默认获取用户数据:
package com.laxsilence.dao;
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("默认获取用户的数据");
}
}
接下来需要去服务层,处理数据:
业务层接口:
package com.laxsilence.service;
public interface UserService {
void getUserService();
}
业务层接口实现类:
package com.laxsilence.service;
import com.laxsilence.dao.UserDao;
import com.laxsilence.dao.UserDaoImpl;
import com.laxsilence.dao.UserDaoMySQLImpl;
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public void getUserService() {
userDao.getUser();
}
}
然后我们去用户层测试:
import com.laxsilence.service.UserService;
import com.laxsilence.service.UserServiceImpl;
import org.junit.Test;
public class MyTest {
@Test
public void test(){
//用户实际调用的是业务层,dao层不需要接触
UserServiceImpl userService = new UserServiceImpl();
userService.getUserService();
}
}
可以发现,输出的确实是 ---> 默认获取用户的数据
这个时候,用户说,我不想默认了,我要用MySQL获取,我们就要去Dao层的UserDao接口实现MySQL的方法:
package com.laxsilence.dao;
public class UserDaoMySQLImpl implements UserDao{
@Override
public void getUser() {
System.out.println("MySQL获取用户数据");
}
}
但是这样实现了没用,因为在业务层接口的实现类中,我们new 的userDao 对象的实现方法是默认的接口实现:UserDaoImpl:
private UserDao userDao = new UserDaoImpl();
所以我们需要修改这里:
private UserDao userDao = new UserDaoMySQLImpl();
这样在用户层,调用业务层才能够实现MySQL这个实现类的方法。
这个时候用户又说,我要用Oracl的,你又要重复这个流程,好不容易改好了,用户又提出了其他需求,这还只是一个业务层的情况,假设有多个业务层呢,每次都需要去修改UserDao接口的不同实现类,这是一件很麻烦的事情,我们该如何解决呢?
利用set进行动态实现值的注入!
我们对比一下:
-
以前:
package com.laxsilence.service; import com.laxsilence.dao.UserDao; import com.laxsilence.dao.UserDaoImpl; import com.laxsilence.dao.UserDaoMySQLImpl; public class UserServiceImpl implements UserService{ private UserDao userDao = new UserDaoMySQLImpl(); @Override public void getUserService() { userDao.getUser(); } } -
动态实现:
package com.laxsilence.service; import com.laxsilence.dao.UserDao; import com.laxsilence.dao.UserDaoImpl; import com.laxsilence.dao.UserDaoMySQLImpl; public class UserServiceImpl implements UserService{ private UserDao userDao; //利用set进行动态实现值的注入! public void setUserDao(UserDao userDao){ this.userDao = userDao; } @Override public void getUserService() { userDao.getUser(); } }这个时候,我们只需要在用户层让用户自己选择使用哪种方式实现:
import com.laxsilence.dao.UserDaoMySQLImpl; import com.laxsilence.service.UserService; import com.laxsilence.service.UserServiceImpl; import org.junit.Test; public class MyTest { @Test public void test(){ //用户实际调用的是业务层,dao层不需要接触 UserService userService = new UserServiceImpl(); ((UserServiceImpl) userService).setUserDao(new UserDaoMySQLImpl());//强转是因为接口中没有写这个方法而已 //((UserServiceImpl) userService).setUserDao(new UserDaoImpl());//可以随意指定UserService接口中调用dao层的哪一个实现了 userService.getUserService(); } }
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求对代码进行增删改,如果程序代码量十分大,修改一次的成本代价十分昂贵,而之后我们使用set一个接口实现,这样已经发生了革命性的变化
之前程序是主动创建对象,控制权在程序员手里。使用set注入后,程序不再具有主动性,而是变成了被动的接收对象。
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注的在业务的实现了。这就是IOC的原型!控制反转,将自己的控制权反转到用户手里。

整体架构没有发生变化,但本质发生了变化~
2. IOC 的本质
控制反转IOC(Inversion of control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI是IOC的另外一种说法。在没有IOC的程序中,我们使用面向对象编程,对象的创建与对象之间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,控制反转就是说:获得依赖对象的方式反转了~

IOC是Spring框架的核心内容,使用多种方式完美的实现类IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的IOC容器,其实现方式是依赖注入(DI:Dependency Injection)。
三、HelloSpring
写一个HelloSpring 程序:
-
导包,我们在父项目中已经导过包了
-
新建实体类
package com.laxsilence.pojo; public class Hello { private String str; public String getStr(){ return str; } public void setStr(String str){ this.str = str; } @Override public String toString() { return "Hello{" + "str='" + str + '\'' + '}'; } } -
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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--使用Spring来创建对象,在Spring中这些都称为Bean--> <bean id="Hello" class="com.laxsilence.pojo.Hello"> <property name="str" value="Spring"/> </bean> </beans> -
测试:
提供给构造函数的一个或多个位置路径
ApplicationContext是资源字符串,允许容器从各种外部资源(例如本地文件系统、Java 等)加载配置元数据CLASSPATH。ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");import com.laxsilence.pojo.Hello; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { //获取Spring的上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext( "beans.xml"); //我们的对象现在都在Spring中管理了,我们要使用直接去里面取出来就可以了 Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.toString()); } } -
结果:

思考:
- Hello 对象是谁创建的?
- Hello 对象的属性是怎么设置的?
<!--
以前:
类型 变量名 = new 类型();
Hello hello = new Hello();
现在:
bean标签的意思: new Hello();
id 就是对象的变量名 class 就是要new 的对象的全限定名
property 相当于给对象中的属性设置一个值
-->
这个过程就叫做控制反转:
- 控制:谁来控制对象的创建,传统的应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
- 反转:程序本身不创建对象,而变为被动的接收对象
- 依赖注入:就是利用set方法来进行注入的实体类必须要有set方法
- IOC是一种编程思想,由主动的编程变成被动的接收
- 可以通过
new ClassPathXmlApplicationContext来浏览一下底层源码
到了现在,我们彻底不用再程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修,所谓的IOC,就是:对象由Spring来创建,管理,装配!
如果你的实体类在XML中进行配置了,那么实体类就会有标志:

接下来,我们改造一下推导IOC过程的那个程序:
-
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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="mysqlImpl" class="com.laxsilence.dao.UserDaoMySQLImpl"></bean> <bean id="oraclImpl" class="com.laxsilence.dao.UserDaoOracleImpl"></bean> <bean id="userServiceImpl" class="com.laxsilence.service.UserServiceImpl"> <!--ref:引用Spring容器中已经创建好的对象 value:引用八大基本数据类型--> <property name="userDao" ref="mysqlImpl"/> </bean> </beans> -
测试类
java import com.laxsilence.dao.UserDaoMySQLImpl; import com.laxsilence.service.UserService; import com.laxsilence.service.UserServiceImpl; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { //获取Applicationcontext:拿到Spring的容器 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //容器在手,天下都有 UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl"); userServiceImpl.getUserService(); } }
四、IOC容器创建对象的方式
1. 有无参构造的实体类
-
我们在实体类的无参构造里输出一句话:
package com.laxsilence.pojo; public class User { private String name; public User(){ System.out.println("User的无参构造"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name:"+name); } } -
进行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 id="user" class="com.laxsilence.pojo.User"> <property name="name" value="张三"/> </bean> </beans> -
测试:
import com.laxsilence.pojo.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { // User user = new User(); ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) context.getBean("user"); user.show(); } } -
结果:

可以发现,在getBean的时候,确实调用了无参构造,也就是说在getBean之前,对象已经创建好了!其实对象的实例化在获取上下文的时候就创建好了那我们自定义一个有参构造呢?
2. 含有参构造的实体类
-
添加有参方法
package com.laxsilence.pojo; public class User { private String name; // public User(){ // System.out.println("User的无参构造"); // } public User(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name:"+name); } } -
测试:
直接报错

回到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 id="user" class="com.laxsilence.pojo.User"> <!--有参构造 之 使用 下标赋值--> <constructor-arg index="0" value="Laxsilence"/> </bean> </beans> -
第二种方式 ---> 通过参数类型匹配:
不建议使用:因为如果出现两个同类型的容易混淆,会按照参数的顺序进行赋值。
<?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="user" class="com.laxsilence.pojo.User"> <!--有参构造 之 使用 类型赋值--> <constructor-arg type="java.lang.String" value="DearLiu"/> </bean> </beans> -
第三 种方式 ---> 通过参数名匹配:
<?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="user" class="com.laxsilence.pojo.User"> <!--有参构造 之 使用 参数名--> <constructor-arg name="name" value="Jar"/> </bean> </beans>
此外在获取上下文的时候,所有的bean都被实例化了,且每个bean在内存中只有一份实例,比如我们getBean同一个id,然后进行比较,发现是true
五、Spring配置
在xml中 <一下,后面就会出来相关的配置

可以发现,比起MyBatis的要少很多:
1. alias的配置
别名:
<alias name="user" alias="dnfhdfh"></alias>
name对应的是bean的id,alias是给这个id起的别名,别名区分大小写
如果添加了别名,使用的时候通过getBean(“别名”),也可以获取到一个对象
2. Bean的配置
<bean id="user" class="com.laxsilence.pojo.User" name="user2,u2 u3">
<constructor-arg name="name" value="Laxsilence"/>
</bean>
id:bean的唯一标识符,也就是相当于对象名
class:bean对象所对应的全限定名
name:也是别名,name可以同时取多个别名,可以通过逗号 空格 分号来分割
这里有一个高频问点:Bean的生命周期,可以自行搜索查找
3. import的配置
一般用于团队开发,可以将多个配置文件,导入合并为一个。
假设团队中有四个人写了四个xml:

但是在Test的时候,只要扫描一个总的xml:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
这个时候就可以在总的xml中进行导入,重名会报错:
<import resource="lisi.xml"/>
<import resource="wangwu.xml"/>
<import resource="wangwu.xml"/>
导入之后,其他xml中的bean,总的xml中也就可以拿到了。
使用的时候:
- 与主配置的id重名,使用主配置的id
- 多个import中配置的id重名(主配置中没有),调用最后import中配置的id,即后面的覆盖前面的
- 调用别名,哪个配置有这个别名,使用哪个配置
相同id,相同内容 ---> 进行合并;
相同id,不同内容 ---> 后来居上,进行覆盖。
六、DI(依赖注入)
DI:Dependency Injection
共有三种方式
1. 构造器注入
前面IOC容器创建对象的方式中已经学过了
2. Set方式注入【重点】
-
依赖注入:Set注入!
-
依赖
bean对象的创建依赖于容器
-
注入
bean对象中的所有属性由容器来注入
-
(1)环境搭建
-
复杂类型
结构:
Address实体类:
package com.laxsilence.pojo; public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } } -
真实测试环境
Student实体类:
package com.laxsilence.pojo; import java.util.*; public class Student { private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String,String> card; private Set<String> games; private Properties info; private String wife; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String[] getBooks() { return books; } public void setBooks(String[] books) { this.books = books; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } public Map<String, String> getCard() { return card; } public void setCard(Map<String, String> card) { this.card = card; } public Set<String> getGames() { return games; } public void setGames(Set<String> games) { this.games = games; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } public String getWife() { return wife; } public void setWife(String wife) { this.wife = wife; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address + ", books=" + Arrays.toString(books) + ", hobbies=" + hobbies + ", card=" + card + ", games=" + games + ", info=" + info + ", wife='" + wife + '\'' + '}'; } } -
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 id="student" class="com.laxsilence.pojo.Student"> <!--第一种,普通值注入,value--> <property name="name" value="Laxsilence"/> </bean> </beans> -
测试类:
import com.laxsilence.pojo.Student; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Mytest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.getName()); } }
(2)开始测试
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 id="address" class="com.laxsilence.pojo.Address">
<property name="address" value="天上宫阙"/>
</bean>
<bean id="student" class="com.laxsilence.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="Laxsilence"/>
<!--第二种,Bean注入-->
<property name="address" ref="address"/>
<!--第三种,数组注入-->
<property name="books">
<array>
<value>《红楼梦》</value>
<value>《水浒传》</value>
<value>《三国演义》</value>
<value>《西游记》</value>
</array>
</property>
<!--第四种,list注入-->
<property name="hobbies">
<list>
<value>听歌</value>
<value>代码</value>
<value>绝对领域</value>
</list>
</property>
<!--第五种,map注入-->
<property name="card">
<map>
<entry key="身份证" value="123321111111111111"/>
<entry key="银行卡" value="12341235123412341241"/>
</map>
</property>
<!--第六种,set集合注入-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!--第七种,Null值注入-->
<property name="wife">
<null/>
</property>
<!--第八种,properties-->
<property name="info">
<props>
<prop key="driver">111111</prop>
<prop key="url">男</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
测试:
import com.laxsilence.pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
/*
Student{
name='Laxsilence',
address=Address{address='天上宫阙'},
books=[《红楼梦》, 《水浒传》, 《三国演义》, 《西游记》],
hobbies=[听歌, 代码, 绝对领域],
card={身份证=123321111111111111, 银行卡=12341235123412341241},
games=[LOL, COC, BOB],
info={password=123456, url=男, driver=111111, username=root},
wife='null'
}
**/
}
}
3. 拓展方式注入
注意:P命名空间和C命名空间在配置中都需要导入约束。一个无参,一个有参。其实p就是property,c就是constructor-arg
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
P命名空间:
实体类:
package com.laxsilence.pojo;
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--P 命名空间注入:可以直接注入属性的值-->
<bean id="user" class="com.laxsilence.pojo.User" p:age="18" p:name="Laxsilence">
</bean>
</beans>
测试:
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBean.xml");
User user = context.getBean("user", User.class);//如果显示的说明 就不用强转
System.out.println(user);
}
C命名空间:
实体类:需要有参构造
package com.laxsilence.pojo;
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
配置:
<!--C 命名空间注入:通过构造器注入:construct-args-->
<bean id="user2" class="com.laxsilence.pojo.User" c:age="18" c:name="DearLiu"></bean>
测试:
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBean.xml");
//User user = context.getBean("user", User.class);//如果显示的说明 就不用强转
User user2 = context.getBean("user2", User.class);
System.out.println(user2);
}
4. Bean的作用域

-
单例模式(Spring默认机制)
每一次getBean得到的对象都是同一个,hashcode都是相同的
<bean id="user2" class="com.laxsilence.pojo.User" c:age="18" c:name="DearLiu" scope="singleton"></bean> -
原型模式
每一次getBean得到的对象不一样,hashCode不同
<bean id="user2" class="com.laxsilence.pojo.User" c:age="18" c:name="DearLiu" scope="prototype"></bean> -
其余的request、session、application、websocket只会在web开发中使用到。
七、Bean的自动装配
-
自动装配
是Spring满足Bean依赖的一种方式
-
Spring会在上下文中自动寻找并自动给Bean装配属性
在Spring中有三种装配方式:
- 在xml中显示的配置
- 在java中显示配置
- 隐式的自动装配bean【重点】
1. 测试
-
一个人有两个宠物:一只猫,一只狗
实体类:
People
package com.laxsilence.pojo; public class People { private Cat cat; private Dog dog; private String name; public Cat getCat() { return cat; } public void setCat(Cat cat) { this.cat = cat; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "People{" + "cat=" + cat + ", dog=" + dog + ", name='" + name + '\'' + '}'; } }Dog
package com.laxsilence.pojo; public class Dog { public void shout(){ System.out.println("旺旺旺"); } }Cat
package com.laxsilence.pojo; public class Cat { public void shout(){ System.out.println("喵喵喵"); } } -
配置Bean
<?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="cat" class="com.laxsilence.pojo.Cat"/> <bean id="dog" class="com.laxsilence.pojo.Dog"/> <bean id="people" class="com.laxsilence.pojo.People"> <property name="name" value="Laxsilence"/> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> </bean> </beans> -
测试
import com.laxsilence.pojo.People; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); People people = context.getBean("people", People.class); people.getCat().shout(); people.getDog().shout(); } } -
结果:

2. ByName和ByType自动装配
<?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="cat" class="com.laxsilence.pojo.Cat"/>
<bean id="dog" class="com.laxsilence.pojo.Dog"/>
<!--
byName:会自动在容器上下文查找,和自己属性set方法后面的值对应的beanID
比如 cat属性 的set方法为 setCat 就会自动寻找id为cat的bean 进行ref引用
set/get方法会自动将属性名大写,所以Spring会将setCat方法的Cat自动转换为小写cat
byName只能取到id为小写的,不能取到id为大写的
需要保证id唯一
-->
<bean id="people" class="com.laxsilence.pojo.People" autowire="byName">
<property name="name" value="Laxsilence"/>
</bean>
<!--
byType:会自动在容器上下文查找,和自己属性set方法参数的类型相同的Bean
比如cat属性的setCat方法参数的类型为Cat 就会寻找Cat类型的Bean 进行装配
但是如果有两个Cat属性的Bean 就会失败报错
必须保证类型全局唯一 甚至可以省略id进行装配
-->
<bean id="people2" class="com.laxsilence.pojo.People" autowire="byType">
<property name="name" value="DearLiu"/>
</bean>
</beans>
3. 使用注解实现自动装配
JDK1.5支持的注解,Spring从2.5开始支持注解
基于注释的配置的引入提出了这种方法是否比 XML“更好”的问题。简短的回答是“视情况而定”。
要使用注解:
-
在配置文件中导入约束
xmlns:context="http://www.springframework.org/schema/context" -
配置开启注解的支持
<context:annotation-config/><?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"> <context:annotation-config/> </beans>
(1)@Autowired
直接在属性上或者set方法上使用即可
如果加在属性上,甚至可以省略掉set方法,但是官方建议写在set上~
@Autowired
private Cat cat;
@Autowired
private Dog dog;
使用@Autowired我们可以不用编写set方法了,前提是你这个自动装配的属性在IOC是(Spring)容器中存在且符合类型.
其实这个注解的实现默认就是ByType,当ByType查找不到或当ByType的满足个数大于1的时候就会使用ByName
科普:
@Nullable 属性标记了这个注解,说明这个属性可以为null
进入@Autowired可以看到是有一个属性的:
public @interface Autowired {
boolean required() default true;
}
如果显示的定义了required属性为false,说明这个属性可以为null,可以不用再Bean中装配。一般来说 包装类默认可以是null,不需要声明,但是一些基本属性想要默认为null的时候,可以这样声明。
@Autowired(required = false)
private Cat cat;
通过组合@Autowired和@Qualifier,可以指定具体的一个bean:如果同时有两个Dog类的bean,一个id为dog111,一个为dog222,这个时候byType查不到了,因为有两个,所以就去ByName,但是发现两个bean的id也跟setDog方法的“dog”不对应,这时候就不知道要用哪一个,会报错!
然后我们就可以通过@Qualifier指定ByName使用。
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
拓展:
除了使用Spring的自动装配注解外,java的jdk也提供了一个默认的装配注解:@Resource,但是jdk11取消了这个注解,因此存在版本兼容问题。这个注解默认是按照ByName进行匹配,匹配不到用ByType进行匹配。@Resource(name=“cat2”),同样的,这个注解也可以指定匹配一个bean。
4. 小结(重点)
@Resource和@Autowired的区别:
-
都是用来自动装配的,都可以放在属性或者set方法之前
-
@Resource:
- 默认使用ByName匹配,有同名直接报错!通过Name匹配不到,再使用ByType进行匹配
- 可以指定使用name或type进行匹配给出的bean:@Resource(name=“”)或@Resource(Type=“”)
- 如果指定name,就会去查找指定的值,如果查找不到,会直接报错,会自动屏蔽掉另外一种查询方式!如果找到了,会判断type,只有type也匹配才会成功。
-
@Autowired:
-
默认使用ByType匹配,匹配不到直接报错!匹配数大于1或匹配到多个,再使用ByName进行匹配。
-
可以指定属性是否为空:@Autowired(required=false),false:可以空,true:不能为空
-
可以结合@Qualifier通过byName匹配给出的bean:
@Autowired @Qualifier(value="") -
如果同时配置了@Qualifier(value=""),就会去寻找配置的这个值,这时候找不到就直接报错,会自动屏蔽ByType匹配,如果找到了,会判断type,只有type也匹配才会成功。
-
八、使用注解开发
在Spring4之后,要使用注解开发,必须要保证AOP的包导入了

同上,要使用注解:
-
在配置文件中导入约束
xmlns:context="http://www.springframework.org/schema/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"> <!--指定要扫描的包,这个包下的注解就会生效,正常来说这个<context:annotation-config/>就可以忽略了--> <context:component-scan base-package="com.laxsilence.pojo"/> <!--注解驱动的包,可以识别spring之外的注解,如上一章中的@Resource等等--> <context:annotation-config/> </beans>
1. Bean
-
实体类:
package com.laxsilence.pojo; import org.springframework.stereotype.Component; //这个注解等价于xml中的 <bean id="user" class="com.laxsilence.pojo.User /> //@Compontent 意思为组件 使用前我们已经在xml中配置了组件扫描 //使用这个注解,放在类上,说明我们的这个类被Spring管理了 @Component public class User { public String name = "Laxsilence"; } -
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" 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"> <!--指定要扫描的包,这个包下的注解就会生效,正常来说这个<context:annotation-config/>就可以忽略了--> <context:component-scan base-package="com.laxsilence.pojo"/> <!--注解驱动的包,可以识别spring之外的注解,如上一章中的@Resource等等--> <context:annotation-config/> </beans> -
测试:
import com.laxsilence.pojo.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //注意这里我们getBean的时候,因为注解没有显式定义bean的id,默认为首字母小写的类名 如User --> user UserStory --> userStory User user = context.getBean("user", User.class); System.out.println(user.name); } }
2. 属性如何注入
package com.laxsilence.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//这个注解等价于xml中的 <bean id="user" class="com.laxsilence.pojo.User />
//@Compontent 意思为组件 使用前我们已经在xml中配置了组件扫描
//使用这个注解,放在类上,说明我们的这个类被Spring管理了
@Component
public class User {
// //相当于<property name="name" vlaue="DearLiu"/> 如果是很复杂的 建议使用xml配置
// 也可以写在set方法上面
// @Value("DearLiu")
public String name;
//相当于<property name="name" vlaue="DearLiu"/> 如果是很复杂的 建议使用xml配置
@Value("DearLiu")
public void setName(String name) {
this.name = name;
}
}
3. 衍生的注解
@Component有几个衍生的注解,我们在web开发中,会按照MVC三层架构分层!
- dao层:
@Repository等价于pojo层的@Component - service层:
@Service同样等价于pojo层的@Component - controller层:(也就是以前的servlet层)
@Controller还是等价于pojo层的@Component
这个时候需要我们把配置文件中的componment组件扫描的范围扩大:
<context:component-scan base-package="com.laxsilence"/>
这四个注解的功能都一样,是等价的,都是代表将某个类注册到Spring容器中,装配Bean,会被componment扫描成为一个组件。
4. 自动装配
上一章已经讲过
5. 作用域
@Scope("singleton")//单例
@Scope("prototype")//原型
6. 小结
xml和注解:
- xml更加万能,适用于任何场合!维护简单方便
- 注解:无法引用别的类,维护相对复杂
最佳实践:
- xml用来管理bean
- 注解用来完成属性的注入
- 我们在使用的过程中需要注意必须要让注解生效
九、使用Java的方式配置Spring
我们现在要完全不适用Spring的xml配置,全权交给Java来做
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能!
配置类(主)
package com.laxsilence.config;
import com.laxsilence.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//相当于以前的配置文件
//<beans> </beans>
@Configuration//这个也会被Spring容器托管,注册到容器中,因为它本来就是一个Component,它代表这是一个配置类,就是beans.xml
@ComponentScan("com.laxsilence.pojo")//相当于扫描包
@Import(MyConfig2.class)//相当于xml中引入其他xml
public class MyConfig {
//注册一个bean 就相当于我们之前写的<bean></bean>标签
//id 标签 就是这个方法的名字
//class 标签 就是这个方法的返回值类型
@Bean
public User getUser(){
return new User(); //返回要注入到Bean的对象
}
}
配置类2
package com.laxsilence.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig2 {
}
实体类:
package com.laxsilence.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//说明这个类被Spring接管了,被注册到容器中了 注意:这个注解跟MyConfig中的注解@Bean作用是重复的 在配置类通过Bean注入后 就不需要这个标签再注入了 是重复的
@Component
public class User {
private String name;
public User(){
System.out.println("无参构造实例化!");
}
public String getName() {
return name;
}
@Value("Laxsilence")//属性注入值
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
测试:
import com.laxsilence.config.MyConfig;
import com.laxsilence.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
public static void main(String[] args) {
//如果完全使用配置类去做,而不用xml 我们就需要通过AnnotationConfigApplicationContext 上下文来获取容器,通过配置类.class获取
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user", User.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(user==getUser);
System.out.println(getUser.getName());
System.out.println(user.getName());
}
}
结果:

通过结果我们可以发现:
实体类的注解@Component和配置类的注解 @Bean作用是重复的,都是将某一个类注册到Spring容器,如果都使用了,那么经过我们的测试,取出来可以发现,这个类会实例化两次,而且两次实例化的对象是不一样的!所以我们只需要选择其中一种就可以!
这种纯Java的配置方式,在SpringBoot中随处可见!
十、代理模式
我们有在多线程中提到过代理模式,这里我们再来说一说代理模式!
为什么要学习代理模式?因为这是SpringAOP的底层!【SpringAOP 和 SpringMVC 面试重点】
代理模式的分类:
- 静态代理
- 动态代理

1. 静态代理
角色分析:
- 抽象角色:一般会使用接口或抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,会做一些附属操作
- 客户:访问代理对象的人
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注真实角色的公共业务
- 公共业务交给代理角色,实现了业务的分工(不需要给每一个房东都加相同的功能,这些相同的功能都提取到代理角色)
- 业务发生扩展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
- 解决缺点就用到了动态代理
测试:
-
接口
package com.laxsilence.Demo01; //租房的接口 public interface Rent { public void rent(); } -
真实角色
package com.laxsilence.Demo01; public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子"); } } -
代理角色
package com.laxsilence.Demo01; public class Proxy implements Rent{ private Host host; public Proxy(){ } public Proxy(Host host){ this.host = host; } @Override public void rent() { seeHouse(); host.rent(); heTong(); fee(); } //看房 public void seeHouse(){ System.out.println("中介带你看房"); } //收中介费 public void fee(){ System.out.println("收中介费"); } //签合同 public void heTong(){ System.out.println("签合同"); } } -
客户端访问
package com.laxsilence.Demo01; public class Client { public static void main(String[] args) { //房东要租房子 Host host = new Host(); //代理,中介帮房东租房子,但是代理角色一般会有一些附属操作 Proxy proxy = new Proxy(host); //你不用面对房东 直接找中介租房即可 proxy.rent(); } }
2. 加深理解
代码:
-
接口:
package com.laxsilence.Demo02; public interface UserService { public void add(); public void delete(); public void update(); public void query(); } -
真实角色:
package com.laxsilence.Demo02; public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void query() { System.out.println("查询了一个用户"); } //改动原有的业务代码,在公司中是大忌 } -
代理角色
package com.laxsilence.Demo02; public class UserServiceProxy implements UserService{ private UserServiceImpl userService; public void setUserService(UserServiceImpl userService){ this.userService = userService; } @Override public void add() { log("add"); userService.add(); } @Override public void delete() { log("delete"); userService.delete(); } @Override public void update() { log("update"); userService.update(); } @Override public void query() { log("query"); userService.query(); } public void log(String msg){ System.out.println("使用了"+msg+"方法!"); } } -
客户端:
package com.laxsilence.Demo02; public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(); userServiceProxy.setUserService(userService); userServiceProxy.add(); } }
聊聊AOP:

3. 动态代理
-
动态代理和静态代理的角色一样
-
动态代理的代理类是动态生成的不是我们直接写好的
-
动态代理也分为两大类:基于接口的动态代理、基于类的动态代理
-
基于接口的动态代理【我们在这里使用这种方式】
JDK的动态代理
-
基于类的动态代理、
cglib
-
Java字节码实现:javasist
-
需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)
-
InvocationHandler:
一个接口,java.lang.reflect,反射包下
InvocationHandler是由代理实例的调用处理程序实现的接口,每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。
这个接口只有一个方法:Object invoke(Object proxy , 方法 method , Object[] args) throws Throwable
-
Proxy:
一个类,java.lang.reflect,反射包下
Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。也就说说可以通过类来调用方法。
代码实现:
package com.laxsilence.Demo04;
import com.laxsilence.Demo03.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等会我们会用这个类自动生成代理类
public class ProxyInvocationhandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object object){
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是通过反射机制实现
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
package com.laxsilence.Demo04;
import com.laxsilence.Demo02.UserService;
import com.laxsilence.Demo02.UserServiceImpl;
public class Clent {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色不存在
ProxyInvocationhandler pih = new ProxyInvocationhandler();
pih.setTarget(userService);//设置要代理的对象
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}
}
动态代理的优点:
- 可以使真实角色的操作更加纯粹,不用去关注真实角色的公共业务
- 公共业务交给代理角色,实现了业务的分工(不需要给每一个房东都加相同的功能,这些相同的功能都提取到代理角色)
- 业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现类同一个接口即可。
十一、AOP
1. 什么是AOP
AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是0OP的延续,是软件开发中一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2. AOP在Spring中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能,即,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等等
- 切面(Aspect):横切关注点 被模块化的特殊对象,即,它是一个类
- 通知(Advice):切面必须要完成的工作,即,它是类中的一个方法
- 目标(Target):被通知对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的“地点”的定义
- 连接点(JoinPoint):与切入点匹配的执行点

在SpringAOP中:通过Advice定义横切逻辑,Spring支持五种类型的Advice:

即AOP在不改变原有代码的情况下,去增加新的功能。
3. 使用Spring实现AOP
【重点】使用AOP织入,需要导入一个依赖包!
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
(1)方式一:使用Spring的API接口
接口:
package com.laxsilence.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
实现类:
package com.laxsilence.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("更新了一个用户");
}
@Override
public void select() {
System.out.println("查询了一个用户");
}
}
新加日志功能:
package com.laxsilence.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//object:参数
//target:目标
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了!");
}
}
package com.laxsilence.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue:返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.laxsilence.service.UserServiceImpl"/>
<bean id="log" class="com.laxsilence.log.Log"/>
<bean id="afterLog" class="com.laxsilence.log.AfterLog"/>
<!--方式一:使用原生的Spring API接口-->
<!--配置AOP:需要导入aop的约束-->
<aop:config>
<!--切入点 expression:表达式,execution(要执行的位置! 修饰词 返回值 类名 方法名 参数) .. 代表有任意的参数-->
<aop:pointcut id="pointcut" expression="execution(* com.laxsilence.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试:
import com.laxsilence.service.UserService;
import com.laxsilence.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
(2)方式二:自定义类实现实现AOP
自定义插入类:
package com.laxsilence.diy;
public class DiyPointCut {
public void before(){
System.out.println("================方法执行前:===========");
}
public void after(){
System.out.println("================方法执行后:===========");
}
}
配置文件:
<?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.laxsilence.service.UserServiceImpl"/>
<!--方式二:自定义类-->
<!--比第一类简单,但是功能没有第一种强大-->
<bean id="diy" class="com.laxsilence.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面 ref:要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.laxsilence.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
(3)方式三:使用注解实现AOP
package com.laxsilence.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class Annotation {
@Before("execution(* com.laxsilence.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("================方法执行前============");
}
@After("execution(* com.laxsilence.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("================方法执行后============");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.laxsilence.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前:");
Signature signature = jp.getSignature();//获得签名
System.out.println("signature"+signature);
Object proceed = jp.proceed();
System.out.println("环绕后:");
}
}
<?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.laxsilence.service.UserServiceImpl"/>
<!--方式三:使用注解-->
<!--注册bean-->
<bean id="annotation" class="com.laxsilence.diy.Annotation"/>
<!--开启注解支持 JDK(默认实现) CGLib-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
<!--proxy-target-class设置为false为JDK实现,true 是CGLib实现-->
</beans>
十二、整合MyBatis
步骤:
-
导入相关jar包
- junit
- mybatis
- mysql
- spring相关
- aop织入
- mybatis-spring【new】
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-study</artifactId> <groupId>com.laxsilence</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-10-mybatis</artifactId> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.18</version> </dependency> <!--Spring 操作数据库,需要spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!--Mybatis 和 Spring 整合--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project> -
编写配置文件
-
测试
1. 回忆Mybatis
数据库依然使用mybatis_study数据库
-
编写实体类、工具类
实体类:
package com.laxsilence.pojo; import lombok.Data; @Data public class User { private int id; private String name; private String pwd; }工具类:
package com.laxsilence.utils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); } } -
编写核心配置文件
<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--configuration 核心配置文件--> <configuration> <properties resource="db.properties"></properties> <settings> <!--开启驼峰命名--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <typeAlias type="com.laxsilence.pojo.User" alias="user"/> </typeAliases > <!--设置默认环境--> <environments default="development"> <!--可以有多个环境--> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper class="com.laxsilence.mapper.UserMapper"></mapper> </mappers> </configuration> -
编写接口
package com.laxsilence.mapper; import com.laxsilence.pojo.User; import java.util.List; public interface UserMapper { public List<User> selectUser(); } -
编写Mapper.xml
<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.laxsilence.mapper.UserMapper"> <select id="selectUser" resultType="user"> select * from mybatis_study.user </select> </mapper> -
测试
import com.laxsilence.mapper.UserMapper; import com.laxsilence.pojo.User; import com.laxsilence.utils.MyBatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class MyTest { @Test public void test(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser(); for (User user : userList) { System.out.println(user); } } } -
结果:

2. MyBatis-Spring 整合方式一
官网:http://mybatis.org/spring/zh/index.html
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
要使用 MyBatis-Spring 模块,只需要在类路径下包含 mybatis-spring-2.0.6.jar 文件和相关依赖即可。
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
-
编写数据源配置(基本替代了原来的mybatis配置文件)
spring-dao.xml:
<?xml version="1.0" encoding="UTF8"?> <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"> <!--DataSource;使用Spring的数据源替换MyBatis的配置 c3p0 dbcp--> <!--我们这里使用SPring提供的JDBC,可以将Mybatis配置文件中的<environment>部分去掉了--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_study?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/> <property name="username" value="root"/> <property name="password" value="19142001lj"/> </bean> <!--sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!--绑定mybatis配置文件,(可选)--> <!--其实完全可以用这个代替mybatis的配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/laxsilence/mapper/*.xml"/> </bean> <!--SqlSessionTemplate:就是我们要使用的sqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能使用构造器注入方法注入,因为它没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> </beans> -
给接口加实现类(可选)
package com.laxsilence.mapper; import com.laxsilence.pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.SqlSessionTemplate; import java.util.List; public class UserMapperImpl implements UserMapper{ //我们的所有操作,在原来都使用SqlSession来执行,现在都使用SqlSessionTemplate; private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } } -
注册实现类到容器中,新建一个Spring配置,专门用来处理除Mybatis配置外的配置(可选)
applicationContext.xml:
<?xml version="1.0" encoding="UTF8"?> <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"> <import resource="spring-dao.xml"/> <bean id="userMapper" class="com.laxsilence.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean> </beans> -
测试
第一种,没有实现类:
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); SqlSession sqlSession = context.getBean("sqlSession", SqlSession.class); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser(); for (User user : userList) { System.out.println(user); } }第二种:有实现类:
@Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); List<User> userList = userMapper.selectUser(); for (User user : userList) { System.out.println(user); } } -
结果:

3. MyBatis-Spring 整合方式二
方式一中我们需要再xml中注册以下内容:
<!--SqlSessionTemplate:就是我们要使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入方法注入,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
其他不变,我们重新写一个实现类:
package com.laxsilence.mapper;
import com.laxsilence.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}
点进继承类,可以发现,这个类不过是把方式一的内容又封装了一下,不过因此,我们在使用的过程中,都不需要再xml中注册sqlSession了:

我们将这个实现类注册到容器中,只需要将spring-dao.xml中配置的sqlSessionFactory注入即可:
<bean id="userMapper2" class="com.laxsilence.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
使用方式二,相当于将中间的一步省略掉了,但其实是被SqlSessionDaoSupport封装起来了。
十三、声明式事务
1. 回顾事务
-
把一组业务当成一个业务来做:要么都成功,要么都失败!
-
事务在项目开发中,十分重要,涉及到数据的一致性问题。
-
确保完整性和一致性。
-
事务的ACID原则!
原子性、一致性、隔离性、持久性。
2. Spring 中的事务管理
一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。
一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。
事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。
在Spring中的事务管理,分为以下两类:
- 声明式事务:AOP
- 编程式事务:在代码中进行事务的管理
我们主要学习声明式事务:因为它在不改变原有代码的基础上,使用AOP的方法完成了事务的声明。
第一步:我们需要在Spring的xml配置文件中,配置上面的内容就可以完成事务的声明。
<!--结合AOP实现事务的织入-->
<!--配置事务通知-->
<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="query" read-only="true"/>-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.laxsilence.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
注意,面试常问:链接:Spring 事物的四种隔离级别和七大传播特性,事务的四种隔离级别,以及脏读幻读等等都可以参考MySQL里关于事务的内容!!!
第二步:接口:
package com.laxsilence.mapper;
import com.laxsilence.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
List<User> selectUser();
//添加一个用户
int addUser(User user);
//删除一个用户
int deleteById(@Param("id") int id);
}
第三步:mapper.xml:
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.laxsilence.mapper.UserMapper">
<select id="selectUser" resultType="user">
select * from mybatis_study.user;
</select>
<insert id="addUser" parameterType="user">
insert into mybatis_study.user(id, name, pwd) values (#{id},#{name},#{pwd});
</insert>
<delete id="deleteById">
<!--故意写错-->
deletes from mybatis_study.user where id=#{id};
</delete>
</mapper>
注意:这里我们故意写错了一条语句,用来测试事务
deletes from mybatis_study.user where id=#{id};
第四步:实现类:
package com.laxsilence.mapper;
import com.laxsilence.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
User user = new User(5, "小雪", "000");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteById(4);
return mapper.selectUser();
}
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteById(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteById(id);
}
}
可以看出在实现类中,我们先插入一个新用户5号,然后再删除一个老用户4号。因为我们的删除语句是错误的,可以想象,当我们执行的时候,程序会报错,此时:如果我们没有开启事务,那么插入新用户5号会执行成功,如果我们开启了事务,那么插入和删除都不会成功,接下来我们测试一下:
第五步:测试类:
import com.laxsilence.mapper.UserMapper;
import com.laxsilence.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> userList = userMapper.selectUser();
for (User user : userList) {
System.out.println(user);
}
}
}
第六步:结果:
这是表初始的样子:

-
我们关闭事务,即把上面Spring配置文件中与事务相关的内容都注释掉,执行程序:

可以发现,当事务关闭的时候,确实插入成功了,而且4号用户没有删除。
-
我们开启事务,将注释的内容复原,顺便将表中的5号删除,执行程序:

不管怎么刷新,表都没有发生变化,说明事务开启后,确实让两个语句都一起失败了,那我们测测成功的语句,将我们故意修改错误的语句,修改正确。开启事务,重新执行程序:

可以发现4号用户确实没了,而且5号用户成功的插入了进来!
综上所述:
事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题。
另外,如果我们不在Spring中配置声明式事务的话,就需要在代码中进行编程式事务的配置。

浙公网安备 33010602011771号