1、Spring
1.1 Spring的概念
1.2 Spring 作用
- Spring是一个项目管理框架,同时也是一套Java EE解决方案。
- Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
- Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。
1.3 Spring的组成
Spring架构由诸多模块组成,可分类为
Spring的组成 |
---|
1.4 spring的IOC底层实现原理
spring的IOC底层实现原理 |
---|
3、Spring快速入门
3.1 引入spring依赖
<dependencies>
<!--
如果是只是IOC的开发,那么只需要导入Spring的四个核心就可以了
beans、core、context、expression
Context中已经包含了其他依赖
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
</dependencies>
3.2 spring的配置文件
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">
<!--
配置UserDao的信息
id:对象的唯一标识
class:对象的全限定名
-->
<bean id="userDao" class="com.qf.dao.UserDao"></bean>
</beans>
3.3 测试
创建Spring容器,获取对象实例
public class SpringTest {
/**
* Spring的开发步骤
* 1、引入Spring依赖
* 2、编写spring的配置文件(配置bean的信息)
* 3、创建spring容器获取对象
*/
@Test
public void test01(){
//获取Spring容器
ApplicationContext ac
= new ClassPathXmlApplicationContext("applicationContext.xml");
//以前创建UserDao对象 UserDao userDao = new UserDao();
//使用Spring容器获取对象
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.addUser();
}
}
4、Spring的开发细节
4.1 BeanFactory的类间关系
- SpringIOC容器类的结构
- 最顶层的接口 BeanFactory
- 子接口: ApplicationContext
- ClassPathXmlApplicationContext 针对类路径下的xml文件配置的方式
- AnnotationConfigApplicationContext 针对纯注解式开发配置的方式
- FileSystemXmlApplicationContext 针对系统路径下的xml文件配置的方式
- BeanFactory 和ApplicationContext 区别
- BeanFactory使用的懒加载的形式,对象在使用的时候才会创建
- ApplicationContext,当程序启动的时候会直接加载配置文件创建对象 (web推荐使用)
@Test
public void test01(){
//创建Spring容器对象 ApplicationContext
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
Object userDao = ac.getBean("userDao1");
System.out.println(userDao);
}
@Test
public void test02(){
//创建Spring容器对象 BeanFactory
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
Object userDao1 = beanFactory.getBean("userDao1");
System.out.println(userDao1);
}
4.2 getBean方法
- getBean(“对象id标识”);
- getBean(类对象);
- getBean(“对象id标识”,类对象);
@Test
public void test03(){
//创建Spring容器对象 ApplicationContext
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
//缺点:获取对象的时候需要强制转换
//UserDao userDao = (UserDao) ac.getBean("userDao1");
//userDao.addUser();
//缺点:如果一个类有多个对象,则报错
//UserDao userDao = ac.getBean(UserDaoImpl.class);
//userDao.addUser();
UserDaoImpl userDao1 = ac.getBean("userDao1", UserDaoImpl.class);
UserDaoImpl userDao2 = ac.getBean("userDao2", UserDaoImpl.class);
userDao1.addUser();
userDao2.addUser();
}
4.3 bean标签中的scope属性
- scope属性的取值
- singleton :表示创建的对象是单例的(默认取值)
- prototype :表示创建的对象是原型模型
- 基于web
- request :表示创建的对象再request范围内
- session :表示创建的对象再session范围内
- globalSession : Spring5.X弃用
<!-- 设置为单例模式 -->
<bean id="userDao2" class="com.qf.dao.impl.UserDaoImpl"></bean>
<!-- 设置为原型模式 -->
<bean id="userDao3" class="com.qf.dao.impl.UserDaoImpl" scope="prototype"></bean>
@Test
public void test04(){
//创建Spring容器对象 ApplicationContext
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao3 = ac.getBean("userDao3", UserDaoImpl.class);
UserDaoImpl userDao4 = ac.getBean("userDao3", UserDaoImpl.class);
System.out.println(userDao3);
System.out.println(userDao4);
}
4.4 创建的对象三种方式
- a、直接配置bean标签 id 对象的唯一标识 class 对象的全限定名
- b、通过静态工厂来创建对象(了解)
- c、通过非静态工厂来创建对象(了解)
静态工厂
<!-- 方式二:静态工厂 class:工厂类 factory-method:静态工厂方法-->
<bean id="userDao5" class="com.qf.factory.UserDaoFactory" factory-method="getUserDao1"/>
非静态工厂
<!-- 方式三:非静态工厂 factory-bean:工厂类的对象 factory-method:工厂非静态方法-->
<!-- 先创建工厂类的对象 -->
<bean id="factory" class="com.qf.factory.UserDaoFactory"></bean>
<bean id="userDao6" factory-bean="factory" factory-method="getUserDao2"></bean>
@Test
public void test05(){
//创建Spring容器对象 ApplicationContext
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao4 = ac.getBean("userDao4", UserDaoImpl.class);
System.out.println(userDao4);
UserDaoImpl userDao5 = ac.getBean("userDao5", UserDaoImpl.class);
System.out.println(userDao5);
UserDaoImpl userDao6 = ac.getBean("userDao6", UserDaoImpl.class);
System.out.println(userDao6);
}
4.5 创建对象的生命周期
- SpringIOC容器创建Bean的生命周期
- 当创建的实例是单例模式:
- 当Spring容器创建的时候,对象就会被创建
- Spring容器关闭,就会销毁类的对象
- 当创建的实例是原型模式:
- Spring容器创建,并不会初始化,而是什么时候使用,什么时候初始化
- Spring容器创建,不会销毁类的对象。而是交个Java内存回收机制
属性配置
<bean id="userDao7" class="com.qf.dao.impl.UserDaoImpl"
scope="prototype" init-method="init" destroy-method="destroy">
</bean>
UserDaoImpl
public class UserDaoImpl implements UserDao {
@Override
public int addUser() {
System.out.println("UserDaoImpl添加。。");
return 0;
}
public void init(){
System.out.println("UserDaoImpl对象初始化");
}
public void destroy(){
System.out.println("UserDaoImpl对象销毁");
}
}
测试
/**
* Spring容器创建对象的生命周期
* 使用ApplicationContext作为Spring容器,
* 当Spring容器启动的时候,对象就会被创建
* 当Spring容器关闭的时候:
* 如果对象是单例的:那么容器关闭,对象就会被销毁
* 原型的:那么容器关闭,对象不会销毁,而是将对象给他Java垃圾回收
*/
@Test
public void test06(){
//创建Spring容器对象 ApplicationContext
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao7 = ac.getBean("userDao7", UserDaoImpl.class);
System.out.println(userDao7);
//Spring容器关闭
ac.close();
}
5、依赖注入(DI)
在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
5.1 set方法注入
创建对象时,Spring工厂会通过Set方法为对象的属性赋值。
范例:定义一个Bean类型
public class User {
private String name;
private int age;
private double money;
private String hobby;
private Date birthday;
//省略set、get方法、构造方法
}
属性注入
<!-- 依赖注入:使用set方法进行注入 user.setName("") user.setBirthday(new Date())-->
<bean id="user" class="com.qf.pojo.User">
<!-- 简单类型注入(String+基本类型) -->
<property name="name" value="张三"/>
<property name="age" value="10"/>
<property name="hobby" value="打篮球"/>
<property name="money" value="200.22"/>
<!-- 对象类型注入(String+基本类型) -->
<property name="birthday" ref="date"/>
</bean>
<bean id="date" class="java.util.Date">
<property name="year" value="122"/>
</bean>
5.2 构造方法注入
范例:定义一个Bean类型 提供构造方法
public class User {
private String name;
private int age;
private double money;
private String hobby;
private Date birthday;
//省略set、get方法、构造方法
}
构造方法注入
<!-- 依赖注入:使用构造方法进行注入 User user = new User("cxk",30,3000,"打篮球",new Date())-->
<bean id="user1" class="com.qf.pojo.User">
<!-- 通过构造方法名注入 -->
<!-- <constructor-arg name="name" value="李四"/>-->
<!-- <constructor-arg name="age" value="20"/>-->
<!-- <constructor-arg name="money" value="222.22"/>-->
<!-- <constructor-arg name="hobby" value="跳"/>-->
<!-- <constructor-arg name="birthday" ref="date"/>-->
<!-- 通过构造方法的参数类型注入 -->
<!-- <constructor-arg type="java.lang.String" value="王五"/>-->
<!-- <constructor-arg type="int" value="30"/>-->
<!-- <constructor-arg type="double" value="333.33"/>-->
<!-- <constructor-arg type="java.lang.String" value="唱"/>-->
<!-- <constructor-arg type="java.util.Date" ref="date"/>-->
<!-- 通过构造方法的下标注入 -->
<constructor-arg index="0" value="赵六"/>
<constructor-arg index="1" value="60"/>
<constructor-arg index="2" value="444.44"/>
<constructor-arg index="3" value="rap"/>
<constructor-arg index="4" ref="date"/>
</bean>
5.3 复杂类型注入
复杂类型指的是:list、set、map、array、properties等类型
定义复杂Bean类型
public class Person {
private List<User> userList;
private Map<String,String> map;
private Set<String> set;
private String[] arr;
private Properties properties;
//省略set、get方法、构造方法
}
给复杂类型注入属性值
<bean id="person" class="com.qf.pojo.Person">
<property name="userList">
<list>
<bean class="com.qf.pojo.User"></bean>
<bean class="com.qf.pojo.User"></bean>
<bean class="com.qf.pojo.User"></bean>
</list>
</property>
<property name="set">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
</set>
</property>
<property name="arr">
<array>
<value>a</value>
<value>b</value>
<value>c</value>
</array>
</property>
<property name="map">
<map>
<entry key="k1" value="v1"></entry>
<entry key="k2" value="v2"></entry>
<entry key="k3" value="v3"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="driver">com.mysql.jdbc.Dirver</prop>
<prop key="url">jdbc:mysql:///java2101</prop>
</props>
</property>
</bean>
5.4 自定义类型的注入
范例:在UserServiceImpl中注入UserDaoImpl类型的属性
public class UserServiceImpl {
private UserDaoImpl userDao;
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
public void addUser(){
userDao.addUser();
}
}
<!-- 属性注入演示 -->
<!-- 创建UserDaoImpl对象 -->
<bean id="userDao" class="com.qf.dao.UserDaoImpl"></bean>
<!-- 创建UserServiceImpl对象 -->
<bean id="userService" class="com.qf.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
6、依赖注入案例
EmpServiceImpl
public class EmpServiceImpl {
private EmpDaoImpl empDao;
public void setEmpDao(EmpDaoImpl empDao) {
this.empDao = empDao;
}
public List<Emp> getAll(){
try {
return empDao.getAll();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
}
EmpDaoImpl
public class EmpDaoImpl {
private QueryRunner queryRunner;
public EmpDaoImpl(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public List<Emp> getAll() throws SQLException {
return queryRunner.query("select * from emp ",new BeanListHandler<Emp>(Emp.class));
}
}
依赖注入配置
<!-- 使用Spring整合DBUtils工具类 -->
<!-- 创建EmpServiceImpl对象 -->
<bean id="empService" class="com.qf.service.EmpServiceImpl">
<property name="empDao" ref="empDao"/>
</bean>
<!-- 创建EmpDaoImpl对象 -->
<bean id="empDao" class="com.qf.dao.EmpDaoImpl">
<constructor-arg name="queryRunner" ref="queryRunner"></constructor-arg>
</bean>
<!-- 创建QueryRunner对象 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!-- 创建DruidDataSource对象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 注入DruidDataSource连接池的配置信息 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///java2101"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<!-- <property name="initialSize" value="40"/>-->
<!-- <property name="maxActive" value="100"/>-->
<!-- <property name="minIdle" value="20"/>-->
<!-- <property name="maxWait" value="600000"/>-->
</bean>
测试
@Test
public void test05(){
//创建Spring容器对象
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
EmpServiceImpl empService = ac.getBean("empService", EmpServiceImpl.class);
List<Emp> empList = empService.getAll();
empList.stream().forEach(System.out::println);
}
7、Spring注解开发
7.1 开启注解扫描
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.qf"/>
7.2 Component 注解
用于替换自建类型组件的 <bean…>标签;可以更快速的声明bean
@Component 注解的含义:创建类的对象,相当于xml中配置的bean标签
- @Controller 针对于控制器层的类的注解
- @Service 针对于业务层的类的注解
- @Repository 针对持久层的类的注解
@Component
//@Service("empService")
//@Controller("empService")
//@Repository("empService")
public class EmpServiceImpl implements EmpService{
}
7.3 Autowired注解
主要用于属性注入
@Autowired 自动装配:将对象属性自动进行注入(类型注入)
@Qualifier(“empDaoImpl1”) 限定要自动注入的bean的id,一般和@Autowired联用
注意:@Autowired先会按照类型注入,如果这个类有多个实例,那么再按照名称注入,如果有多个实例那么需要使用Qualifier进行指定
@Component
public class EmpServiceImpl implements EmpService{
@Autowired
@Qualifier("empDaoImpl1")
//@Resource(name="empDaoImpl2")
private EmpDao empDao;
public void addEmp(){
empDao.addEmp();
}
}
7.4 Resource注解
主要用于属性注入
@Resource 自动装配:将对象属性自动进行注入(名称注入)
注意:默认按照名称进行装配,如果找到指定的名称则直接注入。如果没有找到,那么再按类型称注入,如果有多个实例那么需要使用name属性进行指定
@Component//@Controller @Service @Repository
public class EmpServiceImpl implements EmpService{
@Resource(name="empDaoImpl2")
private EmpDao empDao;
public void addEmp(){
empDao.addEmp();
}
}
8、Spring与Mybaits整合
将 SqlSessionFactory、Mapper、Service 都交给SpringIOC容器管理
8.1 导入依赖
<dependencies>
<!-- 持久层依赖 -->
<!-- 数据库驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- Mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- druid连接池依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- Spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.6</version>
</dependency>
<!-- Spring和Mybatis整合依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Spring整合持久层依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
<!-- 其他依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
8.2 配置SqlSessionFactory
<!-- SqlSessionFactoryBean用来整合Mybatis的类 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置mybatis数据源连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置mybatis的配置信息 -->
<!-- <property name="typeAliasesPackage" value="com.qf.pojo"/> -->
<!-- <property name="mapperLocations" value="classpath:com/qf/mapper/*.xml"/> -->
<!-- 加载mybatis的配置文件 -->
<property name="configLocation" value="classpath*:mybatis-config.xml"/>
</bean>
8.3 配置MapperScannerConfigurer
管理创建Mapper对象,存入工厂管理
- 扫描所有Mapper接口,去构建Mapper实现
- 将Mapper实现存入工厂管理
- Mapper实现对象在工厂中的id是:“首字母小写的-接口的类名”,
例如:UseMapper==>userMapper , OrderMapper==>orderMapper
<!--
MapperScannerConfigurer扫描所有的mapper映射文件,
并且将Mapper接口创建并交给Spring容器 (要保证接口和映射的名称一致、路径一致)
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qf.mapper"/>
</bean>
8.4 配置Service
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper mapper;
@Override
public List<User> getAll() {
return mapper.getAll();
}
}
8.5 测试
public class SMTest {
@Test
public void test01(){
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userServiceImpl = ac.getBean("userServiceImpl", UserServiceImpl.class);
List<User> userList = userServiceImpl.getAll();
userList.stream().forEach(System.out::println);
}
}
9、Spring细节补充
9.1 导入外部配置文件
db.propertis
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///java2101
jdbc.username=root
jdbc.password=123456
applicationContext.xml
<!-- 引入外部的配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 获取外部的配置文件中的属性值 格式:${key} 注意:不能使用username -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<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>
9.2 将配置文件分离
applicationContext.xml Spring主配置文件
applicationContext-dao.xml Spring整合Mybatis的配置文件
applicationContext-xxx.xml 未来Spring整合其他技术的配置文件
applicationContext.xml
<!-- 引入其他Spring的配置文件 -->
<!--整合Mybatis -->
<import resource="classpath:applicationContext-dao.xml"/>
applicationContext-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入外部的配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- Spring整合Mybatis -->
<!-- 配置Druid数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<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>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- mybatis的配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qf.mapper"/>
</bean>
</beans>
9.3 Spring整合Mybatis后的日志
Mybatis核心配置文件
<configuration>
<!-- 设置mybatis日志 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 设置类型别名 -->
<typeAliases>
<package name="com.qf.pojo"/>
</typeAliases>
</configuration>
Spring配置文件中引入
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- mybatis的配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
10、Spring整合Junit
导入依赖
<!-- Spring单元测试依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.6</version>
</dependency>
测试类
@RunWith(SpringJUnit4ClassRunner.class) //使用Spring来执行单元测试
@ContextConfiguration("classpath:applicationContext.xml") //加载spring的配置文件
public class SMTest {
@Autowired
private UserService userService;
@Test
public void test01(){
List<User> userList = userService.getAll();
userList.stream().forEach(System.out::println);
}
}
11、纯注解开发
常见的注解(还有上面的几个注解):
@Configuration 表示当前这个类是Spring配置类
@ComponentScan 扫描指定包上的注解
@Import 引入其他的配置类
@Bean 将创建的对象添加到SpringIOC容器
@Value 将值注入到属性中
@PropertySource 导入外部的Properties配置文件键
主配置类
@Configuration // 表示当前这个类是一个spring配置类(相当于applicationContext.xml)
@ComponentScan("com.qf") //开启注解扫描
@Import(MybatisConfiguration.class) //引入其他的spring的配置类
public class SpringConfiguration {
}
Spring整合Mybatis配置类
@Configuration
@PropertySource(value={"classpath:db.properties"})
@Import(MapperScannerConfigurerConfiguration.class)
public class MybatisConfiguration {
@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") //将当前方法返回的对象交给Spring容器
public DataSource getDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driver);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//设置Mybatis的信息
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
return sqlSessionFactoryBean;
}
}
@Configuration
public class MapperScannerConfigurerConfiguration {
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.qf.mapper");
return mapperScannerConfigurer;
}
}
12、SpringAOP开发
12.1 原理
AOP原理 |
---|
12.2 动态代理
Spring的底层技术就是动态代理
- 动态代理分类
- JDK动态代理
- Cglib动态代理
12.2.1 JDK动态代理
接口:定义代理对象和被代理对象需要做的事情
//定义代理类和被代理类共同做的事情
public interface Subject {
void saleHouse();
}
实现类:目标对象(被增强对象)
public class RealSubject implements Subject{
@Override
public void saleHouse() {
System.out.println("买房子....");
}
}
代理工厂
public class JDKProxyFactory {
/**
* 使用jdk动态代理,获取代理对象
* @param obj 被代理对象
* @return 代理对象
*/
public static Object getProxy(Object obj){
//类加载器
ClassLoader classLoader = JDKProxyFactory.class.getClassLoader();
//被代理对象所实现的接口
Class<?>[] interfaces = obj.getClass().getInterfaces();
//对对象的方法调用的拦截
InvocationHandler invocationHandler = new InvocationHandler() {
/**
*
* @param proxy 代理对象
* @param method 拦截方法
* @param args 拦截方法的参数
* @return 返回的是该方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//做增强工作
System.out.println("找客户。。。。");
Object invoke = method.invoke(obj, args);
System.out.println("办手续。。。。");
return invoke;
}
};
Object proxyInstance =
Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxyInstance;
}
}
测试
@Test
public void test01(){
//通过代理工厂获取代理对象,通过代理对象调用方法
Subject proxy = (Subject) JDKProxyFactory.getProxy(new RealSubject());
proxy.saleHouse();
//说明代理对象不属于RealSubject类,而是Subject接口的实现类
System.out.println(proxy instanceof RealSubject); //false
}
12.2.2 Cglib动态代理
类:目标对象(被增强对象) 不需要实现接口
public class RealSubject{
public void saleHouse() {
System.out.println("买房子....");
}
}
动态代理工厂
public class CglibProxyFactory {
public static Object getProxy(Object obj){
//创建Cglib代理工具类对象
Enhancer enhancer = new Enhancer();
//设置代理对象的父类(代理对象和被代理对象是继承关系)
enhancer.setSuperclass(obj.getClass());
Callback callback = new MethodInterceptor() {
/**
*
* @param o 代理对象
* @param method 拦截的方法
* @param objects 拦截的方法参数
* @param methodProxy 方法的代理对象
* @return 方法的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
//做增强工作
System.out.println("找客户cglib。。。。");
Object invoke = method.invoke(obj, objects);
System.out.println("办手续cglib。。。。");
return invoke;
}
};
//拦截被代理对象的方法
enhancer.setCallback(callback);
//创建代理对象
Object o = enhancer.create();
return o ;
}
}
测试
@Test
public void test02(){
//被代理对象
RealSubject realSubject = new RealSubject();
//通过代理工厂获取代理对象,通过代理对象调用方法
//代理对象
RealSubject proxy = (RealSubject) CglibProxyFactory.getProxy(realSubject);
proxy.saleHouse();
//说明代理对象属于RealSubject类,而且就是RealSubject类的子类
System.out.println(proxy instanceof RealSubject); //true
}
12.2.3 总结
JDK动态代理和Cglib动态代理
- 两者都可以实现产生代理对象
- JDK动态代理是java原生自带的,Cglib动态代理是第三方的
- JDK动态代理实现代理:代理对象和目标对象之间实现同一个接口
- Cglib动态代理实现代理:代理对象是目标对象的子类
12.3 SpringAOP
12.3.1 AOP专业术语
- 连接点(Joinpoint): 表示一切能被增强方法
- 切入点(Pointcut): 被增强的方法
- 通知、增强(Advice): 增强的功能(通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知)
- 目标对象(Target): 被代理对象
- 织入(Weaving): 将通知加入连接点的过程
- 代理(Proxy): 代理对象
- 切面(Aspect): 连接点被增强的内容称之为切面(切入点+通知)
12.3.2 环境搭建
导入依赖
<!-- springAOP切面开发的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.6</version>
</dependency>
12.3.3 基于XML配置
aop配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.qf.aop"/>
<!-- SpringAOP的配置 -->
<!-- 创建增强类 -->
<bean id="advice" class="com.qf.advice.MyAdvice"></bean>
<aop:config>
<!-- SpringAOP的切面 -->
<aop:aspect ref="advice">
<!-- SpringAOP切入点 id:切入点的唯一标识 expression:切入点表达式 -->
<!--
切入点表达式
方法的全称
public void com.qf.service.impl.UserServiceImpl.addUser();
* com.qf.service.impl.UserServiceImpl.addUser();
* com.qf.service..*.*(..);
-->
<aop:pointcut id="pc" expression="execution(* com.qf.service..*.*(..))"/>
<!-- SpringAOP的通知配置 -->
<!-- <aop:before method="before" pointcut-ref="pc"></aop:before>-->
<!-- <aop:after method="after" pointcut-ref="pc"></aop:after>-->
<!-- <aop:after-throwing method="throwing" pointcut-ref="pc"/>-->
<!-- <aop:after-returning method="returning" pointcut-ref="pc"/>-->
<aop:around method="around" pointcut-ref="pc"></aop:around>
</aop:aspect>
</aop:config>
</beans>
增强(通知)类
//增强类
public class MyAdvice {
// public void before(){
// System.out.println("前置通知(在目标对象的方法之前执行)");
// }
// public void after(){
// System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
// }
// public void throwing(){
// System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
// }
// public void returning(){
// System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
// }
public Object around(ProceedingJoinPoint joinPoint){
try {
System.out.println("前置通知(在目标对象的方法之前执行)");
//System.out.println("方法本身");
//执行目标对象的方法
Object o = joinPoint.proceed();
//获取到目标对象
Object target = joinPoint.getTarget();
System.out.println(target);
//获取目标对象的方法
Signature signature = joinPoint.getSignature();
System.out.println(signature.getName());
System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
//通过调用目标对象的方法获取到返回值,并继续返回
return o;
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
} finally {
System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
}
return null;
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private UserService userService;
@Test
public void test01(){
int count = userService.addUser();
System.out.println(count);
//userService.deleteUser();
}
}
结果
前置通知(在目标对象的方法之前执行)
addUser...
com.qf.service.impl.UserServiceImpl@543588e6
addUser
后置通知(在目标对象的方法之后执行(如果有异常则不会执行))
最终通知(在目标对象的方法之后执行(有没有异常都会执行))
100
12.3.4 基于注解配置
xml配置文件
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.qf"/>
<!-- 开启AOP的注解扫描 -->
<aop:aspectj-autoproxy/>
增强类
@Component
@Aspect //设置为切面
public class MyAdvice2 {
@Pointcut("execution(* com.qf.service..*.*(..))")
public void pc(){}
@Before("pc()")
public void before(){
System.out.println("前置通知(在目标对象的方法之前执行)");
}
@After("execution(* com.qf.service..*.*(..))")
public void after(){
System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
}
@AfterThrowing("execution(* com.qf.service..*.*(..))")
public void throwing(){
System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
}
@AfterReturning("execution(* com.qf.service..*.*(..))")
public void returning(){
System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
}
// @Around("execution(* com.qf.service..*.*(..))")
// public Object around(ProceedingJoinPoint joinPoint){
// try {
// System.out.println("前置通知(在目标对象的方法之前执行)");
// //System.out.println("方法本身");
// //执行目标对象的方法
// Object o = joinPoint.proceed();
// //获取到目标对象
// Object target = joinPoint.getTarget();
// System.out.println(target);
// //获取目标对象的方法
// Signature signature = joinPoint.getSignature();
// System.out.println(signature.getName());
// System.out.println("后置通知(在目标对象的方法之后执行(如果有异常则不会执行))");
// //通过调用目标对象的方法获取到返回值,并继续返回
// return o;
// } catch (Throwable e) {
// e.printStackTrace();
// System.out.println("异常通知(在目标对象的方法之后执行(如果有异常则才会执行))");
// } finally {
// System.out.println("最终通知(在目标对象的方法之后执行(有没有异常都会执行))");
// }
// return null;
// }
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class AOPTest {
@Autowired
private UserService userService;
@Test
public void test01(){
userService.deleteUser();
}
}
结果
前置通知(在目标对象的方法之前执行)
deleteUser...
后置通知(在目标对象的方法之后执行(如果有异常则不会执行))
最终通知(在目标对象的方法之后执行(有没有异常都会执行))
13、SpringAOP事务管理
13.1 事务
13.1.1 事务概念
事务是一个原子操作。是一个最小执行单元。可以由一个或多个SQL语句组成,在同一个事务当中,所有的SQL语句都成功执行时,整个事务成功,有一个SQL语句执行失败,整个事务都执行失败。
13.1.2 事务的特性
一个事务中的事务操作,不可再分,要一起成功要么一起失败
一个或多个事务的操作,在操作前后数据是一致的
一个事务的操作不能影响到另外一个事务
一旦事务条件,将会永久保存到数据库中
13.1.3 并发问题
事务并发时的安全问题
问题 | 描述 |
---|---|
脏读 | 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止 |
不可重复读 | 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止 |
幻读 | 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止 |
13.1.4 隔离级别
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
13.1.5 隔离级别的特性
- 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
- 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
13.2 编程式事务管理
技术:Spring+DBUtils、AOP
1)增强类 (负责对方法进行增强,开启事务、提交事务、回滚事务、关闭资源)
2)配置切面(增强+切入点)
注意:要保证这个操作中的Connetion是同一个
13.2.1 事务管理类
public class TXAdvice {
@Autowired
TXUtils txUtils;
public Object around(ProceedingJoinPoint joinPoint){
try {
//开启事务
txUtils.start();
//调用目标对象的方法
Object obj = joinPoint.proceed();
//提交事务
txUtils.commit();
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
//回滚事务
txUtils.rollback();
}finally {
//释放资源
txUtils.close();
}
return null;
}
}
13.2.2 事务管理工具类
//事务工具类
@Component
public class TXUtils {
@Autowired
DataSource dataSource;
ThreadLocal<Connection> tl = new ThreadLocal<>();
public Connection getConnection(){
try {
Connection conn = tl.get();
if(conn == null){
conn = dataSource.getConnection();
//保存ThreadLocal中
tl.set(conn);
}
return conn;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public void start(){
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void commit(){
Connection connection = getConnection();
try {
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void rollback(){
Connection connection = getConnection();
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void close(){
Connection connection = getConnection();
try {
connection.close();
//在ThreadLocal移除Connection对象
tl.remove();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
13.2.3 DBUtils实现的Dao操作
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner qr;
@Autowired
TXUtils txUtils;
@Override
public Account selectAccount(String name) throws SQLException {
return qr.query(txUtils.getConnection(),
"select * from tb_account where name = ?",
new BeanHandler<Account>(Account.class),
name);
}
@Override
public int updateAccount(String name, double money) throws SQLException {
String sql = "update tb_account set money = money + ? where name = ?";
return qr.update(txUtils.getConnection(),
sql,
money,
name);
}
}
13.2.4 业务层转账操作
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public String zhuanzhang(String fromName, String toName, double money)
throws SQLException {
//1、判断用户是否存在
Account account = accountDao.selectAccount(fromName);
if(account == null){
return "用户不存在";
}
//2、判断余额是否足够
if(account.getMoney() < money){
return "余额不足";
}
//3、判断对方账户是否存在
Account toAccount = accountDao.selectAccount(toName);
if(toAccount == null){
return "对方用户不存在";
}
//4、我方扣钱
accountDao.updateAccount(fromName,-money);
System.out.println(10/0);
//5、敌方加钱
accountDao.updateAccount(toName,money);
return "转账成功";
}
}
13.2.5 SpringAOP事务管理配置
<bean id="advice" class="com.qf.advice.TXAdvice"></bean>
<!-- AOP配置-->
<aop:config>
<!-- AOP切面配置-->
<aop:aspect ref="advice">
<!-- AOP切入点配置-->
<aop:pointcut id="pc" expression="execution(* com.qf.service..*.*(..))"/>
<!-- AOP通知配置-->
<aop:around method="around" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
13.2.6 注解式配置
<!-- 开启springAOP的注解扫描 -->
<aop:aspectj-autoproxy/>
@Component
@Aspect
public class TXAdvice { //通知增强
@Autowired
TXUtils txUtils;
@Pointcut("execution(* com.qf.service..*.*(..))")
public void pc(){}
@Around("pc()")
public Object around(ProceedingJoinPoint joinPoint){
try {
//开启事务
txUtils.start();
//调用目标对象的方法
Object obj = joinPoint.proceed();
//提交事务
txUtils.commit();
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
//回滚事务
txUtils.rollback();
}finally {
//释放资源
txUtils.close();
}
return null;
}
}
13.3 声明式事务管理
13.3.1 导入依赖
<!-- Spring声明式事务管理依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.6</version>
</dependency>
<!-- Spring声明式事务织入依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
13.3.2 配置事务管理器
<!-- 配置Spring事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源信息-->
<property name="dataSource" ref="dataSource"/>
</bean>
注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource,否则事务控制失败!!!
13.3.3 配置事务通知(增强)
<!-- 配置Spring事务的增强类-->
<tx:advice id="txAdvice" transaction-manager="transactionManager" >
<!-- 配置Spring事务的增强类的信息-->
<tx:attributes>
<!--
name:Spring:事务增强类增强的方法
isolation:事务的隔离级别
propagation:事务传播行为
REQUIRED(以事务的方式运行,如有事务,加入事务,如无事务,则创建事务) 更新
SUPPORTS(有事务则以事务方式运行,没有事务以非事务方式运行) 查询
read-only:是否只读 查询true 更新false
timeout:超时时间(默认为-1表示永久等待) 设置单位为‘秒’
rollback-for:设置哪些异常回滚
no-rollback-for:设置哪些异常不回滚
增强方法的配置技巧
通配符匹配
get*
select*
find*
query*
* 其他所有方法
-->
<tx:method name="get*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="select*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="find*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="query*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" read-only="false" timeout="-1"/>
</tx:attributes>
</tx:advice>
13.3.3.1 属性说明
isolation
隔离级别
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
propagation
事务传播行为
名称 | 作用 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
当涉及到事务嵌套(Service调用Service)时,可以设置:
- SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
- REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
readonly
读写性
- true:只读,可提高查询效率。(适合查询)
- false:可读可写。 (默认值)(适合增删改)
timeout
事务超时时间
当前事务所需操作的数据被其他事务占用,则等待。
- 1000:自定义等待时间1000(秒)。
- -1:由数据库指定等待时间,默认值。(建议)
rollbackFor
配置回滚的异常
noRollbackFor
配置不回滚的异常
13.3.4 配置事务切面
<!-- 配置Spring切面-->
<aop:config>
<!-- 配置Spring切入点-->
<aop:pointcut id="pc" expression="execution(* com.qf.service..*.*(..))"/>
<!-- 配置Spring增强类-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
13.3.5 基于注解配置
<!-- Spring声明式事务基于注解的配置-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源信息-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启Spring事务的包扫描 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在service层的方法或者类上添加注解
注解加在类上表示所有的方法都使用事务管理
注解加在方法上仅表示当前方法使用事务管理
@Transactional(isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED,readOnly = false,timeout = -1)