一、Spring简介
1. Spring是什么?
spring单词本义是“春天”,程序员的春天
是一个开源的控制反转(IoC)和面向切面(AOP)的容器框架,用来简化企业开发
官网:https://spring.io
2. 为什么使用Spring
降低组件之间的耦合度,实现软件各层之间的解耦
提供了众多的技术支持
对主流框架提供了集成
3. 核心概念
IoC:Inversion Of Control 控制反转
- 控制反转(IoC)就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护由外部容器负责,这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转。
- 外部容器/IoC容器:存放对象(bean)的容器
1 | //Service依赖于DAO |
DI:Dependency Injection 依赖注入
- 依赖注入(DI)就是指在运行期,由外部容器动态地将依赖对象注入到组件中。
1 | //依赖对象的创建及维护由外部容器负责 |
二、第一个Spring程序
1. 添加依赖
1 | <dependency> |
2. 核心配置文件
用来进行bean的配置,文件名可自定义,一般默认为applicationContext.xml
1 |
|
3. 操作
1 | public static void main(String[] args) { |
4. 案例
用户登陆
1 | <bean id="userDao" class="ioc02.dao.impl.UserDaoImpl"/> |
三、数据装配
1. 简介
为bean中的属性注入值,称为数据的装配,可装配不同类型的数据:
简单类型 ——> 使用value
基本类型及包装类型、String
其他bean的引用 ——> 使用ref
集合类型 ——>使用相对应的子元素
数组 List Set Map Properties
注:数据装配是通过setter方法进行的,所以必须有setter方法
2. 基本用法
1 | <bean id="springBean" class="ioc.SpringBean"> |
3. 自动装配
对于其他bean的引用的装配,IoC容器可以根据bean的名称、类型或构造方法进行自动的注入,称为自动装配
通过bean元素的autowire来配置
1 | <!-- |
四、bean管理
1. bean生命周期
生命周期:实例化–>数据装配–>初始化方法–>使用–>销毁方法–>从容器中销毁
1 | <!-- 生命周期的扩展 init destory --> |
在bean实例化并进行数据装配后,会调用初始化方法,执行初始化操作
在bean销毁之前会调用销毁方法,执行销毁前的操作
2. bean实例化时机
默认预先实例化,即在容器启动时实例化,可以设置懒实例化,在第一次使用bean时实例化
1 | <bean id="springBean" class="ioc.SpringBean" lazy-init="true"> |
3. bean作用域
在IoC容器中bean默认是单例的
通过bean元素的scope属性来设置bean的作用域,即是否为单例模式
1 | <!-- |
4. 在普通类中获取bean
实际开发中,并不是所有类都需要交由IoC容器管理,某些类并不需要交由容器管理,如各种工具类,本身都提供了静态方法
- 问题:没有交由容器管理的类,容器是无法进行数据装配的,如何在这些类中获取容器中的bean呢?
- 解决:使用ApplicationContextAware获取容器,然后从容器中获取bean
定义IoC容器工具类,步骤:
- 定义一个类,实现ApplicationContextAware接口
- 将该类添加到IoC容器中,当实例化时会自动注入当前的ApplicationContext
- 调用IoC容器工具类,从容器中获取bean
1 | public class ApplicationContextHolder implements ApplicationContextAware{ |
五、注解配置
1. 简介
Spring提供了一系列注解来替代配置文件,简化配置
使用注解时,需要扫描注解所在的包
1 | <context:component-scan base-package="net.wanho.dao.impl"/> |
2. 常用注解
2.1 定义组件
@Component 定义组件Bean,添加到IoC容器中,不区分组件类型
区分组件类型的注解:
- @Repository 表示Dao组件
- @Service 表示Service组件
- @Controller 表示Controller组件
注:以上三个注解和@Component的作用相同,只是用来表示不同的组件类型
2.2 数据装配
注解方式的数据装配是直接使用属性
进行注入,不是使用setter方法,所以可以没有setter方法
简单类型
1 |
|
1 | <!-- 引用属性文件 --> |
其他bean的引用,使用@Autowired
或 @Resource
1 | // @Autowired // Spring提供的注解,默认按类型注入,如果有多个同类型的,则按名称注入 |
2.3 bean生命周期
1 | // 相当于init-method |
2.4 bean实例化时机
1 | // 默认是预实例化,配置为懒初始化 |
2.5 scope作用域
1 | //默认是单例,配置为非单例 |
六、AOP
1. 简介
AOP:Aspect Oriented Programming 面向切面编程,是OOP面向对象编程的一种补充
将程序中的交叉业务逻辑(事务、日志、异常等)代码提取出来,封装成切面,由AOP容器在适当的时机(位置)将封装的切面动态的织入到具体业务逻辑中。
2. 作用
- 在不改变原有代码的基础上动态添加新的功能
- 模块化,将分散在各层中的相同代码,通过横向切割的方式抽取到单独的模块中
- 方便维护
- 可扩展性强
3. 原理
AOP的原理是使用动态代理技术
动态代理的含义:
- 代理类是在程序运行期间由JVM根据反射等机制动态生成的,自动生成代理类和代理对象
- 所谓动态是指在程序运行前不存在代理类的字节码文件,代理类和委托类的关系是在程序运行时确定
动态代理的两种技术:
- jdk技术:适用于有接口时使用,目标对象必须实现一个或多个接口,否则无法使用jdk动态代理
- cglib技术:适用于无接口时使用(有接口时也可以使用)
注:Spring默认使用的是jdk动态代理,SpringBoot默认使用的是cglib动态代理
4. 用法
步骤:
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>配置Advice
定义增强类并添加@Component和@Aspect注解,表示其为一个切面
配置Pointcut
定义切点表达式
- 在一个空方法上添加@Pointcut注解,配置切点表达式
为方法添加通知类型注解并指定切点
- @Before 前置通知,在方法执行前添加功能
- @AfterReturning 后置通知,在方法执行后添加功能
- @AfterThrowing 异常通知,在方法抛出异常后添加功能
- @Around 环绕通知 在方法执行前后添加功能
配置自动创建代理并织入
1
<aop:aspectj-autoproxy />
七、Spring整合JDBC
1 | drop database if exists spring; |
1. 基本用法
步骤:
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>配置
DataSource–>JdbcTemplate–>Dao–>Service–>Controller
1
2
3
4
5
6
public class UserDaoJdbcImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14<context:property-placeholder location="classpath:datasource.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
2. 事务操作
JDBC默认是自动提交事务的,每执行完一条SQL语句就提交事务
问题:如果Service层出现异常,并不会回滚,怎么办?
解决:配置事务操作(使用的是环绕通知)
1 | <!-- 定义事务管理器 --> |
1 | // @Transactional也可以配置在类上,如果方法上没有配置@Transactional,则使用类上的事务配置 |
3. 事务属性和事务特性
3.1 事务属性
五个事务属性:传播属性、隔离级别、回滚条件、只读优化、超时处理
传播属性
propagation:定义事务的边界,用来配置当前方法是否需要有事务,常用取值:
- REQUIRED 必须添加事务,如果当前没有事务则创建一个新的事务,一般用于增删改
- SUPPORTS 可以没有事务,如果当前有事务则运行,如果没有事务也可以运行,一般用于查询
隔离级别
isolation:用来解决事务并发时会出现的一些问题,四种隔离级别,由低到高依次为:
- READ_UNCOMMITTED 读未提交——>可能出现脏读、不可重复读、幻读
- READ_COMMITTED 读已提交——>避免脏读,但可能出现不可重复读、幻读
- REPEATABLE_READ 可重复读——>避免脏读、不可重复读,但可能出现幻读(MySQL默认隔离级别)
- SERIALIZABLE 可序列化(串行)——>避免脏读、不可重复读、幻读,相当于是单并发,没意义
事务并发时会出现的三个问题(脏读、不可重复读、幻读或虚读):
脏读: 一个事务读取到另一个事务中没有提交的数据,一般不会发生,如MySQL、Oracle底层默认都是只读取提交的数据
1
2
3
4
5-- 打开两个session,即同时登陆两个账户
mysql -uroot -p
set autocommit=off;
insert into t_user values(null,'alice','123');
select * from t_user;不可重复读 : 一个事务已经读取数据,另一个事务在修改数据,可能导致使用的数据与数据库不同步
幻读或虚读 : 一个事务已经读取数据量,另一个事务在添加或删除数据,可能导致使用的数据量与数据库不一致
注:不可重复读和虚读都是小概率事件,实际开发中一般不需要配置隔离级别,大多是通过定时任务来检查+人工审核
回滚条件
rollback:默认抛出RuntimeException时才进行回滚
rollbackFor=”” 表示发生该异常时回滚
noRollbackFor=”” 表示发生该异常时不回滚
只读优化
readOnly:在该事务中只能读取,一般用于查询
超时处理
timeout:配置事务超时的时间,一般不配置
3.2 事务特性
四个事务特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、永久性(Durability)
简称为ACID
八、Spring整合MyBatis
步骤:
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- Spring整合MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>创建Mapper映射文件
映射文件存放位置,两种形式:
将映射文件放在resources目录下
映射文件会进行打包部署
将映射文件放在java目录下
默认只会对该目录下的java代码进行打包部署,如果希望对该目录下的配置文件也进行打包,需要添加额外的配置
编辑pom.xml文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<build>
<resources>
<!-- 将java目录下的配置文件也进行打包 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 可以引用独立的mybatis配置文件,也可以不用 -->
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"></property> -->
<property name="dataSource" ref="dataSource"></property>
<!-- 指定映射文件所在路径 -->
<property name="mapperLocations" value="classpath:org/wanho/mapper/*Mapper.xml"></property>
<!-- 为映射的类指定别名 -->
<property name="typeAliasesPackage" value="net.wanho.entity"></property>
<!-- 自定义配置 -->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<!-- 打印sql -->
<property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
<!-- 下划线映射为驼峰 -->
<property name="mapUnderscoreToCamelCase" value="true"/>
</bean>
</property>
<!-- 分页插件 -->
<property name="plugins">
<list>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
</props>
</property>
</bean>
</list>
</property>
</bean>
<!-- 通过反射创建Dao实现类,然后放到IoC容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定Dao接口所在的包 -->
<property name="basePackage" value="net.wanho.dao"/>
<!--注入sqlSessionFactory,通过value注入String类型的名称-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>