- 底层是核心容器
- Beans
- Core
- Context
- SpringEl表达式
- 中间层技术
- AOP
- Aspects
- 应用层技术
- 数据访问与数据集成
- Web集成
- Web实现
- 基于Test测试
- loC (Ilnversion Of Control)控制反转,Spring反向控制应用程序所需要使用的外部资源
- Spring控制的资源全部放置在Spring容器中,该容器称为loC容器
- 导入spring坐标(5.1.9.release)
- 编写业务层与表现层(模拟〕接口与实现类
- 建立spring配置文件
- 配置所需资源(Service)为spring控制的资源
- 表现层(App)通过spring获取资源(Service实例)
项目用Maven管理工具创建
在 pom.xml 导入spring坐标编写业务层与表现层(模拟〕接口与实现类org.springframework spring-context 5.1.9.RELEASE
表现层(模拟〕接口
package com.liu.service;
public interface UserService {
public void save();
}
实现类
package com.liu.service.impl;
import com.liu.service.UserService;
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("user service running...");
}
}
建立spring配置文件
在resources文件夹下创建applicationContext.xml文件(规定一定要叫这个名字)
配置所需资源(Service)为spring控制的资源在applicationContext.xml文件里
表现层(App)通过spring获取资源(Service实例)
import com.liu.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserApp {
public static void main(String[] args) {
// 2. 加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 3.获取资源
UserService userService = (UserService) ctx.getBean("userService");
userService.save();
}
}
Ioc配置(XML格式)
bean
- 名称: bean
- 类型:标签
- 归属: beans标签
- 作用:定义spring中的资源。受此标签定义的资源将受到spring控制
- 格式:
- 基本属性:
- id: bean的名称,通过id值获取bean
- class: bean的类型
- name: bean的名称,可以通过name值获取bean,用于多人配合时给bean起别名
调用可以用userService、userService2、userService3
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 3.获取资源
UserService userService = (UserService) ctx.getBean("userService2");
bean属性scope
- 名称: scope
- 类型:属性
- 归属: bean标签
- 作用:定义bean的作用范围
- 格式:
- 取值:
- singleton:设定创建出的对象保存在spring容器中,是一个单例的对象
- prototype:设定创建出的对象保存在spring容器中,是一个非单例的对象
- request、session、application、websocket:设定创建出的对象放置在web容器对应的位置
// 2. 加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 3.获取资源
UserService userService = (UserService) ctx.getBean("userService3");
UserService userService2 = (UserService) ctx.getBean("userService3");
UserService userService3 = (UserService) ctx.getBean("userService3");
System.out.println(userService);
System.out.println(userService2);
System.out.println(userService3);
设定(prototype)非单例对象
有多个个性化的使用时就用非单例对象
// 2. 加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 3.获取资源
UserService userService = (UserService) ctx.getBean("userService3");
UserService userService2 = (UserService) ctx.getBean("userService3");
UserService userService3 = (UserService) ctx.getBean("userService3");
System.out.println(userService);
System.out.println(userService2);
System.out.println(userService3);
bean生命周期
- 名称: init-method,destroy-method
- 类型:属性
- 归属: bean标签
- 作用:定义bean对象在初始化或销毁时完成的工作
- 格式:
- 取值: bean对应的类中对应的具体方法名
- 注意事项:
- 当scope= “singleton”时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次
- 当scope= “prototype”时,spring容器要创建同一类型的多个对象,init方法在每个对象创建时均执行一次
- 当scope= "singleton”时,关闭容器会导致bean实例的销毁,调用destroy方法一次
- 当scope= "prototype”时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行
配置
注意: 单例模式只初始化一次,非单例模式 获取多少次就调用多少次初始化函数
引用的是 com.liu.service.impl.UserServiceImpl实现类,所有两个方法在这个类里实现
package com.liu.service.impl;
import com.liu.service.UserService;
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("user service running...");
}
public void init(){
System.out.println("init..........");
}
public void destroy(){
System.out.println("destroy..........");
}
}
bean对象创建方式(了解)
- 名称: factory-bean
- 类型:属性
- 归属: bean标签
- 作用:定义bean对象创建方式,使用静态工厂的形式创建bean,兼容早期遗留系统的升级工作
- 格式:
-
取值:工厂bean中用于获取对象的静态方法名
-
注意事项:
- class属性必须配置成静态工厂的类名
-
名称: factory-bean,factory-method
-
类型:属性
-
归属: bean标签
-
作用:定义bean对象创建方式,使用实例工厂的形式创建bean,兼容早期遗留系统的升级工作
-
格式:
- 取值:工厂bean中用于获取对象的实例方法名
- 注意事项:
- 使用实例工厂创建bean首先需要将实例工厂配置bean,交由spring进行管理
- factory-bean是实例工厂的beanld
DI(Dependency Injection)依赖注入,应用程序运行依赖的资源由Spring为其提供,资源进入应用程序的方式称为注入
依赖注入的两种方式 set注入(主流)- 名称:property
- 类型:标签
- 归属:bean标签
- 作用:使用set方法的形式为bean提供资源
- 格式:
- 基本属性:
- name:对应bean中的属性名,要求该属性必须提供可访问的set方法(严格规范为此名称是set方法对应名称)
- value:设定非引用类型属性对应的值,不能与ref同时使用
- ref:设定引用类型属性对应bean的id,不能与value同时使用
注入引用类型 创建要引入的dao层
UserDao 接口类
package com.liu.dao;
public interface UserDao {
public void save();
}
UserDaoImpl 实现类
package com.liu.dao.impl;
import com.liu.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao running。。。");
}
}
在Service层里创建对应变量
在 UserServiceImpl 实现类里
package com.liu.service.impl;
import com.liu.dao.UserDao;
import com.liu.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao;
//1.对需要进行储存的变量添加set方法
public void setUserDao(UserDao userDao){
this.userDao=userDao;
}
public void save() {
System.out.println("user service running...");
userDao.save();
}
}
在Spring配置文件里注入
在 applicationContext.xml 配置文件里
注入非引用类型
非引用的变量就不用创建dao层
在Service层添创建变量在 UserServiceImpl 实现类里
private int num;
public void setNum(int num){
this.num=num;
}
在Spring配置文件里注入
不用 注入的资源声明 了直接
构造器注入(了解) 创建要引入的dao层(像上面一样,这里不写了) 在Service层里创建对应变量
在 UserServiceImpl 实现类里
package com.liu.service.impl;
import com.liu.dao.UserDao;
import com.liu.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao;
private int num;
private String version;
public UserServiceImpl(UserDao userDao ,Integer num ,String version){
this.userDao=userDao;
this.num=num;
this.version=version;
}
public void save() {
System.out.println("user service running..."+num +" "+version);
userDao.save();
}
}
在Spring配置文件里注入
集合类型数据注入
- 名称:array,list, set,map,props
- 类型:标签
- 归属: property标签或constructor-arg标签
- 作用:注入集合数据类型属性
- 格式:
创建要引入的dao层
BookDao 接口类
package com.liu.dao;
public interface UserDao {
public void save();
}
BookDaoImpl 实现类
package com.liu.dao.impl;
import com.liu.dao.BookDao;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
public class BookDaoImpl implements BookDao {
private ArrayList al;
private Properties properties;
private int[] arr;
private HashSet hs;
private HashMap hm;
public void setAl(ArrayList al) {
this.al = al;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setArr(int[] arr) {
this.arr = arr;
}
public void setHs(HashSet hs) {
this.hs = hs;
}
public void setHm(HashMap hm) {
this.hm = hm;
}
public void save() {
System.out.println("book dao running。。。。");
System.out.println("ArrayList:"+al);
System.out.println("Properties:"+properties);
for (int i=0;i
在Service层里创建对应变量
在 UserServiceImpl 实现类里
package com.liu.service.impl;
import com.liu.dao.BookDao;
import com.liu.dao.UserDao;
import com.liu.service.UserService;
public class UserServiceImpl implements UserService {
private BookDao bookDao;
private UserDao userDao;
private int num;
private String version;
public UserServiceImpl() {
}
public UserServiceImpl(UserDao userDao, int num, String version) {
this.userDao = userDao;
this.num = num;
this.version = version;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setNum(int num) {
this.num = num;
}
public void setVersion(String version) {
this.version = version;
}
public void save() {
System.out.println("user service running..."+num +" "+version);
userDao.save();
bookDao.save();
}
}
在Spring配置文件里注入
在 applicationContext.xml 配置文件里
liu
66666
liu
55555
66666
liu
66666
使用p命名空间简化配置(了解)
- 名称: p:propertyName,p:propertyName-ref类型:属性
- 归属: bean标签
- 作用:为bean注入属性值
- 格式:
- 注意:使用p命令空间需要先开启spring对p命令空间的的支持,在beans标签中添加对应空间支持
后续课程中还将开启其他的命名空间,方式同上
SpEL
- 所有格式统一使用value="************"
- 常量 #{10} #{3.14} #{2e5} #{‘itcast’’}
- 引用bean #{beanld}
- 引用bean属性 #{beanld.propertyName}
- 引用bean方法 beanld.methodName().method2()
- 引用静态方法 T(java.lang.Math).PI
- 运算符支持 #{ 3 lt 4 == 4 ge 3}
- 正则表达式支持 #{user.name matches ‘[a-z]{6}’ }
- 集合支持 #{likes[3]}
properties文件
- Spring提供了读取外部properties文件的机制,使用读取到的数据为bean的属性赋值
- 操作步骤
- 准备外部properties文件
- 开启context命名空间支持
xmlns: context="http: / /www .springframework.org/schema/context"
- 加载指定的properties文件
- 使用加载的数据
- 注意:如果需要加载所有的properties文件,可以使用*. properties表示加载所有的properties文件
- 注意:读取数据使用${propertiesName}格式进行,其中propertiesName指properties文件中的属性名
创建data.properties 文件
username=root
pwd=123
我这里使用 username 变量名会拿到我的电脑名字不知道为什么
修改配置文件
团队开发
- 名称: import
- 类型:标签
- 归属: beans标签
- 作用:在当前配置文件中导入其他配置文件中的项格式:
- 基本属性:
- resource:加载的配置文件名
- Spring容器加载多个配置文件(基本不用)
new ClassPathXm1ApplicationContext ("oonfig1.xml","config2.xml");
创建子配置文件
applicationContext-user.xml
applicationContext-book.xml
引入主配置文件
applicationContext.xml
ApplicationContext 和 BeanFactory
ApplicationContext
- ApplicationContext是一个接口,提供了访问spring容器的API
- ClassPathXmlApplicationContext是一个类,实现了上述功能
- ApplicationContext的顶层接口是BeanFactory
- BeanFactory定义了bean相关的最基本操作
- ApplicationContext在BeanFactory基础上追加了若干新功能
对比BeanFactory
- BeanFactory创建的bean采用延迟加载形式,使用才创建
- ApplicationContext创建的bean默认采用立即加载形式
第三方资源配置
阿里数据源方案 Druid
- maven导入
org.springframework
spring-context
5.1.9.RELEASE
com.alibaba
druid
1.2.7
- applicationContext.xml 配置
- 使用
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserApp {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DruidDataSource dataSource = (DruidDataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
spring整合mybatis综合案例
案例分析
- 非spring环境
- 实体类与表
- 业务层接口与实现
- 数据层接口
- Mybatis核心配置
- Mybatis映射配置
- 客户端程序测试功能
- spring环境
- 实体类与表
- 业务层接口与实现(提供数据层接口的注入操作)
- 数据层接口
- Mybatis核心配置(交给spring控制,该文件省略)
- Mybatis映射配置
- 客户端程序测试功能(使用spring方式获取bean)
- Spring核心配置文件
- Druid数据源的应用(可选)
- Spring整合MyBatis
案例制作步骤——基础准备工作
- 环境准备
- 导入Spring坐标,MyBatis坐标,MySQL坐标,Druid坐标
- 业务类与接口准备
- 创建数据库表,并制作相应的实体类Account
- 定义业务层接口与数据层接口
- 在业务层调用数据层接口,并实现业务方法的调用
- 基础配置文件
- jcbc.properties
- MyBatis映射配置文件
案例制作步骤——整合准备工作
- 整合前基础准备工作
- spring配置文件,加上context命名空间,用于加载properties文件
- 2开启加载properties文件
- 配置数据源druid(备用)
- 定义service层bean,注入dao层bean5. dao的bean无需定义,使用代理自动生成
案例制作步骤——整合工作
- 整合工作
- 导入Spring整合MyBatis坐标
- 将mybatis配置成spring管理的bean (SqlSessionFactoryBean)
- 将原始配置文件中的所有项,转入到当前配置中
- 数据源转换
- 映射转换
- 通过spring加载mybatis的映射配置文件到spring环境中
- 设置类型别名
- 测试结果
- 使用spring环境加载业务层bean,执行操作
具体实现
maven 引入需要的包
在pom.xml文件
4.0.0
org.example
springtest1
1.0-SNAPSHOT
war
org.mybatis
mybatis
3.5.3
mysql
mysql-connector-java
5.1.46
org.springframework
spring-context
5.1.9.RELEASE
org.springframework
spring-jdbc
5.1.9.RELEASE
com.alibaba
druid
1.2.7
com.alibaba
druid
1.2.7
org.mybatis
mybatis-spring
1.3.0
在 domain 层 创建实体类
创建 Account 实体类
package com.liu.domain;
import java.io.Serializable;
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 "Acoount{" +
"id=" + id +
", name='" + name + ''' +
", money=" + money +
'}';
}
}
在dao层创建接口类
package com.liu.dao;
import com.liu.domain.Account;
import java.util.List;
public interface AccountDao {
void save(AccountDao account);
void delete(Integer id);
void update(AccountDao account);
List findAll();
Account findById(Integer id);
}
在service层创建接口和实现类
创建AccountService接口
package com.liu.service;
import com.liu.dao.AccountDao;
import com.liu.domain.Account;
import java.util.List;
public interface AccountService {
void save(AccountDao account);
void delete(Integer id);
void update(AccountDao account);
List findAll();
Account findById(Integer id);
}
创建AccountServiceImpl实现类
package com.liu.service.impl;
import com.liu.dao.AccountDao;
import com.liu.domain.Account;
import com.liu.service.AccountService;
import java.util.List;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void save(AccountDao account) {
accountDao.save(account);
}
public void update(AccountDao account) {
accountDao.update(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
public List findAll() {
return accountDao.findAll();
}
}
创建mybatis映射文件
创建 AccountDao.xml 文件
SELECT * FROM account
INSERT INTO account VALUES (#{id},#{name},#{money})
UPDATE account SET name=#{name},money=#{money} WHERe id=#{id}
DELETE FROM account WHERe id=#{id}
创建spring配置文件
创建applicationContext.xml文件
创建测试类
创建一个仿测试类app.java
import com.liu.domain.Account;
import com.liu.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ctx.getBean("accountService");
Account ac = accountService.findById(1);
System.out.println(ac);
}
}
spring注解开发
spring原始注解
Spring原始注解主要是替代的配置
注解 说明 @Component 使用在类实例化Bean @Controller 使用在web层类上用于实例化Bean @Service 使用在service层类上用于实例化Bean @Repository 使用在dao层类上用于实例化Bean @Autowired 使用在字段上用于根据类型依赖注入 @Qualifier 结合@Autowired一起使用用于根据名称进行依赖注入 @Resource 相当于@Autowired+@Qualifier,按照名称进行注入 @Value 注入普通属性 @Scope 标注Bean的作用范围 @PostConstruct 使用在方法上标注该方法是Bean的初始化方法 @PreDestroy 使用在方法上标注该方法是Bean的销毁方法
注意:
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
案例
先在Spring配置文件(applicationContext.xml)加入配置组件扫描
在要配置的文件里注解
package com.liu.service.impl;
import com.liu.dao.AccountDao;
import com.liu.domain.Account;
import com.liu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.List;
// 就等于
@Component("accountService") //可以用@Service("accountService")代替提高可读性
// @Scope("singleton") 单例模式 @Scope("prototype") 多例模式
public class AccountServiceImpl implements AccountService {
// 就等于
@Autowired // 单个使用,会按照数据类型从spring容器中进行匹配的
@Qualifier("accountDao") // 是按照id值从容器中进行匹配的,但是要结合@Autowired一起使用
//@Resource(name="accountDao") 就等于 @Autowired + @Qualifier("accountDao")
private AccountDao accountDao;
// 直接写值做用不大,拿容器中的配置文件变量值才是它的做用
@Value("${jdbc.driver}")
private String driver;
public void save(AccountDao account) {
accountDao.save(account);
}
// init-method="" 属性等于
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
// destroy-method="" 属性等于
@PreDestroy
public void destroy(){
System.out.println("销毁方法");
}
}
spring新注解
使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:
- 非自定义的Bean的配置:
- 加载properties文件的配置:
- 组件扫描的配置:
- 引入其他文件:
注解 说明 @Configuration 用于指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解 @ComponentScan 用于指定Spring在初始化容器时要扫描的包。
作用和在Spring的xml配置文件中的
—样 @Bean 使用在方法上,标注将该方法的返回值存储到Spring容器中 @PropertySource 用于加载.properties文件中的配置 @import 用于导入其他配置类
案例(整合mybatis)
创建主配置文件类
创建 SpringConfig 主配置文件类
package com.liu.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.import;
import org.springframework.context.annotation.PropertySource;
// 标志该类是Spring的核心配置类
@Configuration
// 配置组件扫描
@ComponentScan("com.liu")
// 加载properties配置文件信息
@PropertySource("classpath:jdbc.properties")
// 引入子配置文件
@import({JDBCConfig.class,MyBatisConfig.class}})
public class SpringConfig {
}
创建 子配置文件类
创建 JDBCConfig 子配置文件类
package com.liu.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import javax.sql.DataSource;
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("dataSource")
public DataSource getDataSource(){
DruidDataSource ds =new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
创建 MyBatisConfig 子配置文件类
package com.liu.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MyBatisConfig {
//
//
//
//
//
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb=new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.liu.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
//
//
//
//
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc=new MapperScannerConfigurer();
msc.setbasePackage("com.liu.dao");
return msc;
}
}
使用Spring
import com.liu.config.SpringConfiguration;
import com.liu.domain.Account;
import com.liu.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); 不用这个了
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = (AccountService) ctx.getBean("accountService");
Account ac = accountService.findById(1);
System.out.println(ac);
}
}
注解整合Junit
在pom.xml加上对应的坐标
......
junit
junit
4.12
test
org.springframework
spring-test
5.1.9.RELEASE
创建测试类
在test文件夹里要测试,就要创建对应的测试类创建com.liu.service.UserServiceTest类
package com.liu.service;
import com.liu.config.SpringConfig;
import com.liu.domain.Account;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
// 设定spring上下文对应的配置
@RunWith(SpringJUnit4ClassRunner.class)
// 设定加载的spring上下文对应的配置
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private AccountService accountService;
@Test
public void textFindById(){
Account ac = accountService.findById(2);
// System.out.println(ac);
// 测试预计值和真实值是否相等
Assert.assertEquals("Joke",ac.getName());
}
@Test
public void testFindAll(){
List list = accountService.findAll();
Assert.assertEquals(2,list.size());
}
}
设定组件扫描加载过滤器
应用场景:当多人做同一个项目,你做好别人还没有做好,直接测试会报错,这时就要过滤别人写的类
- 名称:@ComponentScan
- 类型:类注解
- 位置:类定义上方
- 作用:设置spring配置加载类扫描规则
- 范例:
- includeFilters:设置包含性过滤器
- excludeFilters:设置排除性过滤器
- type:设置过滤器类型
componentscan (
value="com.liu" , //设置基础扫描路径
excludeFilters = //设置过滤规则,当前为排除过滤
@componentscan.Filter( //设置过滤器
type = FilterType.ANNOTATION, //设置过滤方式为按照注解进行过滤
classes=Repository.alass) //设置具体的过滤项,过滤所有CRepository修饰的bean
)
自定义组件过滤器
- 名称:TypeFilter
- 类型:接口
- 作用:自定义类型过滤器
- 范例:
创建过滤器类com.liu.config.filter.MyTypeFilter
public class MyTypeFilter implements TypeFilter {
public boolean match (metadataReader mr,metadataReaderFactory mrf) throws IOException {
Classmetadata cm = metadataReader.getClassmetadata();
String className = cm.getClassName();
if(className.equals("com.liu.dao.imp1.BookDaoImpl")){
return false;
}
return false;
}
}
使用自定义类
@componentscan (
value = "com.liu" ,
excludeFilters = ecomponentscan.Filter(
type = FilterType.CUSTOM,
classes = MyTypeFilter.class
)
)
小节
- 组件扫描加载过滤器
- 过滤策略
- ANNOTATION
- ASSIGNABLE_TYPE
- ASPECTJ
- REGEX
- CUSTOM
- 应用场景
- 数据层接口测试环境
- 业务层接口测试环境
- 各种运行环境设置
自定义导入器
- 名称: lmportSelector
- 类型:接口
- 作用:自定义bean导入器
- 范例:
public class MyimportSelector implements importSelector {
public String[] selectimports(Annotationmetadata icm){
return new String[ ] {"com.liu.dao.impl.AccountDaoImpl"};
}
}
@Configuration
@Componentscan("com.itheima")
@import (MyimportSelector.class)
public class SpringConfig { }
扩展(使用配置文件自定义导入)
创建import.properties配置文件(用于指定那个包下的类导入)
path=com.itheima.dao.impl.*
CustomerimportSelector.java 自定义导入包工具类
package config.selector;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.importSelector;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.Annotationmetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class CustomerimportSelector implements importSelector {
private String expression;
public CustomerimportSelector(){
try {
//初始化时指定加载的properties文件名
Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
//设定加载的属性名
expression = loadAllProperties.getProperty("path");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public String[] selectimports(Annotationmetadata importingClassmetadata) {
//1.定义扫描包的名称
String[] basePackages = null;
//2.判断有@import注解的类上是否有@ComponentScan注解
if (importingClassmetadata.hasAnnotation(ComponentScan.class.getName())) {
//3.取出@ComponentScan注解的属性
Map annotationAttributes = importingClassmetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4.取出属性名称为basePackages属性的值
basePackages = (String[]) annotationAttributes.get("basePackages");
}
//5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
if (basePackages == null || basePackages.length == 0) {
String basePackage = null;
try {
//6.取出包含@import注解类的包名
basePackage = Class.forName(importingClassmetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7.存入数组中
basePackages = new String[] {basePackage};
}
//8.创建类路径扫描器
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
//9.创建类型过滤器(此处使用切入点表达式类型过滤器)
TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
//10.给扫描器加入类型过滤器
scanner.addIncludeFilter(typeFilter);
//11.创建存放全限定类名的集合
Set classes = new HashSet<>();
//12.填充集合数据
for (String basePackage : basePackages) {
scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
}
//13.按照规则返回
return classes.toArray(new String[classes.size()]);
}
}
在主配置文件(SpringConfig.java)里导入 自定义导入包 工具类
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.import;
@Configuration
@ComponentScan("com.liu")
@import(CustomerimportSelector.class)
public class SpringConfig {
}
自定义注册器
- 名称:importBeanDefinitionRegistrar
- 类型:接口
- 作用:自定义bean定义注册器
- 范例:
创建 bean定义注册器MyimportBeanDefinitionRegistrar
public class MyimportBeanDefinitionRegistrar implements importBeanDefinitionRegistrar {
@Override
public void registerBeanbefinitions(Annotationmetadata icm,BeanDefinitionRegistry r) {
//自定义注册器
//1.开启类路径bean定义扫描器,需要参数bean定义注册器BeanDefinitionRegistry,需要制定是否使用默认类型过滤器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(r,false);
//2.添加包含性加载类型过滤器(可选,也可以设置为排除性加载类型过滤器)
TyreFilter tf = new TypeFilter() {
@Override
public boolean match (metadataReader mr,metadataReaderFactory mrf) throws IOException {
//所有匹配全部成功,此处应该添加实际的业务判定条件
return true;
}
};
scanner.addIncludeFilter(tf);
//scanner.accExcIuceEiiter(tf);
//设置扫描路径
scanner.scan("com.itheima");
}
}
在主配置文件(SpringConfig)里使用
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.import;
@Configuration
@ComponentScan("com.liu")
@import(MyimportBeanDefinitionRegistrar.class)
public class SpringConfig {
}
扩展(使用配置文件自定义导入)
创建import.properties配置文件(用于指定那个包下的类导入)
path=com.itheima.dao.impl.*
CustomeimportBeanDefinitionRegistrar.java 自定义导入包工具类
package config.registrar;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.importBeanDefinitionRegistrar;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.Annotationmetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
public class CustomeimportBeanDefinitionRegistrar implements importBeanDefinitionRegistrar {
private String expression;
public CustomeimportBeanDefinitionRegistrar(){
try {
//初始化时指定加载的properties文件名
Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
//设定加载的属性名
expression = loadAllProperties.getProperty("path");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void registerBeanDefinitions(Annotationmetadata importingClassmetadata, BeanDefinitionRegistry registry) {
//1.定义扫描包的名称
String[] basePackages = null;
//2.判断有@import注解的类上是否有@ComponentScan注解
if (importingClassmetadata.hasAnnotation(ComponentScan.class.getName())) {
//3.取出@ComponentScan注解的属性
Map annotationAttributes = importingClassmetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4.取出属性名称为basePackages属性的值
basePackages = (String[]) annotationAttributes.get("basePackages");
}
//5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
if (basePackages == null || basePackages.length == 0) {
String basePackage = null;
try {
//6.取出包含@import注解类的包名
basePackage = Class.forName(importingClassmetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7.存入数组中
basePackages = new String[] {basePackage};
}
//8.创建类路径扫描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
//9.创建类型过滤器(此处使用切入点表达式类型过滤器)
TypeFilter typeFilter = new AspectJTypeFilter(expression,this.getClass().getClassLoader());
//10.给扫描器加入类型过滤器
scanner.addIncludeFilter(typeFilter);
//11.扫描指定包
scanner.scan(basePackages);
}
}
在主配置文件(SpringConfig.java)里导入 自定义导入包 工具类
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.import;
@Configuration
@ComponentScan("com.liu")
@import(CustomeimportBeanDefinitionRegistrar.class)
public class SpringConfig {
}
AOP
Spring的AOP简介
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
AOP 动态代理技术(了解)
后期配置即可,不用写那么多代码
常用的动态代理技术
- JDK代理:基于接口的动态代理技术
- cglib代理:基于父类的动态代理技术
JDK代理的动态代理
创建目标接口和目标实现类
目标接口
package com.liu.proxy.jdk;
public interface TargetInterface {
public void save();
}
目标实现类
package com.liu.proxy.jdk;
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running....");
}
}
创建增强类
package com.liu.proxy.jdk;
public class Advice {
public void before(){
System.out.println("前置增强。。。");
}
public void afterReturning(){
System.out.println("后置增强。。。");
}
}
实现动态代理
package com.liu.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
// 目标对象
final Target target =new Target();
// 增强对象
final Advice advice = new Advice();
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
// 调用代理对象的任何方法,实质上执行的都是invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行目标方法
//前置增强
advice.before();
Object invoke = method.invoke(target, args);
//后置增强
advice.afterReturning();
return invoke;
}
}
);
// 调用代理对象的方法
proxy.save();
}
}
cglib 的动态代理
创建目标类
package com.liu.proxy.jdk;
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running....");
}
}
创建增强类
package com.liu.proxy.jdk;
public class Advice {
public void before(){
System.out.println("前置增强。。。");
}
public void afterReturning(){
System.out.println("后置增强。。。");
}
}
cglib 实现动态代理
package com.liu.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyTest {
public static void main(String[] args) {
// 目标对象
final Target target =new Target();
// 增强对象
final Advice advice = new Advice();
// 返回值 就是动态生成的代理对象 基于cglib
// 1.创建增强器
Enhancer enhancer = new Enhancer();
// 2.设置父类(目标)
enhancer.setSuperclass(Target.class);
// 3.设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
advice.before();//前置增强
Object invoke = method.invoke(target, args); // 执行目标方法
advice.afterReturning();//后置增强
return invoke;
}
});
// 4.创建代理对象
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
AOP相关概念
Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
在正式讲解AOP的操作之前,我们必须理解AOP的相关术语,常用的术语如下:
- Target(目标对象)︰代理的目标对象
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
- Joinpoint(连接点)︰所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点(可以被增强的方法)
- Pointcut(切入点)∶所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(真正被增强的方法)
- Advice(通知/增强)∶所谓通知是指拦截到Joinpoint之后所要做的事情就是通知(增强)
- Aspect(切面):是切入点和通知(引介)的结合
- Weaving(织入)︰是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而Aspect采用编译期织入和类装载期织入
开发明确的事项
需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
AOP技术实现的内容
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP底层使用哪种代理方式
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
知识要点
- aop:面向切面编程
- aop底层实现:基于JDK的动态代理和基于Cglib的动态代理
- aop的重点概念:
- Pointcut(切入点):被增强的方法
- Advice(通知/增强):封装增强业务逻辑的方法
- Aspect(切面):切点+通知
- Weaving(织入):将切点与通知结合的过程
- 开发明确事项:
- 谁是切点(切点表达式配置)
- 谁是通知(切面类中的增强方法)
- 将切点和通知进行织入配置
基于XML的AOP开发
快速入门
- 导入AOP相关坐标
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给spring
- 在applicationContext.xml中配置织入关系
- 测试代码
导入AOP相关坐标
Maven 配置文件 pom.xml
4.0.0
org.example
Spring_aop01
1.0-SNAPSHOT
war
org.springframework
spring-context
5.1.9.RELEASE
org.aspectj
aspectjweaver
1.9.4
org.springframework
spring-test
5.1.9.RELEASE
junit
junit
4.12
test
org.springframework
spring-test
5.1.9.RELEASE
test
创建目标接口和目标类(内部有切点)
创建目标接口类
package com.liu.aop;
public interface TargetInterface {
public void save();
}
创建目标类
package com.liu.aop;
public class Target implements TargetInterface {
public void save() {
System.out.println("save running....");
}
}
创建切面类(内部有增强方法)
package com.liu.aop;
public class MyAspect {
public void before(){
System.out.println("前置增强。。。。。");
}
}
在applicationContext.xml中配置
测试代码
package com.liu.aop;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
XML配置AOP详情
切点表达式的写法
表达式语法:
execution([修饰符]返回值类型包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号*代表任意
- 包名与类名之间一个点.代表当前包下的类,两个点…表示当前包及其子包下的类
- 参数列表可以使用两个点…表示任意个数,任意类型的参数列表
列如:
execution(public void com.itheima. aop.Target.method( ) )
execution (void com.itheima . aop. Target.* ( .. ) )
execution ( * com.itheima.aop.*.*( .. ) )
execution (* com.itheima. aop..*.* (..) )
execution (* *..*.* (..) )
用常用的方式修改:
通知类型
通知的配置语法:
名称 标签 说明 前置通知 用于配置前置通知。指定增强的方法在切入点方法之前执行 后置通知 用于配置后置通知。指定增强的方法在切入点方法之后执行 环绕通知 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行 异常抛出通知 用于配置异常抛出通知。指定增强的方法在出现异常时执行 最终通知 用于配置最终通知。无论增强方式执行是否有异常都会执行
切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。
基于注解的AOP开发
快速入门
基于注解的AOP开发步骤:
- 创建目标接口和目标类(内部有切点)
- 创建切面类((内部有增强方法)
- 将目标类和切面类的对象创建权交给spring
- 在切面类中使用注解配置织入关系
- 在配置文件中开启组件扫描和AOP的自动代理
- 测试
创建目标接口和目标类(内部有切点)
创建目标接口类
package com.liu.anno;
public interface TargetInterface {
public void save();
}
创建目标类
package com.liu.anno;
import org.springframework.stereotype.Component;
@Component("target")
public class Target implements TargetInterface {
public void save() {
System.out.println("save running....");
}
}
创建切面类(内部有增强方法)
package com.liu.anno;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面
public class MyAspect {
// 配置前置通知
@Before("execution(* com.liu.anno.*.*(..))")
public void before(){
System.out.println("前置增强。。。。。");
}
}
在applicationContext-anno.xml中配置
测试代码
package com.liu.test;
import com.liu.anno.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
注解配置AOP详解
注解通知的类型
通知的配置语法:
@通知注解("切点表达式")
名称 标签 说明 前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行 后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行 环绕通知 @Around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行 异常抛出通知 @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行 最终通知 @After 用于配置最终通知。无论增强方式执行是否有异常都会执行
切点表达式的抽取
package com.liu.anno;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面
public class MyAspect {
// 方式一
@Before("pointcut()")
public void before(){
System.out.println("前置增强。。。。。");
}
// 方式二
@AfterReturning("MyAspect.pointcut()")
public void afterReturning(){
System.out.println("后置增强。。。。。");
}
// 定义切点表达式
@Pointcut("execution(* com.liu.anno.*.*(..))")
public void pointcut(){}
}
知识要点注解
aop开发步骤:
- 使用@Aspect标注切面类
- 使用@通知注解标注通知方法
- 在配置文件中配置aop自动代理
AOP底层原理
静态代理
- 装饰者模式(Decorator Pattern)︰在不惊动原始设计的基础上,为其添加功能
创建原始设计的接口和实现类
接口类
package com.liu.service;
import com.liu.service.;
public interface UserService{
public void save ();
}
实现类
package com.liu.service.impl;
import com.liu.service.UserService;
public class UserServiceImpl implements UserService{
public void save () {
system .out.println ( "水泥墙") ;
}
}
装饰者模式 实现类
增强功能一
package base.decorator;
imnport com.liu.service.UserService;
public class UserServiceImplDecorator implements Userservice{
private UserService userService;
public UserServiceimplDecorator(UserService userService){
this.userService= userService;
}
public void save(){
userService.save();
//增强功能
System.out-println("刮大白");
}
}
增强功能二
package base.decorator;
imnport com.liu.service.UserService;
public class UserServiceImplDecorator2 implements Userservice{
private UserService userService;
public UserServiceimplDecorator(UserService userService){
this.userService= userService;
}
public void save(){
userService.save();
//增强功能
System.out-println("贴墙纸");
}
}
测试类
package base.decorator;
import com.liu.service.UserService;
import ccm.liu.service.impl.UserServiceImpl;
public class App {
public static void main (string[] args){
UserService userService = new UserServiceImpl();
UserService userService1 = new UserServiceImplDecorator(userservice);
UserService userService2 = new UserServiceImplDecorator2(userservice1);
userservice2.save();
}
}
动态代理 ----- JDK Proxy
本节开始处有代码
动态代理 ----- CGLIB
- CGLIB(Code Generation Library),Code生成类库
- CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
- CGLIB动态代理无需要原始被代理对象,动态创建出新的代理对象
被代理的类
package com.liu.service;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy(proxyTargetClass = true)
public interface UserService {
public void save();
}
代理代码
package base.cglib;
import com.liu.service.UserService;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class UserServiceCglibProxy {
public static UserService createUserServiceCglibProxy(Class clazz){
//创建Enhancer对象(可以理解为内存中动态创建了一个类的字节码)
Enhancer enhancer = new Enhancer();
//设置Enhancer对象的父类是指定类型UserServerImpl
enhancer.setSuperclass(clazz);
//设置回调方法
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//通过调用父类的方法实现对原始方法的调用
Object ret = methodProxy.invokeSuper(o, args);
//后置增强内容,与JDKProxy区别:JDKProxy仅对接口方法做增强,cglib对所有方法做增强,包括Object类中的方法
if(method.getName().equals("save")) {
System.out.println("刮大白3");
System.out.println("贴墙纸3");
}
return ret;
}
});
//使用Enhancer对象创建对应的对象
return (UserService) enhancer.create();
}
}
测试类
package base.cglib;
import com.liu.service.UserService;
import com.liu.service.impl.UserServiceImpl;
public class App {
public static void main(String[] args) {
UserService userService = UserServiceCglibProxy.createUserServiceCglibProxy(UserServiceImpl.class);
userService.save();
}
}
事务
事务回顾
事务的作用
- 当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性©(要么操作前状态,要么操作后状态)。
- 当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)。
- 事务特征(ACID)
- 原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
- 一致性(Consistency)事务前后数据的完整性必须保持一致
- 隔离性(lsolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
事务隔离级
- 脏读:允许读取未提交的信息
- 原因:Read uncommitted
- 解决方案; Read committed(表级读镇)
- 不可重复读:读取过程中单个数据发生了变化
- 解决方案: Repeatable read(行级写锁)
- 幻读:读取过程中数据条目发生了变化
- 解决方案: Serializable(表级写锁)
事务管理
Spring事务核心对象
- J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
- Spring为业务层提供了整套的事务解决方案
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
PlatformTransactionManager
-
平台事务管理器实现类
- DataSourceTransactionManager (适用于Spring JDBC或MyBatis)(重点)
- HibernateTransactionManager (适用于Hibernate3.0及以上版本)(用的比较少)
- JpaTransactionManager (适用于JPA)(了解)
- JdoTransactionManager (适用于JDO)(了解)
- JtaTransactionManager (适用于JTA)(了解)
-
JPA (Java Persistence APl) Java EE标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行
-
JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
-
JTA (Java Transaction APl) JavaEE标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO都们以参与到一个JTA事务中
-
此接口定义了事务的基本操作
- 获取事务∶
Transactionstatus getTransaction (TransactionDefinition definition) - 提交事务∶
void commit(Transactionstatus status) - 回滚事务︰
void rollback (Transactionstatus status)
TransactionDefinition
- 此接口定义了事务的基本信息
- 获取事务定义名称
string getName() - 获取事务的读写属性
boolean isReadonly() - 获取事务隔离级别
int getIsolationLevel() - 获取事务超时时间
int getTimeout () - 获取事务传播行为特征
int getPropagationBehavior()
TransactionStatus
- 此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
- 获取事务是否处于新开启事务状态
boolean isNewTransaction() - 刷新事务状态
void flush() - 获取事务是否处于已完成状态
boolean iscompleted() - 获取事务是否具有回滚存储点
boolean hassavepoint() - 获取事务是否处于回滚状态
boolean isRollbackonly() - 设置事务处于回滚状态
void setRo1lbackonly()
事务控制方式
- 编程式
- 声明式(XML)
- 声明式(注解)
案例介绍
- 银行转账业务说明:
银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
案例环境(基于Spring、Mybatis整合)
- 业务层接口提供转账操作
package com.liu.service;
public interface AccountService {
public void transfer(String outName,String inName,Double money);
}
- 业务层实现提供转账操作
package com.liu.service.impl;
import com.liu.dao.AccountDao;
import com.liu.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName,money);
accountDao.outMoney(inName,money);
}
}
- 数据层提供对应的入账与出账操作
update account set money = money + #{money} where name = #{name}
update account set money = money - #{money} where name = #{name}
编程式事务
业务层
package com.liu.service.impl;
import com.liu.dao.AccountDao;
import com.liu.service.AccountService;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 注入dataSource对象
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void transfer(String outName, String inName, Double money) {
// 开启事务
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
// 事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态
TransactionStatus ts=ptm.getTransaction(td);
accountDao.inMoney(outName,money);
// int x=1/0; //异常就回滚
accountDao.outMoney(inName,money);
// 提交事务
ptm.commit(ts);
}
}
Spring 配置文件(applicationContext.xml)
- 使用spring提供的专用于mybatis的事务管理器在业务层硬编码进行事务管理
- 业务层要注入dataSource对象
使用AOP控制事务
创建事务类
package com.liu.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
public class TxAdvice {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object transactionManager(ProceedingJoinPoint pjp) throws Throwable{
// 开启事务
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
// 事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态
TransactionStatus ts=ptm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
// 提交事务
ptm.commit(ts);
return ret;
}
}
修改业务层
package com.liu.service.impl;
import com.liu.dao.AccountDao;
import com.liu.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName,money);
// int i= 1/0;
accountDao.outMoney(inName,money);
}
}
在Spring配置文件(applicationContext.xml)里配置 AOP
声明式事务(XML格式)
声明式事务配置方式:
- 设定事务管理器
- 专用事务通知器
- AOP配置切面,使用通知器绑定切入点
创建事务类(可以删除了)
业务层(不改变)
在Spring配置文件(applicationContext.xml)里配置 AOP
aop:advice与aop:advisor区别
- aop;advice配置的通知类可以是普通java对象,不实现接口。也不使用继承关系
- aop:advisor配置的通知类必须实现通知接口
- MethodBeforeAdvice
- AfterReturningAdvice
- ThrowsAdvice
- …
tx配置
tx:advice
- 名称: tx:advice
- 类型:标签
- 归属:beans标签
- 作用:专用于声明事务通知
- 格式:
- 基本属性:
- id:用于配置aop时指定通知器的id
- transaction-manager:指定事务管理器bean
tx:attributes
- 名称:tx:attributes
- 类型:标签
- 归属:tx:advice标签
- 作用:定义通知属性
- 格式:
- 基本属性:
- 无
tx:method
- 名称:tx:method
- 类型:标签
- 归属:tx:attribute标签
- 作用:设置具体的事务属性
- 格式:
REQUIRED开启T1 加入T1 无 新建T2 REQUIRES_NEW 开启T1 新建T2 无 新建T2 SUPPORTS 开启T1 加入T1 无 无 NOT_SUPPORTED 开启T1 无 无 无 MANDATORY 开启T1 加入T1 无 报错 NEVER 开启T1 报错 无 无 NESTED 设置savePoint,一旦事务回滚,事务将回滚到aavePoint处,交由客户响应提交/回滚
事务传播应用
- 场景A:生成订单业务
- 子业务S1:记录日志到数据车表X
- 子业务S2:保存订单数据到数据库表Y
- 子业务S3: …
- 如果S2或S3或…事务提交失败,此时S1是否回滚?如何控制?
- (S1需要新事务)
- 场景B:生成订单业务
- 背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取
- 背景2∶每次获取完订单号。表M中记录的编号白增1
- 子业务S1:从表M中获取订单编号
- 子业务S2:保存订单数据,订单编号来自于表M
- 子业务S3:…
- 如果S2或S3或…事务提交失败,此时S1是否回滚?如何控制?
- (S1需要新事务)
声明式事务(注解)
- 名称:@Transactional
- 类型:方法注解,类注解,接口注解
- 位置:方法定义上方,类定义上方,接口定义上方
- 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
- 范例:
@Transactional(
readonly = false,
timeout - -1,
isolation = Isolation.DEEAULT,
rollbackFor = (ArithmeticException.class,工OException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRES_NEW
)
- 名称:tx:annotation-driven
- 类型:标签
- 归属:beans标签
- 作用:开启事务注解驱动,并指定对应的事务管理器
- 范例:
在Spring配置文件(applicationContext.xml)中修改为
在接口类里面加入注解
package com.liu.service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(isolation = Isolation.DEFAULT)
public interface AccountService {
@Transactional(
readonly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {}, //java.lang.ArithmeticException.class, IOException.class
noRollbackFor = {},
propagation = Propagation.MANDATORY.REQUIRED
)
public void transfer(String outName,String inName,Double money);
}
- 小节
- 声明式事务注解方式
- 开启注解驱动
- 配置事务属性(@Transactional)
- 接口(主流)
- 类
- 方法
声明式事务(纯注解驱动)
- 名称:@EnableTransactionManagement
- 类型:类注解
- 位置: Spring注解配置类上方
- 作用:开启注解驱动,等同XML格式中的注解驱动
- 范例:
@configuration
ecomponentscan ("com.liu")
Propertysource ("classpath:jdbc.properties")
@import ({JDBCConfig.class,MyBatisconfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class springconfig {}
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired Datasource dataSource) {
return new DatasourceTransactionManager(dataSource);
}
}
public interface AccountService {
@Transactional
public void transfer(String outName,String inName,Double money);
}
模板对象
Spring模板对象
- TransactionTemplate
- JdbcTemplate
- RedisTemplate
- RabbitTemplate
- JmsTemplate
- HibernateTemplate
- RestTemplate
JdbcTemplate (了解)
提供标准的sql语句操作API
//注册JdbcTemplate模块对象bean
@Bean("jdbaTemplate")
public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//注入模板对象Autowired
private JdbcTemplate jdbaTemplate;
public void save(Account account) {
string sql = "insert into account(name ,money)values (?,?)";
jdbaTemplate.update(sql,account.getName(),account.getMoney());
}
public string findNameById(Integer id){
string sql = "select name from account where id = ? ";
//单字段查询可以使用专用的查询方法,必须制定查询出的数据类型,例如name为string类型
return jdbcTemplate.queryForObject(sql,string.class,id);
}
public List findAll(){
string sql ="select* from account" ;
//使用spring自带的行映射解析器,要求必领是标准封装
return jdboTemplate.query(sql,new BeanPropertyRowMapper(Account.class));
}
redisTemplate
先在Maven配置文件导入redis坐标
......
org.springframework.data
spring-data-redis
2.0.6.RELEASE
redis.clients
jedis
2.9.0
创建redis的配置文件(redis.properties)
# redis服务器主机地址
redis.host=192.168.40.130
#redis服务器主机端口号
redis.port=6378
#redis服务器登录密码
#redis.password=itheima
#最大活动连接
redis.maxActive=20
#最大空闲连接
redis.maxIdle=10
#最小空闲连接
redis.minIdle=0
#最大等待时间
redis.maxWait=-1
创建redis的spring配置文件
package com.liu.config;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@PropertySource("redis.properties")
public class RedisConfig {
@Value("${redis.host}")
private String hostName;
@Value("${redis.port}")
private Integer port;
// @Value("${redis.password}")
// private String password;
@Value("${redis.maxActive}")
private Integer maxActive;
@Value("${redis.minIdle}")
private Integer minIdle;
@Value("${redis.maxIdle}")
private Integer maxIdle;
@Value("${redis.maxWait}")
private Integer maxWait;
@Bean
//配置RedisTemplate
public RedisTemplate createRedisTemplate(RedisConnectionFactory redisConnectionFactory){
//1.创建对象
RedisTemplate redisTemplate = new RedisTemplate();
//2.设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//3.设置redis生成的key的序列化器,对key编码进行处理
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
//4.返回
return redisTemplate;
}
@Bean
//配置Redis连接工厂
public RedisConnectionFactory createRedisConnectionFactory(RedisStandaloneConfiguration redisStandaloneConfiguration,GenericObjectPoolConfig genericObjectPoolConfig){
//1.创建配置构建器,它是基于池的思想管理Jedis连接的
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder builder = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
//2.设置池的配置信息对象
builder.poolConfig(genericObjectPoolConfig);
//3.创建Jedis连接工厂
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration,builder.build());
//4.返回
return jedisConnectionFactory;
}
@Bean
//配置spring提供的Redis连接池信息
public GenericObjectPoolConfig createGenericObjectPoolConfig(){
//1.创建Jedis连接池的配置对象
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
//2.设置连接池信息
genericObjectPoolConfig.setMaxTotal(maxActive);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMaxWaitMillis(maxWait);
//3.返回
return genericObjectPoolConfig;
}
@Bean
//配置Redis标准连接配置对象
public RedisStandaloneConfiguration createRedisStandaloneConfiguration(){
//1.创建Redis服务器配置信息对象
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
//2.设置Redis服务器地址,端口和密码(如果有密码的话)
redisStandaloneConfiguration.setHostName(hostName);
redisStandaloneConfiguration.setPort(port);
// redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
//3.返回
return redisStandaloneConfiguration;
}
}
在业务层注入使用
package com.liu.service.impl;
import com.liu.domain.Account;
import com.liu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private RedisTemplate redisTemplate;
public void save(Account account) {
}
public void changeMoney(Integer id, Double money) {
//等同于redis中set account:id:1 100
redisTemplate.opsForValue().set("account:id:"+id,money);
}
public Double findMondyById(Integer id) {
//等同于redis中get account:id:1
Object money = redisTemplate.opsForValue().get("account:id:" + id);
return new Double(money.toString());
}
}



