一 Spring 框架概述
1.1 Spring 概述
轻量级开源的 JavaEE 框架。解决企业开发应用的复杂性。
Spring的核心部分:
- IOC:控制反转(Inversion of Controll),将创建对象的过程交由 Spring 管理
- AOP:面向切面编程(Aspect Orient Programming),不修改源代码的情况进行功能增强
特点:
- 解耦、简化开发
- Aop 编程支持
- 方便程序测试
- 方便集成框架
- 方便进行事物的操作
- 降低 API 开发难度
1.2 入门案例
版本:5.2.6
依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
demo01:创建普通类
public class User {
public void add(){
System.out.println("add...");
}
}
在 resources
目录下创建bean1.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="user" class="com.pyd.User"> </bean>
</beans>
进行测试代码编写:
@Test
public void testAdd() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
// 获取id为user的类
User user = context.getBean("user", User.class);
System.out.println(user);
// 调用方法
user.add();
}
getBean方法的Bean是什么?
官方的解释是 Spring Bean是事物处理组件类和实体类(POJO)对象的总称,是能够被实例化、能够被spring容器管理的java对象。可以把bean看做是一个组件,这个组件用来具体实现某个业务功能。总结性的讲,Bean就是由IOC容器初始化、装配及管理的对象,除此之外,和程序中的其他对象没有区别。简单来说,就是Java对象,但是能够被Spring容器管理。
二 IOC
2.1 IOC 概述
控制反转:交给Spring管理
控制反转:
- 面向对象的设计原则,降低耦合度
- 将对象创建和之间的调用过程,交给 Spring 管理
底层原理
底层原理:
- xml 解析、工厂模式、反射
- IOC 的思想基于 IOC 容器完成,IOC 的容器底层就是对象工厂
IOC 的过程
IOC 的过程:
- XML 配置文件,配置创建的对象
- service 和 dao 类,创建工厂类
通过 xml 文件获取类的路径
通过反射创建对象
Spring提供的IOC接口
- Spring 提供 IOC 容器的两种方式:
- BeanFactory:
- IOC 容器的基本实现,是 Spring 内部的使用接口,不允许开发人员进行实现
- 加载配置文件时,不创建对象,使用时创建对象
- ApplicationContext:
- BeanFactory 的子接口,面向开发人员使用
- 加载配置文件时,创建对象
二者区别:是否在加载配置文件时,创建对象。
ApplicationContext的实现类
ApplicationContext 的实现类
绝对路径和类路径的区别
2.2 IOC 操作 Bean 管理
Bean 管理:
- Spring 创建
- Spring 注入属性
Bean 管理的两种方式:
- 基于 xml 配置文件
- 基于注解的实现
创建对象
使用 bean 标签,在标签中添加属性,创建对象,常用属性:
- id:唯一标识
- class 属性:类全路径
- name:可以加特殊符号
创建对象时,默认执行无参方法
注入属性
DI:依赖注入(Dependency Injection),是IOC的另一种表述方式,即组件以一些预先定义好的方式(如:getter方法)来接收来自容器的资源注入,即注入属性
2.2.1 方法一:使用 set 方法实现对Bean的属性注入
- 创建属性,创建属性对应的 set 方法。
public class Book {
String author;
String title;
public void setAuthor(String author) {
this.author = author;
}
public void setTitle(String title) {
this.title = title;
}
}
- 在 spring 配置文件中,配置属性的注入
<bean id="book" class="com.pyd.Book">
<!-- 完成属性的注入 -->
<property name="author" value="张浩瀚"></property>
<property name="title" value="武汉理工优秀学生"></property>
</bean>
使用 p 名称空间注入(需要有 set 方法):
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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"
>
<!-- p名称空间注入,需要有set方法 -->
<bean
id="book"
class="com.pyd.Book"
p:author="张浩瀚"
p:title="武汉理工优秀学生"
>
</bean
></beans>
2.2.2 方法二:有参数的构造注入
public class Order {
String name;
String id;
public Order(String name, String id) {
this.name = name;
this.id = id;
}
}
在 xml 中配置:
<bean id="order" class="com.pyd.Order">
<constructor-arg index="0" value="电脑" />
<constructor-arg name="id" value="1" />
</bean>
2.3 IOC 设置属性其他类型
2.3.1 字面量
- null 值
<property name="address">
<null/>
</property>
- 属性值包含特殊符号
转义:
<property name="address" value="<>">
</property>
CDATA:
<property>
<value> <![CDATA[<<>>]]> </value>
</property>
2.3.2 注入属性:外部 bean
- 创建 service 和 dao
public interface UserDao {
public void update();
}
public class UserDaoImp implements UserDao {
@Override
public void update() {
System.out.println("userDaoImp update() called");
}
}
- serivce 调用 dao 的方法
public class User {
private UserDao userDao;
public void setUserDao(UserDaoImp userDao) {
this.userDao = userDao;
}
public void test() {
this.userDao.update();
}
}
- 在 xml 中实现注入外部 bean
<bean id="userSvc" class="com.pyd.service.User">
<property name="userDao" ref="userDaoImp" />
</bean>
<bean id="userDaoImp" class="com.pyd.dao.UserDaoImp" />
2.3.3 注入属性:内部 bean 和级联赋值
-
一对多的关系:部门和员工
一个部门有多个员工,一个员工属于一个部门。 -
创建 emp 和 dept 类
public class Dept {
String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "dept{" +
"name='" + name + '\'' +
'}';
}
}
public class Emp {
String name;
String gender;
Dept dept;
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
- 编写 xml
方式一:
<bean id="emp" class="com.pyd.bean.Emp">
<property name="name" value="张浩瀚" />
<property name="gender" value="男" />
<property name="dept">
<bean id="dept" class="com.pyd.bean.Dept">
<property name="name" value="帅哥部" />
</bean>
</property>
</bean>
方式二:级联赋值
<bean id="emp" class="com.pyd.bean.Emp">
<property name="name" value="张浩瀚" />
<property name="gender" value="男" />
<property name="dept" ref="dept"> </property>
</bean>
<bean id="dept" class="com.pyd.bean.Dept">
<property name="name" value="帅哥部" />
</bean>
需要 get 方法的写法:
<bean id="emp" class="com.pyd.bean.Emp">
<property name="name" value="张浩瀚" />
<property name="gender" value="男" />
<property name="dept" ref="dept" />
<!--必须有getDept()方法,否则得不到该对象-->
<property name="dept.name" value="大帅哥部" />
</bean>
<bean id="dept" class="com.pyd.bean.Dept"/>
2.4 xml 注入集合属性
集合类型属性的创建:
public class Stu {
private String[] courses;
private List<String> list;
private Map<String, String> map;
private Set<String> set;
public void setSet(Set<String> set) {
this.set = set;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
}
2.4.1 数组类型属性
<property name="courses">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
2.4.2 List 集合类型属性
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
2.4.3 Map 集合类型属性
<property name="map">
<map>
<entry key="zhh" value="天下第一帅" />
</map>
</property>
2.4.4 在集合中设置对象类型的值
<bean id="course1" class="com.pyd.bean.Course">
<property name="name" value="张浩瀚帅哥入门课上" />
<property name="id" value="1" />
</bean>
<bean id="course2" class="com.pyd.bean.Course">
<property name="name" value="张浩瀚帅哥入门课下" />
<property name="id" value="2" />
</bean>
<property name="courseList">
<list>
<ref bean="course1" />
<ref bean="course2" />
</list>
</property>
2.4.5 将集合注入部分提取出来
- 在 spring 配置文件中引入名称空间 util
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
- 提取 list 集合属性注入
<util:list id="bookList">
<value>book1</value>
<value>book2</value>
</util:list>
<bean name="book" class="com.pyd.bean.Book">
<property name="books" ref="bookList" />
</bean>
2.5 IOC 操作 Bean 管理(FactoryBean)
- 普通 bean:定义的类型和返回类型相同
- 工厂 bean:定义的类型和返回类型可以不相同
- 创建类,作为工厂 bean,实现接口 FactoryBean
public class MyBean implements FactoryBean {
}
- 实现接口的方法,在方法中定义返回的 Bean 类型
@Override
public Object getObject() throws Exception {
Course course = new Course();
course.setName("张浩瀚");
return course;
}
- 配置文件 xml
<bean id="myBean" class="com.pyd.factoryBean.MyBean"></bean>
2.6 IOC 操作 Bean 管理(bean 的作用域)
2.6.1 单实例和多实例
创建的对象,默认是单实例。(比较地址是否相同)
设置多实例对象:
scope 属性:
- singleton:默认值,加载配置文件时就会创建,在每个 Spring ioc 容器中只有一个实例。
- prototype:多实例对象,在调用 getBean 方法时创建多实例对象
- request:将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期
- session:将单个 bean 定义的作用域限定为 HTTP 会话的生命周期。
- application:将单个 bean 定义的作用域限定为 ServletContext 的生命周期。
- websocket:将单个 bean 定义的作用域限定为 WebSocket 的生命周期。
<bean id="myBean" class="com.pyd.factoryBean.MyBean" scope="prototype"></bean>
2.7 IOC 操作 Bean 管理(bean 生命周期)
从对象创建到销毁的过程
2.7.1 bean 声明周期
- 通过构造器创建 bean 的实例(无参数构造)
- 为 bean 的属性设置值和其他 bean 引用(调用 set 方法)
- 调用 bean 的初始化方法(需要配置初始化方法)
- 可以使用 bean,获取到了对象
- 容器关闭时,调用 bean 的销毁方法(需要配置销毁的方法)
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
context.close();
1. 无参构造
2. 设置name
3. 执行初始化方法
4. 获取到了bean对象
5. 销毁对象方法执行
后置处理器:
- 通过构造器创建 bean 的实例(无参数构造)
- 为 bean 的属性设置值和其他 bean 引用(调用 set 方法)
- 把 bean 实例传递给 bean 后置处理器 (postProcessBeforeInitialization)
- 调用 bean 的初始化方法(需要配置初始化方法)
- 把 bean 实例传递给 bean 后置处理器 (postProcessAfterInitialization)
- 可以使用 bean,获取到了对象
- 容器关闭时,调用 bean 的销毁方法(需要配置销毁的方法)
<bean name="myPostBean" class="com.pyd.factoryBean.MyBeanPost"></bean>
无参构造
设置name
bean后置处理器 postProcessBeforeInitialization
执行初始化方法
bean后置处理器 postProcessAfterInitialization
获取到了bean对象
销毁对象方法执行
2.7 IOC 操作 Bean 管理(xml 自动装配)
实际工作中,用得很少
自动装配:根据装配规则(属性名称或类型),Spring 自动将匹配的属性值注入。
autowire 属性:
- byName: 根据属性名称注入,bean 的 id 和属性值相同
- byType: 根据属性类型注入,相同类型的不能定义多个
2.8 IOC 操作 Bean 管理(外部属性文件)
2.8.1 直接配置数据库信息
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/fruitdb" />
<property name="username" value="root" />
<property name="password" value="zhanghaohanzuishuai" />
</bean>
2.8.2 引入外部文件配置数据库连接池
数据库文件jdbc.property
:
username=root
password=zhanghaohanzuishuai
url=jdbc:mysql://localhost:3306/fruitdb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
driverClass=com.mysql.cj.jdbc.Driver
引入 context 名称空间:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
>
</beans>
引入外部属性文件:
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClass}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
2.9 IOC 操作 Bean 管理(基于注解方式)
依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
2.9.1 注解
- 特殊的代码标记格式
- 可以作用于类、方法、属性
- 简化 XML 配置
2.9.2 Spring 针对 Bean 管理中创建对象提供的注解
- Component
- Service
- Controller
- Repositary
功能相同
2.9.3 实现对象的创建
- 开启组件扫描
扫描多个包:用逗号分隔
扫描包上层目录
<context:component-scan base-package="com.pyd.aop"></context:component-scan>
- 创建类,并添加注解
注解的 value 默认是类的名称,首字母小写。
@Component("userService")
public class UserService {
public void add() {
System.out.println("user add");
}
}
- 组件扫描的细节
<!--示例 1
use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
context:include-filter ,设置扫描哪些内容
-->
<context:component-scan base-package="com.atguigu" use-default- filters="false">
<context:include-filter
type="annotation"
expression="org.springframework.stereotype.Controller"
/>
</context:component-scan>
<!--示例 2
下面配置扫描包所有内容
context:exclude-filter: 设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.atguigu">
<context:exclude-filter
type="annotation"
expression="org.springframework.stereotype.Controller"
/>
</context:component-scan>
Spring 针对 Bean 中属性的注入提供的注解
- Autowried:根据属性类型自动装配
- Qualifier:根据属性名称自动注入
- Value: 注入普通类型
2.9.3 根据类型实现属性注入
- 创建 service 和 dao 对象,并添加创建对象的注解
接口对象:
public interface UserDao {
public void print();
}
接口实现类:
@Repository
public class UserDaoImp implements UserDao {
@Override
public void print() {
System.out.println("userDaoImp print");
}
}
- 在 service 定义 dao 类型属性,并加上注解(不需要添加 set 方法)
@Component("userService")
public class UserService {
@Autowired
private UserDao userDao;
public void add() {
System.out.println("user add");
userDao.print();
}
}
2.9.4 根据属性名称进行注入
场景:一个接口有多个实现类
需要和 Autowired 一起使用
@Repository("userDaoImp1")
public class UserDaoImp1 implements UserDao {
@Override
public void print() {
System.out.println("userDaoImp1 print");
}
}
根据名称进行注入:
@Autowired
@Qualifier("userDaoImp1")
private UserDao userDao;
2.9.5 根据类型注入或根据名称注入
Resource:根据类型注入或根据名称注入(javax 包中)
根据类型注入:
@Resource
根据名称注入:
@Resource("userService")
2.9.6 注入普通属性
@Value(value = "zhh")
public String name;
2.9.7 完全注解开发
不使用配置文件,完全使用注解的方式。
- 创建配置类,代替配置文件
@Configuration:作为配置类
@ComponentScan: 需要扫描的包路径
@Configuration
@ComponentScan(basePackages = {"com.pyd.aop"})
public class SpringConfig {
}
- 加载配置类
AnnotationConfigApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
三、AOP
3.1 概念
- 面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 通过不修改源码的方式,增加新的功能
基于 Java 的主要 AOP 实现有:AspectJ Spring AOP JBoss AOP
3.2 基本原理
3.2.1 动态代理
有两种情况的动态代理:
(1)有接口:使用 JDK 动态代理
创建 UserDao 接口实现类代理对象,增强类的方法
(2)无接口:使用 CGLIB 动态代理
创建子类。
CGLIB:创建当前类子类的代理对象
3.2.2 JDK 动态代理
使用 Proxy 类的方法创建类的代理对象。
调用 newInstance 方法:
- 参数 1:类加载器
- 参数 2:增强方法锁在类实现的接口(支持多个)
- 参数 3:实现接口 InvcationHandler,创建代理对象,写增强方法。
- 创建接口,定义方法
public interface UserDao {
public int add(int a, int b);
public String update(int id);
}
- 创建实现类,实现方法
public class UserDaoImp implements UserDao {
@Override
public int add(int a, int b) {
System.out.println(a + b);
return 0;
}
@Override
public String update(int id) {
return null;
}
}
- 使用 proxy 类实现增强
public class JDKProxy {
public static void main(String[] args) {
UserDaoImp userDaoImp = new UserDaoImp();
Class[] interfaces = {UserDao.class};
new UserDaoImp();
UserDao proxy = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoImp));
proxy.add(10, 20);
}
}
class UserDaoProxy implements InvocationHandler {
public Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行之前");
System.out.println("method.getName() = " + method.getName());
// 增强方法执行
Object res = method.invoke(obj, args);
System.out.println("方法执行之后");
return res;
}
}
3.3 术语
-
连接点:类中可以被增强的方法
-
切入点:实际真正被增强的方法
-
通知(增强):实际增强的逻辑部分。
通知有多种类型:- 前置
- 后置
- 环绕
- 异常
- 最终
-
切面:将通知应用到切入点的过程。
3.4 准备工作
导入相关 Jar 包:
<!-- AOP相关Jar -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib -->
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<version>2.2.0</version>
</dependency>
3.5 基于 AspectJ 实现 AOP 操作
不是 Spring 的组成部分,独立的 AOP 框架,一般将 AspectJ 和 Spring 框架一起使用。
- 基于 xml 配置文件实现
- 基于注解方式实现
3.5.1 切入点表达式
作用:知道对哪个类和哪个方法来增强。
语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
例:
-
举例 1:对 com.micah.dao.BookDao 类里面的 add 进行增强
execution(\* com.atguigu.dao.BookDao.add(..))
-
举例 2:对 com.micah.dao.BookDao 类里面的所有的方法进行增强
execution(_ com.atguigu.dao.BookDao._ (..))
-
举例 3:对 com.atguigu.dao 包里面所有类,类里面的所有的方法进行增强
execution(_ com.micah.dao._.\* (..))
3.5.2 AspectJ 注解
- 创建类
public class User {
public void add() {
System.out.println("user add");
}
}
- 创建增强类,编写增强逻辑
创建不同方法,代表不同的通知类型
public class UserProxy {
public void before() {
System.out.println("前置通知");
}
}
-
进行通知的配置
(1)在 spring 配置文件中,开启注解扫描<?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" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <!-- 开启注解扫描 --> <context:component-scan base-package="com.pyd.aop" ></context:component-scan> <!-- 开启Aspect生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
(2)使用注解创建 User 和 UserProxy 对象
@Component
(3)在增强类上添加注解 Aspect
@Aspect
(4)开启 ApsectJ 生成代理对象
-
配置不同类型的通知
添加通知的注解,填写切入点表达式:
@Component
@Aspect
public class UserProxy {
@Before(value = "execution(* com.pyd.aop.annotation.User.add(..))")
public void before() {
System.out.println("before 前置通知");
}
@AfterReturning(value = "execution(* com.pyd.aop.annotation.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning 后置通知(返回通知)");
}
@After(value = "execution(* com.pyd.aop.annotation.User.add(..))")
public void after() {
System.out.println("after 最终通知");
}
@AfterThrowing(value = "execution(* com.pyd.aop.annotation.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing 异常通知");
}
@Around(value = "execution(* com.pyd.aop.annotation.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知前");
proceedingJoinPoint.proceed();
System.out.println("环绕通知后");
}
}
- 相同切入点抽取
Pointcut
@Pointcut(value = "execution(* com.pyd.aop.annotation.User.add(..))")
public void pointDemo() {
}
@Before(value = "pointDemo()")
public void before() {
System.out.println("before 前置通知");
}
- 设置增强类的优先级
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高:
@Component
@Aspect
@Order(1)
public class PersonProxy
在 spring 配置文件中配置切入点:
<aop:config>
<!--切入点-->
<aop:pointcut
id="p"
expression="execution(*
com.atguigu.spring5.aopxml.Book.buy(..))"
/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p" />
</aop:aspect>
</aop:config>
3.5.3 AspectJ 完全注解开发
- 创建配置类
@Configuration
@ComponentScan(basePackages = {"com.pyd.annotation"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
class SpringAspectConfig {
}
四、JdbcTemplate
4.1 概述
Spring 框架中提供的一个对象,是对原始 JDBC API 对象的简单封装。
其他操作模板类:
- 操作关系型数据的:JdbcTemplate 和 HibernateTemplate。
- 操作 nosql 数据库的:RedisTemplate。
- 操作消息队列的:JmsTemplate。
4.2 需要的依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.2</version>
</dependency>
4.3 准备工具
4.3.1 Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
>
</beans>
创建数据库连接池:
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClass}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
创建 JdbcTemplate 对象:
<bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入datasource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
开启组件扫描:
<context:component-scan base-package="com.atguigu"></context:component-scan>
4.3.2 创建 service 和 dao 类,在 dao 注入 jdbcTmeplate 对象
public interface BookDao {}
注入配置文件中的 JdbcTemplate 对象
@Repository
public class BookDaoImp implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Service
public class BookService {
@Autowired
private BookDao bookDao;
}
4.4 操作数据库
数据库:
SELECT * FROM t_fruit;
CREATE DATABASE dbtest;
USE dbtest;
CREATE TABLE book(
id int,
name VARCHAR(128),
status BOOLEAN
);
4.4.1 添加
- 对应数据库创建实体类
public class Book {
private int id;
private String name;
private boolean status;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setStatus(boolean status) {
this.status = status;
}
}
- 在 dao 进行数据库添加操作
@Repository
public class BookDaoImp implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(Book book) {
String sql = "insert into book values(?,?,?);";
Object[] args = {book.getId(), book.getName(), book.isStatus()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
}
@Service
public class BookService {
@Autowired
private BookDaoImp bookDao;
public void add(Book book) {
bookDao.add(book);
}
}
4.4.2 查询—返回某个值
查询表有多少条记录
@Override
public int count() {
String sql = "select count(*) from book";
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
4.4.3 查询—返回对象
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
参数 2:RowMapper,接口,返回不同类型的数据
查询图书的详细信息
@Override
public Book getBook(int id) {
String sql = "select * from book where id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
}
4.4.4 查询—返回集合
查询图书列表:
@Override
public List<Book> getBooksList() {
String sql = "select * from book";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
}
4.4.5 批量添加操作
betchUpdate
@Override
public void batchAdd(List<Object[]> books) {
String sql = "insert into book values (?,?,?);";
jdbcTemplate.batchUpdate(sql, books);
}
List<Object[]> books = new ArrayList<>();
Object[] book1 = {3, "zhanghh", false};
Object[] book2 = {4, "zhangh", false};
Object[] book3 = {5, "zhanhh", false};
books.add(book1);
books.add(book2);
books.add(book3);
bookService.batchAdd(books);
4.5 事务
4.5.1 基本概念
数据库操作的基本单元,逻辑上一组的操作,要么都成功,要么都失败。
四个特性(ACID):
- 原子性
- 一致性
- 隔离性
- 持久性
4.5.2 环境搭建
- 数据库建表
CREATE TABLE user
(
id INT,
name VARCHAR(128),
money INT
);
INSERT INTO user VALUES (1,'张浩瀚',200),(2,'章帅',300);
- 创建 service,搭建 dao,完成对象创建和注入关系
dao:
public interface UserDao {
public void add(int money);
public void reduce(int money);
}
@Repository
public class UserDaoImp implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void add(int money) {
System.out.println("id=1 : -" + money);
String sql = "update user set money-? where id=?";
jdbcTemplate.update(sql, money, 1);
}
@Override
public void reduce(int money) {
System.out.println("id=2 : +" + money);
String sql = "update user set money+? where id=?";
jdbcTemplate.update(sql, money, 2);
}
}
service:
@Service
public class UserService {
@Autowired
private UserDaoImp userDao;
public void transfer() {
userDao.add(100);
userDao.reduce(100);
}
}
- 模拟异常
int i = 10/0;
4.5.3 事务操作过程
事务操作过程:
-
开启事务操作
-
捕获异常
-
出现异常,回滚
4.5.4 Spring 事务
-
将事务加到 Service 层上
-
在 spring 进行事务管理操作:编程式事务管理和声明式事务管理
-
在 Spring 进行声明式事务管理,底层使用 AOP 原理
-
相关 API
接口:事务管理器,该接口针对不同框架提供不同实现类
4.5.5 注解方式实现声明式事务管理
- 在 Spring 配置文件中配置事务管理器
<bean
id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<!--注入数据源-->
<property name="dataSource" ref="dataSource" />
</bean>
- 开启事务注解
引入名称空间:tx
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>
开启事务注解:
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
- 在 service 类上添加事务注解
Transactional:
- 加在类上:当前类的所有方法添加事务
- 加在方法:当前方法添加事务
4.5.6 声明式事务管理参数配置
当一个事务的方法被另一个事务方法调用时,该事务方法如何进行
事务传播行为:
传播行为 | 描述 |
---|---|
PROPAGATION_REQUIRED | Spring 默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行 |
PROPAGATION_REQUES_NEW | 该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可 |
PROPAGATION_SUPPORT | 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务 |
PROPAGATION_NEVER | 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务 |
PROPAGATION_NOT_SUPPORT | 该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码 |
PROPAGATION_MANDATORY | 与 NEVER 相反,如果外层没有事务,则抛出异常 |
PROPAGATION_NESTED | 该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。 |
传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
事务隔离级别 isolation
多事务之间不会产生影响。
不考虑隔离性会出现三个读问题:
- 脏读:一个未提交的事务读取到了另一个未提交事务的数据
- 不可重复读: 一个未提交事务读到了另一个已提交事务的修改数据
- 虚读(幻读):一个未提交的事务读到了新提交的数据
通过设置隔离级别来解决读问题。
脏读 | 不可重复读 | 虚读 | |
---|---|---|---|
READ UNCOMMIT 读未提交 | 有 | 有 | 有 |
READ COMMITTED 读已提交 | 无 | 有 | 有 |
REPEATABLE READ 可重复读 (MySQL 默认) | 无 | 无 | 有 |
SERIALIZABLE 串行化 | 无 | 无 | 无 |
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
超时时间 timeout
设置事务需要在一定时间内进行提交,超时则回滚。
默认值为-1,单位为秒。
@Transactional(timeout = 5)
readOnly 是否只读
读:查询操作
写:添加修改删除操作
默认值为 false,可读写
设置为 true,只能查询
rollbackFor 回滚
设置出现哪些异常需要回滚
noRollbackFor 是否只读
设置出现哪些异常不需要回滚
4.5.6 XML 实现声明式事务管理
- 配置事务管理器
<!-- 创建事务管理器-->
<bean
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
id="dataSourceTransactionManager"
>
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 配置通知(事务)
<!-- 创建通知-->
<tx:advice id="txadvice">
<tx:attributes>
<!-- 指定在哪种规则的方法上添加事务-->
<tx:method name="transfer*" propagation="REQUIRES_NEW" />
</tx:attributes>
</tx:advice>
- 配置切入点和切面
<aop:config>
<!--切入点配置-->
<aop:pointcut
id="px"
expression="execution(* com.pyd.jdbctemplate.serivce.UserService.*(..))"
/>
<!--切面配置-->
<aop:advisor advice-ref="txadvice" pointcut-ref="px"></aop:advisor>
</aop:config>
4.5.7 完全注解实现声明式事务管理
- 创建配置类,使用配置类替代 xml
@Configuration
@ComponentScan(basePackages = {"com.pyd.jdbctemplate"})
@EnableTransactionManagement // 开启事务
public class TxConfig {
// 创建数据库的连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/dbtest");
druidDataSource.setPassword("");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return druidDataSource;
}
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入datasource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
Spring 5 框架新功能
5.1 概述
-
常规升级:
- 对 JDK 9 运行时兼容性
- 在 Spring Framework 代码中使用 JDK 8 特性
- 响应式编程支持
- 函数式 Web 框架
- Jigsaw 的 Java 模块化
- 对 Kotlin 支持
- 舍弃的特性
-
基于 Java8 兼容 Java9
- 核心 Spring 接口中的 Java 8 static 方法
- 基于 Java 8 反射增强的内部代码改进
- 在框架代码中使用函数式编程——lambdas 表达式和 stream 流
-
支持响应式编程,需要使用框架
- Reactive Streams:尝试定义与语言无关的响应性 API。
- Reactor:Spring Pivotal 团队提供的响应式编程的 Java 实现。
- Spring WebFlux:启用基于响应式编程的 Web 应用程序的开发。 提供类似于 Spring MVC 的编程模型。
5.2 日志框架
自带日志封装
(1) Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
(2) Spring5 框架整合 Log4j2
- 导入 jar 包
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.2</version>
</dependency>
- 创建 log4j2.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--日志级别以及优先级排序: OFF> FATAL> ERROR> WARN> INFO> DEBUG> TRACE> ALL-->
<!--Configuration后面的 status用于设置 log4j2自身内部的信息输出,可以不设置, 当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="DEBUG">
<!--先定义所有的 appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"
/>
</console>
</appenders>
<!--然后定义 logger,只有定义 logger并引入的 appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>
- 手动输出
public class Log {
private static final Logger log = LoggerFactory.getLogger(Log.class);
public static void main(String[] args) {
log.info("hello log4j");
log.warn("hello log4j");
}
}
5.3 核心容器
5.3.1 支持 Nullable 注解
Nullable:
- 用于方法:方法的返回值可以为空
- 用于方法的参数:参数可以为空
- 用于属性:属性值可以为空
5.3.2 支持函数式风格 GenericApplication Context
GenericApplicationContext context = new GenericApplicationContext();
// 调用context对象进行注册,先清空
context.refresh();
context.registerBean(User.class, () -> new User());
context.registerBean("user1", User.class, () -> new User());
// 获取对象 全路径
User user = (User) context.getBean("com.pyd.User");
// 获取对象:指定名称
User user1 = (User) context.getBean("user1");
5.4 支持 JUnit5
- 整合 JUnit4
@RunWith(SpringJUnit4ClassRunner.class)
//单元测试框架
@ContextConfiguration("classpath:bean1.xml")
//加载配置文件
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
- 整合 JUnit5
需要引入 JUnit5 的 Jar 包
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
- 复合注解
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5{
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
5.4 SpringWebFlux
5.4.1 概述
是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。
使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,WebFlux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet 3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。