# ssm - spring
# 1. 核心概念
(1) 什么IOC/DI思想?
IOC : 控制反转,控制反转的是对象的创建权
DI : 依赖注入,绑定对象与对象之间的依赖关系
(2) 什么是 IOC 容器?
Spring 创建了一个容器用来存放所创建的对象,这个容器就叫 IOC 容器
(3) 什么是 Bean?
容器中所存放的一个个对象就叫 Bean 或 Bean对象
# 2. 入门案例
目录:
${root}
src/main
java/
org/example
App.java
BookDao.java
BookDaoImpl.java
BookService.java
BookServiceImpl.java
resources/
applicationContext.xml
pom.xml
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
resources/applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="org.example.BookDaoImpl"/>
<bean id="bookService" class="org.example.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
class:
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
# 3. IOC 相关
# 3.1. bean 基础配置
<!--
id: 必须唯一
class: 指定实现类的全名,不可以写接口
name: 指定别名,可定义多个,分隔符: 空格、分号、空格
scope: "singleton" (单例,默认) | "prototype" (非单例)
-->
<bean
id="bookDao"
class="org.example.quickstart.BookDaoImpl"
name="bookDao1, bookDao2"
scope="singleton"
/>
# 3.2. bean 实例化
(1) 方式一: (无参)构造函数 实例化 bean
类:
// 实现类
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
配置:
<bean id="bookDao" class="org.example.BookDaoImpl"/>
使用:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
(2) 方式二: 静态工厂 实例化 bean
类:
// 静态工厂类
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
// 实现类
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
配置:
<bean id="orderDao" class="org.example.OrderDaoFactory" factory-method="getOrderDao" />
使用:
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
(3) 方式三: 实例工厂 实例化 bean
类:
// 实例工厂类
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
// 实现类
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
配置:
<bean id="userFactory" class="org.example.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
使用:
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
(4) 方式四: FactoryBean 实例化 bean
类:
// 工厂类
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl(); // 返回 bean
}
@Override
public Class<?> getObjectType() {
return UserDao.class; // 指定 bean 的类型
}
}
配置:
<bean id="userDao2" class="org.example.UserDaoFactoryBean" />
使用:
UserDao userDao = (UserDao) ctx.getBean("userDao2");
userDao.save();
# 3.3. bean 的生命周期
(1) 关于 Spring 中对 bean 生命周期控制提供了两种方式:
- 在配置文件中的 bean 标签中添加
init-method和destroy-method属性 - 类实现
InitializingBean与DisposableBean接口
<bean id="bookDao" class="org.example.BookDaoImpl" init-method="init" destroy-method="destroy" />
<bean id="userDao" class="org.example.UserDaoImpl"/>
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDaoImpl bookDao = (BookDaoImpl) ctx.getBean("bookDao");
bookDao.save();
UserDaoImpl userDao = (UserDaoImpl) ctx.getBean("userDao");
userDao.save();
// ctx.close();
ctx.registerShutdownHook();
}
}
public class BookDaoImpl {
public void save() {
System.out.println("book dao save ...");
}
public void init() {
System.out.println("book dao init ...");
}
public void destroy() {
System.out.println("book dao destroy ...");
}
}
public class UserDaoImpl implements InitializingBean, DisposableBean {
public void save() {
System.out.println("user dao save ...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("user dao init ...");
}
@Override
public void destroy() throws Exception {
System.out.println("user dao destroy ...");
}
}
(2) 对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置如下:
- 初始化容器:
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用 bean
- 执行业务操作
- 关闭/销毁容器
- 执行 bean 销毁方法
(3) 关闭容器的两种方式:
- ConfigurableApplicationContext 是 ApplicationContext 的子类
- close() 方法
- registerShutdownHook() 方法
# 4. DI 相关内容
# 4.1. setter 注入
<bean id="bookDao" class="org.example.BookDaoImpl" />
<bean id="bookService" class="org.example.BookServiceImpl">
<!-- 注入 引用类型 -->
<property name="bookDao" ref="bookDao" />
<!-- 注入 简单类型 -->
<property name="name" value="ZhangSan" />
<property name="num" value="18" />
</bean>
public class BookServiceImpl {
private BookDaoImpl bookDao;
private String name;
private int num;
public void setBookDao(BookDaoImpl bookDao) {
this.bookDao = bookDao;
}
public void setName(String name) {
this.name = name;
}
public void setNum(int num) {
this.num = num;
}
public void save() {
bookDao.save();
System.out.println("book service save ... name=" + name + ", num=" + num);
}
}
# 4.2. 构造器注入
<bean id="bookDao" class="org.example.BookDaoImpl" />
<bean id="bookService" class="org.example.BookServiceImpl">
<!-- name 为形参的名称 -->
<constructor-arg name="bookDao" ref="bookDao" />
<constructor-arg name="name" value="ZhangSan" />
<constructor-arg name="num" value="20" />
</bean>
public class BookServiceImpl {
private BookDaoImpl bookDao;
private String name;
private int num;
public BookServiceImpl(BookDaoImpl bookDao, String name, int num) {
this.bookDao = bookDao;
this.name = name;
this.num = num;
}
public void save() {
bookDao.save();
System.out.println("book service save ... name=" + name + ", num=" + num);
}
}
介绍完两种参数的注入方式,具体我们该如何选择呢?
- 强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致 null 对象出现
- 强制依赖指对象在创建的过程中必须要注入指定的参数
- 可选依赖使用 setter 注入进行,灵活性强
- 可选依赖指对象在创建过程中注入的参数可有可无
- Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
- 自己开发的模块推荐使用 setter 注入
# 4.3. 自动装配
说明:
- IoC 容器根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中的过程称为自动装配
- 针对引用类型自动装配
自动装配方式:
- 按类型(常用)
- 按名称
示例:
<bean class="org.example.BookDaoImpl" />
<bean id="bookService" class="org.example.BookServiceImpl" autowire="byType" />
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意事项:
- 需要注入属性的类中对应属性的 setter 方法不能省略
- 被注入的对象必须要被 Spring 的 IO 容器管理
- 按照类型在 Spring 的 IOC 容器中如果找到多个对象,会报 NoUniqueBeanDefinitionException
# 4.4. 集合注入
集合类型:
- Array
- List
- Set
- Map
- Properties
示例:
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void setArray(int[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println("book dao save ...");
System.out.println("遍历数组:" + Arrays.toString(array));
System.out.println("遍历List" + list);
System.out.println("遍历Set" + set);
System.out.println("遍历Map" + map);
System.out.println("遍历Properties" + properties);
}
}
<bean id="bookDao" class="org.example.BookDaoImpl">
<property name="array">
<array>
<value>1</value>
<value>2</value>
</array>
</property>
<property name="list">
<list>
<value>ZhangSan</value>
<value>LiSi</value>
</list>
</property>
<property name="set">
<set>
<value>数学</value>
<value>语文</value>
</set>
</property>
<property name="map">
<map>
<entry key="code" value="0"/>
<entry key="message" value="success"/>
</map>
</property>
<property name="properties">
<props>
<prop key="name">张三</prop>
<prop key="age">18</prop>
</props>
</property>
</bean>
# 5. IOC/DI 配置管理第三方 bean
# 5.1. 案例:数据源对象管理
Druid(德鲁伊) / C3P0
<!--
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
<property name="maxPoolSize" value="10"/>
</bean>
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
# 5.2. 加载 properties 文件
<!--
开 context 名称空间
-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--
system-properties-mode="NEVER"
不加载系统环境变量,避免与文件中定义的变量冲突
location="classpath:jdbc.properties"
加载当前项目类路径下的 jdbc.properties
location="classpath:1.properties, 2.properties"
加载当前项目类路径下的 多个文件
location="classpath:*.properties"
加载当前项目类路径下的所有 .properties 文件
location="classpath*:*.properties"
加载 当前项目类路径及jar包 中所有 .properties 文件
-->
<context:property-placeholder location="classpath:jdbc.properties" system-properties-mode="NEVER"/>
<bean id="dataSource" class="org.example.MyDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
jdbc.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=123456
public class MyDataSource {
private String driverClassName;
private String url;
private String username;
private String password;
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String toString() {
return "driverClassName=" + driverClassName
+ ", url=" + url
+ ", username=" + username
+ ", password=" + password
;
}
}
# 6. 核心容器
容器的创建方式:
// 类路径下的XML配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 文件系统下的XML配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
Bean的三种获取方式:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
BookDao bookDao = ctx.getBean(BookDao.class);
容器类层次结构:
- BeanFactory: 最上级的父接口
# 7. IOC/DI 注解开发
# 7.1. 注解开发定义 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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描 org.example 递归查找该包及子包的类 -->
<context:component-scan base-package="org.example" />
</beans>
@Repository
public class BookDaoImpl implements BookDao { }
@Service("bookService")
public class BookServiceImpl implements BookService { }
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
System.out.println(bookService);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
}
}
# 7.2. 纯注解开发模式
配置类 替代 配置文件:
@Configuration
@ComponentScan("org.example")
public class SpringConfig {
}
加载配置类:
public class AppForSpringConfig {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = (BookService) ctx.getBean("bookService");
System.out.println(bookService);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao);
}
}
# 7.3. bean 作用范围与生命周期
作用范围:
@Scope("singleton")// "prototype"
生命周期:
@PostConstruct@PreDestroy
示例:
@Repository
@Scope("singleton") // "prototype"
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
@PostConstruct // 构造器执行之后
public void myInit() {
System.out.println("init ...");
}
@PreDestroy // 销毁之前
public void myDestroy() {
System.out.println("destroy ...");
}
}
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println(ctx.getBean(BookDao.class));
System.out.println(ctx.getBean(BookDao.class));
ctx.close();
}
}
# 7.4. 依赖注入
自动装配:
@Autowired、@Qualifier@Value
加载属性配置文件:
@PropertySource("jdbc.properties")
示例:
@Configuration
@ComponentScan("org.example")
@PropertySource("jdbc.properties") // src/main/resources/jdbc.properties
public class SpringConfig {
}
@Service
public class BookServiceImpl implements BookService {
@Autowired // 引用注入
// @Qualifier("bookDao") // 注入指定 id 的 bean,如 @Repository("bookDao")
private BookDao bookDao;
@Value("${jdbc.username}") // 值注入
private String username;
@Override
public void save() {
System.out.println("book service save ..." + username);
bookDao.save();
}
}
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.getBean(BookService.class).save();
}
}
# 7.5. 管理第三方bean
第三方 bean 管理:
@Bean
第三方 bean 依赖注入:
@Import({JdbcConfig.class})- 简单类型:成员属性
- 引用类型:方法形参
示例:
@Configuration
@ComponentScan("org.example")
@PropertySource("jdbc.properties") // src/main/resources/jdbc.properties
@Import({JdbcConfig.class})
public class SpringConfig {
}
public class JdbcConfig {
@Value("${jdbc.username}") // 通过成员属性 注入 简单类型
private String username;
@Bean
public DataSource dataSource(BookDao bookDao) { // 通过形参 注入 引用类型
bookDao.save();
DruidDataSource ds = new DruidDataSource();
ds.setUsername(username);
return ds;
}
}
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource bean = ctx.getBean(DataSource.class);
System.out.println(bean);
}
}
# 8. Spring 整合
# 8.1. 配置集成 Mybatis
数据库:
CREATE DATABASE spring_db;
CREATE TABLE `spring_db`.`tbl_account` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(35) ,
`money` double,
PRIMARY KEY (`id`),
)
目录:
${root}/
src/main/java
org/example
domain/
Account.java
dao/
AccountDao.java
service/
AccountService.java
service/impl/
AccountServiceImpl.java
resources/
jdbc.properties
SqlMapConfig.xml
pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
org.example.domain.Account:
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
org.example.dao.AccountDao:
public interface AccountDao {
@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
void save(Account account);
@Delete("delete from tbl_account where id = #{id} ")
void delete(Integer id);
@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
void update(Account account);
@Select("select * from tbl_account")
List<Account> findAll();
@Select("select * from tbl_account where id = #{id} ")
Account findById(Integer id);
}
org.example.service.AccountService:
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
org.example.service.impl.AccountServiceImpl:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void save(Account account) {
accountDao.save(account);
}
public void update(Account account){
accountDao.update(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
public List<Account> findAll() {
return accountDao.findAll();
}
}
src/main/resources/jdbc.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=123456
src/main/resources/SqlMapConfig.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--读取外部properties配置文件-->
<properties resource="jdbc.properties"/>
<!--别名扫描的包路径-->
<typeAliases>
<package name="org.example.domain"/>
</typeAliases>
<!--数据源-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--映射文件扫描包路径-->
<mappers>
<package name="org.example.dao"/>
</mappers>
</configuration>
App.java:
public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(1);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();
}
}
# 8.2. 注解集成 Mybatis
新增文件的目录:
${root}/
src/main/java
org/example
config/
JdbcConfig.java
MybatisConfig.java
SpringConfig.java
AppForAnnotation.java
依赖:
<dependency>
<!--Spring 操作数据库需要该 jar 包-->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<!--
Spring与Mybatis整合的jar包
这个jar包mybatis在前面,是Mybatis提供的
-->
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
代码:
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
public class MybatisConfig {
// 定义bean,SqlSessionFactoryBean,用于产生 SqlSessionFactory 对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
// 设置模型类的别名扫描
ssfb.setTypeAliasesPackage("org.example.domain");
// 设置数据源
ssfb.setDataSource(dataSource);
return ssfb;
}
// 定义 bean,返回 MapperScannerConfigurer 对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("org.example.dao");
return msc;
}
}
@Configuration
@ComponentScan("org.example")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
public class AppForAnnotation {
public static void main(String[] args) throws IOException {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = ctx.getBean(AccountService.class);
Account ac = accountService.findById(2);
System.out.println(ac);
}
}
# 8.3. 注解集成 Junit
目录:
${root}/
src/test/java/
org/example/service/
AccountServiceTest.java
依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
AccountServiceTest.java:
// 设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
// 设置 Spring 环境对应的配置类
@ContextConfiguration(classes = {SpringConfig.class}) //加载配置类
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
@Test
public void testFindAll(){
System.out.println(accountService.findAll());
}
}
# 9. AOP
# 9.1. AOP 简介
作用: 在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式。
Spring 的理念:无入侵式/无侵入式
# 9.2. AOP 入门案例
需求: 使用 SpringAOP 的注解方式完成在方法执行的前打印出当前系统时间。
依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
目录:
${root}/
src/main/java
org/example/
aop/
MyAdvice.java
config/
SpringConfig.java
dao/
BookDao.java
impl/BookDaoImpl.java
App.java
代码:
public interface BookDao {
public void save();
}
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
@Component
@Aspect
public class MyAdvice {
// 切入点 BookDao.save()
@Pointcut("execution(void org.example.dao.BookDao.save())")
private void pt(){}
// 在切入点 pt() 之前执行
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
@Configuration
@ComponentScan("org.example")
@EnableAspectJAutoProxy // 开启注解格式AOP功能
public class SpringConfig {
}
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.save();
}
}
# 9.3. AOP 工作流程
在创建通知类的对象时,对那些使用了切入点的 bean, 创建代理对象。
# 9.4. AOP 切入点表达式
# 9.4.1. 语法格式
execution(public User com.itheima.service.UserService.findById(int))
- execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
- public:访问修饰符,还可以是public,private等,可以省略
- User:返回值,写返回值类型
- com.itheima.service:包名,多级包使用点连接
- UserService:类/接口名称
- findById:方法名
- int:参数,直接写参数的类型,多个类型用逗号隔开
- 异常名:方法定义中抛出指定异常,可以省略
public class MyAdvice {
// 匹配实现类的方法
// @Pointcut("execution(void org.example.dao.impl.BookDaoImpl.save())")
// (推荐)匹配接口的方法
// @Pointcut("execution(void org.example.dao.BookDao.save())")
private void pt(){}
}
# 9.4.2. 通配符
*: 表示 1个或多个
// 任意类型 org.example.1个任意包名.BookDao.find*(1个任意的参数类型)
@Pointcut("execution(* org.example.*.BookDao.find*(*))")
..:表示 0个或多个
// 任意类型 org.0个或多个任意包名.BookDao.findById(0个或多个参数)
@Pointcut("execution(* org..BookDao.findById(..))")
# 9.4.3. 书写技巧
对于切入点表达式的编写其实是很灵活的,那么在编写的时候,有没有什么好的技巧让我们用用:
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用
*通配快速描述 - 包名书写尽量不使用..匹配,效率过低,常用
*做单个包描述匹配,或精准匹配 - 接口名/类名书写名称与模块相关的采用
*匹配,例如UserService书写成*Service,绑定业务层接口名 - 方法名书写以动词进行精准匹配,名词采用
*匹配,例如 getById 书写成getBy*,selectAll 书写成 selectAll - 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
# 9.5. AOP 通知类型
共提供了5种通知类型:
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回后通知(了解)
- 抛出异常后通知(了解)
(1)前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容
(2)后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容
(3)返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加
(4)抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加
(5)环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能,具体是如何实现的,需要我们往下学习。
示例:
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
@Override
public int selectById(String id) {
System.out.println("book dao selectById ..." + id);
return 100;
}
}
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void org.example.dao.BookDao.save())")
private void savePt(){}
@Pointcut("execution(int org.example.dao.BookDao.selectById(String))")
private void selectByIdPt(){}
@Before("selectByIdPt()")
public void before(){
System.out.println("before ...");
}
@After("selectByIdPt()")
public void after(){
System.out.println("after ...");
}
@Around("selectByIdPt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before ...");
// 如果不调用 pjp.proceed(),则原始方法不执行
Object ret = pjp.proceed();
System.out.println("after ...");
return ret;
}
// 没抛异常才执行
@AfterReturning("selectByIdPt()")
public void afterReturning(){
System.out.println("afterReturning ...");
}
// 抛异常才执行
@AfterThrowing("selectByIdPt()")
public void afterThrowing(){
System.out.println("afterThrowing ...");
}
}
# 9.6. 案例: 业务层接口万次执行效率
@Component
@Aspect
public class ProjectAdvice {
// 配置业务层的所有方法
@Pointcut("execution(* org.example.service.*Service.*(..))")
private void servicePt(){}
@Around("servicePt()")
public void runSpeed(){
// 获取执行签名信息
Signature signature = pjp.getSignature();
// 通过签名获取执行操作名称(接口名)
String className = signature.getDeclaringTypeName();
// 通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"
+ className + "." + methodName
+ "---->" + (end-start) + "ms");
}
}
# 9.7. AOP 通知获取数据
获取参数:
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedingJoinPoint:适用于环绕通知
获取返回值:
- 返回后通知
- 环绕通知
获取异常:
- 抛出异常后通知
- 环绕通知
# 9.7.1. 获取参数
非环绕通知获取方式:
@Before("pt()")
public void before(JoinPoint jp)
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
环绕通知获取方式:
// ProceedingJoinPoint 是 JoinPoint 的子类
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
Object ret = pjp.proceed();
return ret;
}
在环绕通知中,修改参数:
/*
有两个 pjp.proceed() 方法:
* pjp.proceed()
* pjp.proceed(Object[] objects)
调用无参数的 proceed,当原始方法有参数,会在调用的过程中自动传入参数
但是当需要修改原始方法的参数时,就只能采用带有参数的方法,如下:
*/
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = pjp.proceed(args);
return ret;
}
// 有了这个特性后,我们就可以在环绕通知中对原始方法的参数进行拦截过滤,
// 避免由于参数的问题导致程序无法正确运行,保证代码的健壮性。
# 9.7.2. 获取返回值
环绕通知获取返回值:
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object ret = pjp.proceed();
return ret;
}
返回后通知获取返回值:
/*
returning = "形参名称"
如果形参中有 JoinPoint 类型,则该形参必须是第一个参数
*/
@AfterReturning(value = "pt()", returning = "ret")
public void afterReturning(JoinPoint jp, Object ret) {
System.out.println("afterReturning advice ..." + ret);
}
# 9.7.3. 获取异常
环绕通知获取异常:
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object ret = null;
try {
ret = pjp.proceed();
} catch (Throwable throwable){
t.printStackTrace();
}
return ret;
}
抛出异常后通知获取异常:
/*
throwing = "形参名称"
*/
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..." + t);
}
# 9.8. 案例: 去处字符串参数前后空白字符
@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(* org.example.service.*Service.*(..))")
private void servicePt(){}
@Around("DataAdvice.servicePt()")
// @Around("servicePt()") 这两种写法都对
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
// 获取原始方法的参数
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
// 判断参数是不是字符串
if (args[i].getClass().equals(String.class)) {
args[i] = args[i].toString().trim();
}
}
// 将修改后的参数传入到原始方法的执行中
Object ret = pjp.proceed(args);
return ret;
}
}
# 9.9. AOP 事务管理
# 9.9.1. Spring 事务简介
事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring 事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
为什么业务层也需要处理事务呢?
举个简单的例子:
- 转账业务会有两次数据层的调用,一次是加钱一次是减钱
- 把事务放在数据层,加钱和减钱就有两个事务
- 没办法保证加钱和减钱同时成功或者同时失败
- 这个时候就需要将事务放在业务层进行处理
Spring 为了管理事务,提供了一个平台事务管理器 PlatformTransactionManager
public interface PlatformTransactionManager extends TransactionManager {
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
commit 是用来提交事务, rollback 是用来回滚事务。
PlatformTransactionManager 只是一个接口,Spring 还为其提供了一个具体的实现:
public class DataSourceTransactionManager
extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
// ...
}
从名称上可以看出,我们只需要给它一个 DataSource 对象,它就可以帮你去在业务层管理事务。
其内部采用的是 JDBC 的事务,如果你持久层采用的是 JDBC 相关的技术,就可以采用这个事务管理器来管理你的事务。
而 Mybatis 内部采用的就是 JDBC 的事务,所以后期我们 Spring 整合 Mybatis 就采用的这个 DataSourceTransactionManager 事务管理器。
# 9.9.2. 开启事务
步骤 1: 开启事务注解
@Configuration
@ComponentScan("org.example")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
// 开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
步骤 2: 在 JdbcConfig 类中配置事务管理器
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
// 配置事务管理器,mybatis 使用的是 jdbc 事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
步骤 3: 在需要被事务管理的方法上添加注解
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// 该方法开启事务
@Transactional
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
int i = 1/0;
accountDao.inMoney(in,money);
}
}
@Transactional 可以写在 接口类上、接口方法上、实现类上、实现类方法上:
- 写在接口类上,该接口的所有实现类的所有方法都会有事务
- 写在接口方法上,该接口的所有实现类的该方法都会有事务
- 写在实现类上,该类中的所有方法都会有事务
- 写在实现类方法上,该方法上有事务
- 建议写在实现类或实现类的方法上
# 9.9.3. Spring 事务角色
事务管理员:发起事务方,在 Spring 中通常指代业务层开启事务的方法
事务协调员:加入事务方,在 Spring 中通常指代数据层方法,也可以是业务层方法
# 9.9.4. Spring 事务属性
@Transactional的属性
readOnly:
- true 只读事务,false 读写事务,增删改要设为 false, 查询设为 true。
timeout:
- 设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,
-1表示不设置超时时间。
rollbackFor:
- Spring 的事务只会对
Error和RuntimeException及其子类进行事务回滚,也就是说编译期异常默认不回滚 - 此属性可以设置 非
Error和RuntimeException的异常也可以回滚
@Transactional(rollbackFor = {IOException.class})
public void transfer(String out,String in ,Double money) throws IOException {
accountDao.outMoney(out,money);
// int i = 1 / 0;
if (true) throw new IOException();
accountDao.inMoney(in,money);
}
noRollbackFor:
- 当出现指定异常不进行事务回滚
rollbackForClassName:
- 等同于 rollbackFor , 只不过属性为异常的类全名字符串
noRollbackForClassName:
- 等同于 noRollbackFor ,只不过属性为异常的类全名字符串
isolation:
- DEFAULT :默认隔离级别, 会采用数据库的隔离级别
- READ_UNCOMMITTED : 读未提交
- READ_COMMITTED : 读已提交
- REPEATABLE_READ : 重复读取
- SERIALIZABLE: 串行化
propagation:
- 事务的传播行为
# 9.9.5. 事务传播行为

# 9.9.6. 案例: 转账业务追加日志案例
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志

@Repository
public interface LogDao {
@Insert("insert into tbl_log (info,createDate) values(#{info},now())")
void log(String info);
}
public interface LogService {
void log(String out, String in, Double money);
}
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
// 开启新事物,而非加入 事务管理员的事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out,String in,Double money ) {
logDao.log("转账操作由" + out + "到" + in + ",金额:" + money);
}
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private LogService logService;
@Transactional
public void transfer(String out,String in ,Double money) {
try {
accountDao.outMoney(out,money);
int i = 1 / 0;
accountDao.inMoney(in,money);
} finally {
logService.log(out,in,money);
}
}
}
上一篇: 下一篇: