一、简介
1.1 基本介绍
- 目的:解决企业应用开发的复杂性
- 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
- 范围:任何Java应用
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架
-
框架
SSH:Struct2 + Spring + Hibernate
SSM:SpringMVC + Spring + Mybatis
1.2 所需要的包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.15</version>
</dependency>
1.3 优点
- Spring是一个开源免费的框架
- Spring是一个轻量级、非入侵的框架
- 控制反转(IOC)、面向切面编程(AOP)
- 支持事务处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)、面向切面编程(AOP)的框架
1.4 Spring组成
1.5 Spring全家桶关系
-
SpringBoot(需要学习Spring、SpringMVC)
- 一个快速开发脚手架
- 基于SpringBoot可以快速开发单个微服务
-
SpringCloud(需要学习SpringBoot)
- 基于SpringBoot实现
- 协调各个微服务
二、IOC
2.1 理论推导
原来实现步骤:
UserDao
接口UserDaoImpl
实现类UserService
业务接口UserServiceImpl
业务实现类
- 问题:
UserDaoImpl
是UserDao
的实现类,如果现在又来了一个实现类UserDaoMysqlImpl
,需要修改业务层实现类中组合的代码:new UserDaoMysqlImpl
,如果又来一个UserDaoOracleImpl
,那业务层又要改。
程序无法适应用户的变更!!
在之前的代码中,用户的需求可能会影响我们原来的代码,我们需要根据用户需求修改源代码,如果程序代码量十分大,代价会很昂贵。
引出控制反转(IOC)
- 修改
我们在业务层实现层中不直接写明具体要new的类,而是设置一个set
方法,在调用的时候,让用户来决定具体使用哪一个实现类,这样我们不用修改业务层的代码
我们使用一个set接口实现,已经发生了革命性的变化
- 之前程序主动创建对象,控制权在程序员手上
- 使用set注入后,程序不在具有主动性,而是被动的接收对象,控制权在用户手上
- 这就是控制反转的思想,从本质上解决了问题,程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上。这就是IOC的原型
2.2 IOC本质
- 控制反转(Inversion of Control, IoC)是一种设计思想
- 依赖注入(DI)是实现IoC的一种方法
没有IoC的程序中,使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。控制反转后将对象的创建转移给第三方,所谓控制反转就是:获取依赖对象的方式反转了
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解。
Spring容器在初始化时,先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IoC容器中取出需要的对象。
- 采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解方式可以把二者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到零配置的目的
总结:控制反转是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection, DI)
2.3 第一个Spring程序
- 实体类
@Data
public class User {
String name;
public User() {
System.out.println("User的无参构造被调用!!");
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
'}';
}
}
- 通过
Beans.xml
配置容器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中成为Bean
类型 变量名 = new 类型();
com.nick.User user = new com.nick.User();
bean.id = new bean.class();
-->
<bean id="user" class="com.nick.pojo.Users">
<property name="name" value="nickwang"/>
</bean>
</beans>
- 实例化容器
// 获取Spring的上下文对象,现在对象都在Spring中管理,使用时直接取
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
System.out.println(user.toString());
用XML获取上下文对象的方法:
ClassPathXmlApplicationContext
ApplicationContext
是一个接口,下面有很多的实现类,对应不同的容器实例化方法,如图所示,ClassPathXmlApplicationContext
是通过配置类实例化的实现类,而AnnotationConfigApplicationContext
是通过注解实例化的实现类
解释:
User
对象是谁创建的?(Spring创建)User
对象的属性是怎么设置的?(是由Spring容器设置的)
这个过程就是控制反转:
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身创建的;使用Spring之后对象是由Spring创建的
- 反转:程序本身不创建对象,而变成被动接收对象
- 依赖注入:就是利用set方法来进行注入(实体类中必须要有set方法,才能被Spring托管)
到现在为止,我们彻底不用在程序中改动了,要实现不同的操作,只需要在xml中修改配置,所谓IoC,就是对象由Spring来创建、管理、装配
2.4 IOC创建对象的方式
- ==使用无参构造创建对象==(默认)
<bean id = "user" class = "com.nick.User">
<property name="name" value="nickwang"/>
</bean>
- 使用有参构造创建对象(构造器注入)
- 下标赋值
<bean id = "user" class = "com.nick.User">
<constructor-arg index="0" value="wangwk-a"/>
</bean>
- 类型(不建议使用,同类型不唯一不能使用)
<bean id = "user" class = "com.nick.User">
<constructor-arg type="java.lang.String" value="wangwk-a"/>
</bean>
- ==直接通过参数名注入==
<bean id = "user" class = "com.nick.User">
<constructor-arg name="name" value="wangwk-a"/>
</bean>
==总结:在配置文件加载的时候,容器中管理的bean都会被实例化,即使不使用,也会调用其构造函数==
三、Spring配置
官方推荐的配置文件名称:
applicationContext.xml
3.1 别名
<alias name="user" alias="userNickWang"/>
name
:为已经被配置过的bean的idalias
:该bean的别名
3.2 Bean配置
bean
id
:bean的唯一标识符class
:bean对象所对应的全限定名(类型)name
:别名(可以同时取多个别名,用逗号或者空格分隔,比<alias>
更实用)scope
:设置bean的限定域session
singleton
:单例模式(默认)request
3.3 导入
import
:一般用于团队开发,可将多个配置文件,导入合并为一个
四、依赖注入DI
4.1 构造器注入
Constructor-based Dependency Injection
<bean id = "user" class = "com.nick.User">
<constructor-arg name="name" value="wangwk-a"/>
</bean>
4.2 Setter注入*
Setter-based Dependency Injection
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性由容器来注入
- 实体类
- Student
@Data
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String> cards;
private Set<String> games;
private Properties info;
private String wife;
}
- Address
@Data
public class Address {
private String address;
}
- 值注入
- 普通值(使用
value
)
<bean id="student" class="com.nick.pojo.Student">
<property name="name" value="王文堃"/>
</bean>
- 对象(使用
ref
引用一个bean
)
<bean id="address" class="com.nick.pojo.Address">
<property name="address" value="陕西省西安市未央区凤城十二路"/>
</bean>
<bean id="student" class="com.nick.pojo.Student">
<property name="name" value="王文堃"/>
<property name="address" ref="address"/>
</bean>
- 数组(使用非自闭合
property
并使用**<array>
**)
<bean id="student" class="com.nick.pojo.Student">
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</array>
</property>
</bean>
- 队列List(使用非自闭合
property
并使用**<list>
**)
与
<array>
相同
<bean id="student" class="com.nick.pojo.Student">
<property name="hobbies">
<list>
<value>篮球</value>
<value>代码</value>
<value>电影</value>
</list>
</property>
</bean>
- 映射Map(使用非自闭合
property
并使用**<map>
**)
与
<array>
不同之处是map要结合**<entry>
**使用
<bean id="student" class="com.nick.pojo.Student">
<property name="cards">
<map>
<entry key="身份证" value="610XXX19991122XXXX"/>
<entry key="银行卡" value="6217XXXX2104XXXX340"/>
</map>
</property>
</bean>
- 集合Set(直接使用
<set>
)
<bean id="student" class="com.nick.pojo.Student">
<property name="games">
<set>
<value>英雄联盟</value>
<value>三国杀</value>
</set>
</property>
</bean>
- Null(使用**
<null/>
**)
<bean id="student" class="com.nick.pojo.Student">
<!--如果要注入空串则直接使用value
<property name="wife" value=""/>
-->
<property name="wife">
<null/>
</property>
</bean>
- Properties(使用
<prop>
)
prop
与map
不同之处是key
写在标签内,value
在标签外
<bean id="student" class="com.nick.pojo.Student">
<property name="info">
<props>
<prop key="username">NickWang</prop>
<prop key="password">Wang2994</prop>
</props>
</property>
</bean>
结果:
Student{
name='王文堃',
address=Address(address=陕西省西安市未央区凤城十二路),
books=[红楼梦, 西游记, 三国演义, 水浒传],
hobbies=[篮球, 代码, 电影],
cards={身份证=610XXX19991122XXXX, 银行卡=6217XXXX2104XXXX340},
games=[英雄联盟, 三国杀],
info={password=Wang2994, username=NickWang},
wife='null'
}
4.3 拓展方式注入
- p命名空间注入(属性注入)
- 命名空间:
xmlns:p="http://www.springframework.org/schema/p"
- 功能:可以直接注入**属性(property)**的值
- 使用:
p:
可以自动识别bean中的属性
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.nick.pojo.Address" p:address="文艺路"/>
</beans>
- c命名空间注入(构造器注入)
- 命名空间:
xmlns:c="http://www.springframework.org/schema/c"
- 功能:可以通过构造器注入值
- 使用:
c:
可以通过构造器注入
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--需要在Address中添加有参构造器-->
<bean id="address" class="com.nick.pojo.Address" c:address="文艺路"/>
</beans>
4.4 Bean的作用域
Scope | Description |
---|---|
singleton | (Default)单例, |
prototype | 非单例, |
request | WebMVC使用,作用域在一次请求中 |
session | WebMVC使用,在session中 |
application | WebMVC使用,application中 |
websocket | WebMVC使用,websocket中 |
- 单例
singleton
<bean id="address" class="com.nick.pojo.Address" scope="singleton">
<property name="address" value="陕西省西安市未央区凤城十二路"/>
</bean>
- 原型
prototype
每次从容器中get的时候,都会产生一个新的对象
五、自动装配Bean
5.1 什么是自动装配
Spring满足Bean依赖的一种方式,Spring会在上下文中自动寻找并给bean装配属性。在Spring中有三种装配bean的方式:
- 在xml中配置bean
- 在java中显示配置(JavaConfig)
- Java隐式自动装配*
- ByName自动装配
- ByType自动装配
5.2 使用自动装配
5.2.1 手动装配环境搭建
@Autowired
- 实体类
- Dog
@Data
public class Dog {
public void shout() {
System.out.println("汪~~");
}
}
- Cat
@Data
public class Cat {
public void shout() {
System.out.println("喵~~");
}
}
- People
@Data
public class People {
private Cat cat;
private Dog dog;
private String name;
public People() {
}
public People(@Nullable String name) {
this.name = name;
}
}
- 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People">
<property name="name" value="wangwk-a"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
- 测试类
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people", People.class);
people.getDog().shout();
people.getCat().shout();
}
5.2.2 ByName自动装配
- 自动在容器上下文中查找,和自己对象set方法后面的值对应的beanId
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People" autowire="byName">
<property name="name" value="wangwk-a"/>
</bean>
不用手动注入
ref
相关的两个对象,Spring会根据set方法后面的值去已经配置好的bean中找id相同的bean==注意:People中Set方法后面的值,一定要与bean中id的名称相同才能够匹配==
如果修改Dog类bean的id,则byName自动装配失效
5.2.3 ByType自动转配
- 自动在容器上下文中查找,和自己对象属性类型对应的bean
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog11" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People" autowire="byType">
<property name="name" value="wangwk-a"/>
</bean>
类型如果不唯一,则装配不成功
5.3 注解实现自动装配
- JDK 1.5支持注解,Spring 2.5支持注解
- 注解的方式比xml要更为灵活
==要是用注解,须知:==
- ==导入context约束==
- ==要配置注解的支持==
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> </beans>
配置之后,可以在属性、set方法上使用==@Autowired==进行自动装配。使用@Autowired之后就可以不用编写set方法了,前提是这个自动装配的属性在IOC容器中存在,且符合名字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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--开启注解的支持-->
<context:annotation-config/>
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People">
<property name="name" value="wangwk-a"/>
</bean>
</beans>
- 实体类
Autowired注解参数:
@Autowired(required = false)
:表示该属性可以为Null
构造函数参数注解:
@Nullable
:也表示该参数可以为Null
如果IOC中bean的id与实体类中属性名不一样,且类型不唯一,只使用Autowired是不行的,要搭配使用@Qualifier(value="beanName")
后,就可以自动装配。例如:
<bean id="cat" class="com.nick.pojo.Cat"/>
<bean id="dog1" class="com.nick.pojo.Dog"/>
<bean id="dog2" class="com.nick.pojo.Dog"/>
<bean id="people" class="com.nick.pojo.People">
<property name="name" value="wangwk-a"/>
</bean>
@Data
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
此时是不能自动专配的,因为Autowired会去IOC中找dog
,然而没有找到,然后根据type找,不唯一。因此使用@Qualifier
@Data
public class People {
@Autowired
private Cat cat;
@Autowired
@Qualifier("dog1")
private Dog dog;
private String name;
}
==@Resource是java的注解,也可以实现自动装配==
@Resource(name="beanId")
:可以指定bean的id
@Autowired
与@Autowired
的区别:
- 都是自动装配的,都可以放在属性字段上
- ==@Autowired:先找type再找name==
- ==@Resource:先找name再找type==
六、Spring注解开发
- 在Spring4之后,要是用注解开发,必须要保证AOP的包已导入
- 使用注解要导入
context
约束,增加注解支持
6.1 Bean实现
为使用注解开发时,需要在xml中配置bean,而使用注解开发后,则使用
context
中的扫描包
- 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
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 指定要扫描的包,该包下的注解就会生效-->
<context:component-scan base-package="com.nick"/>
<!-- 开启注解的支持-->
<context:annotation-config/>
</beans>
- 实体类
要想让Spring能够自动扫描指定路径的下包,需要在实体类上添加注解@Component
默认bean的id就是实体类的小写名字
// 等价于在bean里面注册了一个bean
// <bean id="user" class="com.nick.pojo.User"/>
@Component
@Scope("singleton")
public class User {
public String name = "王文堃";
}
6.2 属性如何注入
- 通过set方法和注解可以对属性进行注入
对于简单的可以这样使用,但是对于复杂类型,还是使用配置文件比较方便
@Component
@Scope("singleton")
public class User {
public String name;
// 相当于<property name="name" value="王文堃"/>
@Value("王文堃")
public void setName(String name) {
this.name = name;
}
}
6.3 衍生注解
@Component
有几个衍生注解,在web开发中会按照MVC结构分层:
- dao:
@Repository
- service:
@Service
- controller:
@Controller
上面四个注解是等价的,加上这个注解,含义就是告诉Spring这个类将会被IOC托管,实现自动装配。
6.4 作用域
在@Component
的下面使用@Scope
注解可以对该类的作用域进行设置,其值有singleton
、prototype
等
6.5 使用JavaConfig替代配置文件
现在要完全不使用Spring的xml配置,全权交给java来做
JavaConfig是Spring的子项目,在Spring4之后成为了一个核心功能
- 实体类
@Component
public class User {
private String name;
public String getName() {
return name;
}
// 属性注入值
@Value("王文堃")
public void setName(String name) {
this.name = name;
}
}
之前要在xml中导入
context
约束,开启注解支持,开启包扫描,这里的实体类User才能够自动装配到IOC中
- 配置类
建包:
com.nick.config
创建配置类:
JavaConfig
==所使用的注解:==
@Configuration
:本身也是一个Component
,因此本身也会被IOC托管@ComponentScan("com.nick.pojo")
:配置扫描包@Bean
:注册Bean,id是方法名,class是返回值
// 这个也会被spring容器托管,注册到容器中,因为它本身就是一个@Component
// @Configuration代表这是一个注册类,就和beans.xml一样
@Configuration
@ComponentScan("com.nick.pojo")
public class JavaConfig {
// 注册一个bean,相当于我们之前写的bean标签
// 这个方法的名字就是bean标签中的id属性
// 方法的返回值相当bean标签的class属性
@Bean
public User getUser(){
return new User(); // 就是返回要注入到bean的对象
}
}
- 使用配置类后调用方式也发生了改变
之前使用xml是通过
ClassPathXmlApplicationContext
方法来返回ApplicationContext
实例现在使用
AnnotationConfigApplicationContext
来返回实例
public class MyTest {
public static void main(String[] args) {
// 如果完全使用配置类来操作,就要是用AnnotationConfig上下文来获取容器
// 通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user.getName());
}
}
JavaConfig可以通过
@Import
注解将多个config类合并
@Configuration
@ComponentScan("com.nick.pojo")
@Import(JavaConfig2.class) // 将两个类引入成一个类
public class JavaConfig {
// ......
}
==说明:现在出现的都是Spring的注解,实际上学习SpringBoot的时候,会出现很多SpringBoot特有的注解,但是点进去底层都是通过spring的注解实现的==
七、AOP
AOP的底层实现是动态代理,动态代理的基础是静态代理,学习这里应该首先要对静态代理和动态代理有一定的了解
7.1 什么是AOP
==AOP(Aspect Oriented Programming)==:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中一个重要内容,是函数式编程的一种衍生泛范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。
7.2 AOP在spring中的作用
==提供声明式事务;允许用户自定义切面==
一些名词:
- 横切关注点:跨越应用程序多个模块的方法或功能,与我们业务逻辑无关,但是我们需要关注的部分,就是横切关注点,比如==日志、安全、缓存、事务等==
- 切面(Aspect):横切关注点被模块化的特殊对象,是一个==类==
- 通知(Advice):切面必须要完成的工作,即类中的一个==方法==
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的地点定义
- 连接点(JointPoint):与切入点匹配的执行点
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5中类型的Advice
7.3 AOP的使用
7.3.1 环境准备
- 使用AOP需要导入织入包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 原始业务
- 接口
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
- 实现类
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加");
}
public void delete() {
System.out.println("删除");
}
public void update() {
System.out.println("更改");
}
public void select() {
System.out.println("查看");
}
}
7.3.2 方式一:使用Spring接口实现(切入点)
创建包
com.nick.log
,并创建Log
类
- 在业务方法前调用:==org.springframework.aop.MethodBeforeAdvice==
public class Log implements MethodBeforeAdvice {
/**
*
* @param method 要执行的目标对象的方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
- 在业务方法后调用:==org.springframework.aop.AfterReturningAdvice==
public class AfterLog implements AfterReturningAdvice {
/**
* @param returnValue 返回值
* @param method 要执行的目标对象的方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
}
}
- 配置文件引AOP
- 导入AOP:
xmlns:aop="http://www.springframework.org/schema/aop"
- 添加对应约束:
http://www.springframework.org/schema/aop
、http://www.springframework.org/schema/aop/spring-aop.xsd
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userservice" class="com.nick.service.UserServiceImpl"/>
<bean id="log" class="com.nick.log.Log"/>
<bean id="afterlog" class="com.nick.log.AfterLog"/>
<!--方式一:使用原生Spring API接口-->
<!--配置AOP:需要导入AOP约束-->
<aop:config>
<!-- 切入点:expression表达式,execution要执行的位置-->
<aop:pointcut id="pointcut" expression="execution(* com.nick.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
==重点:==
- 切入点:就是告诉Spring需要在哪里执行AOP
- 标签:
<aop:pointcut>
- 值:
id
:切入点的名字expression
:切入点表达式,就是表示要执行切入的具体位置execution()
:要执行的位置*
:第一个*表示任意的返回值com.nick.service.UserServiceImpl
表示具体包下的具体类.*
:表示该类下的所有方法(..)
:表示方法中任意的参数
- 执行环绕增强
- 标签:
<aop:advisor>
- 值:
advice-ref
:指明要执行什么pointcut-ref
:指明什么时候执行
测试:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理代理的是接口:注意点
UserService userService = context.getBean("userservice", UserService.class);
userService.select();
}
结果:
com.nick.service.UserServiceImpl的select被执行了
查看
执行了select方法,返回结果为null
Process finished with exit code 0
7.3.3 方式二:使用自定义类实现AOP(切面)
建包:
com.nick.diy
创建类:
DiyPointCut
public class DiyPointCut {
public void before() {
System.out.println("=======方法执行前=======");
}
public void after() {
System.out.println("=======方法执行后=======");
}
}
- 修改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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userservice" class="com.nick.service.UserServiceImpl"/>
<!--方式二:自定义类-->
<bean id="diy" class="com.nick.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref要引用的类-->
<aop:aspect ref="diy">
<!-- 切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.nick.service.UserServiceImpl.*(..))"/>
<!-- 通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
==重点:==
- ==第一种实现方式,分别实现了Spring定义的连接点,来实现在业务方法前后调用方法,二者分别是两个类,他们是两个分开的点,因此在配置的时候,使用切入点配置==
- ==第二种方式,是实现了一个类
DiyPointCut
,该类中实现了业务前后的方法,这是一个切面(Aspect)【横切关注点被模块化的特殊对象,类】,因此在配置的使用使用切面配置,并使用ref来指明切面所对应的类==- ==切面需要被告知应该在什么时候做什么,因此切面配置
<aop:aspect>
中需要说明:==
- ==
<aop:pointcut>
:什么时候==- ==
<aop:before>
:通知【切面要完成的工作,即做什么】==
7.3.4 方式三:使用注解实现AOP
实际上就是上述方法注解来实现
- 编写切面类
@Aspect
:标记该类是是一个切面@Before
、@After
:前置增强、后置增强,相当于<aop:before>
,注意包要导入org.aspectj.lang.annotation
下的包@Around
:环绕增强,可以增加一个==连接点==参数【与切入点匹配的执行点】,通过连接点的proceed
方法来执行方法
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.nick.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("=========方法执行前=========");
}
@After("execution(* com.nick.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("=========方法执行后=========");
}
// 在环绕增项中,可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.nick.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
// 打印信息
System.out.println("signature:" + jp.getSignature());
// 执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
- 编写xml配置文件
- 开启注解支持:
<aop:aspectj-autoproxy>
proxy-target-class
的值:false
:默认,使用JDKtrue
:使用cglib
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userservice" class="com.nick.service.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.nick.diy.AnnotationPointCut"/>
<!--方式三-->
<!--开启注解支持 JDK(默认 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>
- 结果
- 环绕是在前置后置的外面
getSignature
是获取业务逻辑中被执行的方法
环绕前
signature:void com.nick.service.UserService.select()
=========方法执行前=========
查看
=========方法执行后=========
环绕后
7.4名词总结说明
- 方式一
- 方式二
- 方式三
八、整合Mybatis
参考文档:Mybatis-Spring
8.1 快速入门
要使用 MyBatis-Spring 模块,只需要在类路径下包含 mybatis-spring-2.0.6.jar
文件和相关依赖即可。
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory
和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean
来创建 SqlSessionFactory
。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
- xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置类
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
}
注意:SqlSessionFactory
需要一个 DataSource
(数据源)。这可以是任意的 DataSource
,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
假设你定义了一个如下的 mapper 接口:
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
那么可以通过 MapperFactoryBean
将接口加入到 Spring 中:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。
配置好之后,你就可以像 Spring 中普通的 bean 注入方法那样,将映射器注入到你的业务或服务对象中。MapperFactoryBean
将会负责 SqlSession
的创建和关闭。 如果使用了 Spring 的事务功能,那么当事务完成时,session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 DataAccessException
异常。
使用 Java 代码来配置的方式如下:
@Configuration
public class MyBatisConfig {
@Bean
public UserMapper userMapper() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate.getMapper(UserMapper.class);
}
}
要调用 MyBatis 的数据方法,只需一行代码:
public class FooServiceImpl implements FooService {
private final UserMapper userMapper;
public FooServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User doSomeBusinessStuff(String userId) {
return this.userMapper.getUser(userId);
}
}
8.2 具体示例
mybatis-config.xml
将
sqlSessionFactory
和数据源的配置放到Spring的配置文件中做,这样在使用的时候就不用手动new对象,而在mybatis-config中保留别名
spring-dao.xml
- 数据源
sqlSessionFactory
sqlSessionTemplate
- 给接口增加实现类
application-context.xml
UserMapperImpl
在application-context.xml
中需要配置UserMapperImpl
,将sqlSessionTemplate
通过set方法注入到sqlSession
中,因此在我们不必收到通过配置文件new一个SqlSessionFactory然后获取sqlSession了。
- 具体调用
- 之前
整合Spring之前,调用时需要通过配置文件来创建sqlSessionFactory,然后获取sqlSession
- 现在调用
九、声明式事务
9.1 回顾事务
- 要么都成功,要么都失败
- 事务在项目中十分重要,涉及到数据的一致性问题
事务的ACID原则:
- 原子性
- 一致性
- 隔离性
- 持久性
9.2 spring事务
Spring中事务分为:
- 声明式事务:AOP
- 编程式事务:需要代码中进行事务管理
9.2.1 环境准备
- 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
private int id;
private String name;
private String password;
private String email;
private Date birthday;
}
spring-dao.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">
<!--dataSource:使用Spring提供的jdbc数据源来替换Mybatis的配置 其他的数据源:c3p0 dbcp druid-->
<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/jdbc?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="wang2995"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--绑定Mapper-->
<property name="mapperLocations" value="classpath:com/nick/mapper/*.xml"/>
</bean>
<!--SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--SqlSessionTemplate类没有set方法,只能使用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
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: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/>
<import resource="spring-dao.xml"/>
<!--注入UserMapper的实现类-->
<bean id="userMapper" class="com.nick.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
UserMapper
接口
public interface UserMapper {
List<Users> queryAllUsers();
int insertUser(Users user);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nick.mapper.UserMapper">
<select id="queryAllUsers" resultType="com.nick.pojo.Users">
SELECT * FROM users
</select>
<insert id="insertUser" parameterType="com.nick.pojo.Users">
insert into users
values(#{user.id}, #{user.name}, #{user.password}, #{user.email}, #{user.birthday})
</insert>
</mapper>
UserMapperImpl
public class UserMapperImpl implements UserMapper {
/**
* 之前使用sqlSession执行,现在使用sqlSessionTemplate
*/
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
}
@Override
public List<Users> queryAllUsers() {
return sqlSession.getMapper(UserMapper.class).queryAllUsers();
}
@Override
public int insertUser(Users user) {
return sqlSession.getMapper(UserMapper.class).insertUser(user);
}
}
- 测试
public class MybatisTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<Users> users = userMapper.queryAllUsers();
users.stream().forEach(item-> System.out.println(item.toString()));
}
@Test
public void insertTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
Users user = new Users(2, "王文堃", "wang2995", "928458710@qq.com", new Date());
userMapper.insertUser(user);
}
}
报错:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'user' in 'class com.nick.pojo.Users'
是因为在
UserMapper
接口中没有添加@Param
- 示例
- 结果
期望是delete出错后,数据回滚,不插入id为5的数据
9.2.2 声明式事务
- 要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个
DataSourceTransactionManager
对象:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
@Configuration
public class DataSourceConfig {
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
传入的 DataSource
可以是任何能够与 Spring 兼容的 JDBC DataSource
。包括连接池和通过 JNDI 查找获得的 DataSource
。
注意:为事务管理器指定的 DataSource
必须和用来创建 SqlSessionFactoryBean
的是同一个数据源,否则事务管理器就无法工作了。
- ==结合AOP实现事务的织入==
在
spring-dao.xml
中导入事务tx
约束
- 配置事务通知
spring中其中Propagation类的事物传播特性:
9.2.3 编程式事物
MyBatis 的 SqlSession
提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession
或映射器。也就是说,Spring 总是为你处理了事务。
你不能在 Spring 管理的 SqlSession
上调用 SqlSession.commit()
,SqlSession.rollback()
或 SqlSession.close()
方法。如果这样做了,就会抛出 UnsupportedOperationException
异常。在使用注入的映射器时,这些方法也不会暴露出来。
无论 JDBC 连接是否设置为自动提交,调用 SqlSession
数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。
下面的代码展示了如何使用 PlatformTransactionManager
手工管理事务。
public class UserService {
private final PlatformTransactionManager transactionManager;
public UserService(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void createUser() {
TransactionStatus txStatus =
transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userMapper.insertUser(user);
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
}
}
在使用 TransactionTemplate
的时候,可以省略对 commit
和 rollback
方法的调用。
public class UserService {
private final PlatformTransactionManager transactionManager;
public UserService(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void createUser() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
userMapper.insertUser(user);
return null;
});
}
}