【学自尚硅谷BV1Vf4y127N5,有转载内容,侵删】
第一章 IOC控制反转
一、IOC容器原理
1.1 什么是 IOC
(1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
(2)使用 IOC 目的:为了耦合度降低
(3)做入门案例就是 IOC 实现
1.2 IOC 底层原理 (1)xml 解析、工厂模式、反射
1.3 画图讲解 IOC 底层原理
-
IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
-
Spring 提供 IOC 容器实现两种方式:(两个接口)
-
BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用 (加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象 )
-
ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用 (加载配置文件时候就会把在配置文件对象进行创建)
-
-
ApplicationContext 接口有实现类
二、IOC-Bean管理-XML方式
引言
1、什么是 Bean 管理
Bean 管理指的是两个操作
Spring 创建对象
Spirng 注入属性
2、Bean 管理操作有两种方式
基于 xml 配置文件方式实现
基于注解方式实现
2.1 XML-创建对象和set注入属性
-
创建对象
-
在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
-
在 bean 标签有很多属性,介绍常用的属性
-
id 属性:唯一标识
-
class 属性:类全路径(包类路径)
-
-
-
创建对象时候,默认也是执行无参数构造方法完成对象创建
-
注入属性
2.2 XML-有参构造,set方法注入属性
2.3 XML-p名称空间注入属性
2.4 XML-注入空值和特殊符号
- 空值
<property name="address">
<null/>
</property>
- 特殊符号
<property name="address">
<value><![CDATA[<<南京>>]]></value>
</property>
2.5 XML-注入外部bean
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="userService" class="com.YS.service.UserService">
<property name="userDAO" ref="userDAO"></property>
</bean>
<!--外部bean-->
<bean name="userDAO" class="com.YS.dao.impl.UserDAOImpl"/>
</beans>
UserService类:
package com.YS.service;
import com.YS.dao.UserDAO;
import com.YS.dao.impl.UserDAOImpl;
public class UserService {
UserDAO userDAO;
public void setUserDAO(UserDAOImpl userDAO) {
this.userDAO = userDAO;
}
public void test(){
System.out.println("调用userservice");
userDAO.addUser();
}
}
UserDAO类及其Impl类:
package com.YS.dao;
public interface UserDAO {
public void addUser();
}
----------------------------------------------我是分割线------------------------------------------------------
package com.YS.dao.impl;
import com.YS.dao.UserDAO;
public class UserDAOImpl implements UserDAO {
public void addUser() {
System.out.println("调用addUser" );
}
}
2.6 XML-注入内部bean和级联赋值
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="emp" class="com.YS.spring5.onevsmuch.Emp">
<!--注入两个普通属性-->
<property name="emp_name" value="杨硕"/>
<property name="age" value="20"/>
<!--注入内部bean-->
<property name="dept"> <!--需要Emp中的set方法-->
<bean class="com.YS.spring5.onevsmuch.Dept">
<property name="dept_name" value="财务部"/> <!--需要Dept中的set方法-->
</bean>
</property>
--------------------------------我是分割线,此代码粘贴会报错,有相同的属性名字------------------------------- <!--第一种级联赋值的方法-->
<property name="dept" ref="dept"/>
<property name="dept.dept_name" value="发财部门"/> <!--需要Emp中的get方法,并且赋值与代码顺序有关-->
</bean>
<!--第二种级联赋值的方法,与外部bean基本相同,多了一步给属性赋值-->
<property name="dept" ref="dept"></property>
<bean id="dept" class="com.YS.spring5.onevsmuch.Dept">
<property name="dept_name" value="没钱部门"/> <!--由于上方代码已经赋值,此处代码无效-->
</bean>
</beans>
Emp类:
package com.YS.spring5.onevsmuch;
public class Emp {
private String emp_name;
private int age;
Dept dept;
public Dept getDept() {
return dept;
}
public void setEmp_name(String emp_name) {
this.emp_name = emp_name;
}
public void setAge(int age) {
this.age = age;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void test(){
System.out.println("age:" + age + '\n' + "name:" +emp_name + '\n' +"dept:" + dept);
}
}
Dept类:
package com.YS.spring5.onevsmuch;
public class Dept {
private String dept_name;
public void setDept_name(String dept_name) {
this.dept_name = dept_name;
}
@Override
public String toString() {
return "Dept{" +
"dept_name='" + dept_name + '\'' +
'}';
}
}
2.7 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 name="stu" class="com.YS.spring5.collectiontype.Stu">
<!--演示如何将基本数据类型数据放入数组当中-->
<property name="course">
<!--此标签用array和list均可-->
<array>
<value>语文</value>
<value>数学</value>
<value>英语</value>
</array>
</property>
<!--演示如何将基本数据类型数据放入list当中-->
<property name="list">
<array>
<value>hello</value>
<value>hi</value>
</array>
</property>
<!--演示如何将基本数据类型数据放入map当中-->
<property name="map">
<!--由于map有k,v两个值,所以注意差别-->
<map>
<entry key="1" value="192103101"></entry>
<entry key="2" value="192103102"></entry>
</map>
</property>
<!--演示如何将基本数据类型数据放入setlist当中-->
<property name="set">
<set>
<value>mysql</value>
<value>redis</value>
</set>
</property>
<!--演示如何将对象放入list当中-->
<property name="courseList">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>
<bean id="course1" class="com.YS.spring5.collectiontype.Course">
<property name="name" value="spring5框架"/>
</bean>
<bean id="course2" class="com.YS.spring5.collectiontype.Course">
<property name="name" value="mybatis框架"/>
</bean>
</beans>
Course类(此类用于演示如何将对象注入到集合中):
package com.YS.spring5.collectiontype;
public class Course {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Course{" +
"name='" + name + '\'' +
'}';
}
}
Stu类:
package com.YS.spring5.collectiontype;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Stu {
private String[] course;
private List<String> list;
private Map<String,String> map;
private Set<String> set;
List<Course> courseList;
public void setCourse(String[] course) {
this.course = course;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public void test(){
System.out.println(Arrays.toString(course));//数组
System.out.println(list);//list
System.out.println(map);//map
System.out.println(set);//set
System.out.println(courseList);//注入对象
}
}
测试类:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}
2.8 FactoryBean
-
Spring 有两种类型 bean,一种普通 bean,另外一种工厂bean(FactoryBean)
-
普通bean:在配置文件中定义 bean 类型就是返回类型
-
工厂bean:在配置文件定义bean 类型可以和返回类型不一样
-
第一步创建类,让这个类作为工厂 bean,实现接口 FactoryBean
-
第二步 实现接口里面的方法,在实现的方法中定义返回的bean类型
-
例子
-
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="com.YS.spring5.beanfactory.MyBean">
</bean>
</beans>
FactoryBean的实现类(MyBean)
package com.YS.spring5.beanfactory;
import com.YS.spring5.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;
public class MyBean implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setName("JAVA");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
test方法
@Test
public void test2() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("bean03.xml");
//注意对象和bean的对应关系
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
2.9 bean作用域
- 在 Spring 里面,默认情况下,bean 是单实例对象
-
可以修改bean标签的scope属性
-
singleton 默认值,单实例
-
prototype 多实例
-
request
-
session
-
- prototype和singleton加载时的区别
- 设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象(饿汉式)
- 设置 scope 值是 prototype时候,加载 spring 配置文件时候不创建对象,在调用 getBean 方法时候创建多实例对象(懒汉式)
2.10 bean生命周期
-
生命周期:从兑现创建到对象销毁的过程
-
通过无参构造创建bean实例
-
为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
-
调用 bean 的初始化的方法(需要进行配置初始化的方法)
-
bean 可以使用了(对象获取到了)
-
当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
-
xml: <bean id="demo" class="com.YS.spring5.bean.demo" init-method="init" destroy-method="close"> <property name="name" value="测试"/> </bean> ----------------------------------------------我是分割线----------------------------------------------- test: public class testBean { @Test public void test(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean04.xml"); context.getBean("demo", demo.class); System.out.println("第四步,获取创建bean实例对象"); context.close(); } } demo: package com.YS.spring5.bean; public class demo { private String name; public demo(){ System.out.println("第一步,通过无参构造创建bean对象"); } public void setName(String name) { this.name = name; System.out.println("第二步,通过set方法设置属性值"); } public void init(){ System.out.println("第三步,执行初始化方法"); } public void close(){ System.out.println("第五步,销毁bean对象"); } }
-
-
添加后置处理器的效果
-
xml <bean id="demo" class="com.YS.spring5.bean.demo" init-method="init" destroy-method="close"> <property name="name" value="测试"/> </bean> <!--若添加了后置处理器,对所有bean标签都生效--> <bean id="MyBeanPost" class="com.YS.spring5.bean.MyBeanPost"/> --------------------------------------------我是分割线---------------------------------------------------- public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("before"); return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("after"); return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); } }
-
生命周期变为:
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization )
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization )
- bean 可以使用了(对象获取到了)
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
2.11 XML自动装配
- 根据属性名称自动注入
xml:
<bean id="emp" class="com.YS.spring5.autowire.Emp" autowire="byName">
<property name="name" value="ys"/>
</bean>
<bean id="dept" class="com.YS.spring5.autowire.Dept"/>
Emp:
public class Emp {
private String name;
Dept dept;
public void setName(String name) {
this.name = name;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", dept=" + dept +
'}';
}
}
Dept:
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
- 根据属性类型自动注入
<bean id="emp" class="com.YS.spring5.autowire.Emp" autowire="byName">
—————> <bean id="emp" class="com.YS.spring5.autowire.Emp" autowire="byType">
2.12 外部properties
- 直接配置数据库信息
- 配置德鲁伊连接池
- 引入德鲁伊连接池依赖 jar 包
//maven pom.xml中添加依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
//xml:直接配置连接池
<bean id="dataSourse" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/userDb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
-
引入外部properties文件配置数据库连接池
-
创建外部属性文件,properties 格式文件,写数据库信息
-
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.username=root prop.password=root
-
-
把外部 properties 属性文件引入到 spring 配置文件中
-
<!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置数据库连接池--> <bean id="dataSourse" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"/> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.username}"/> <property name="password" value="${prop.driverClass}"/> </bean>
-
-
三、IOC-Bean管理-注解方式
3.1 注解基础操作
-
什么是注解
- 注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化 xml 配置
-
Spring 针对 Bean 管理中创建对象提供注解
- @Component
- @Service
- @Controller
- @Repository
- *上面四个注解功能是一样的,都可以用来创建 bean 实例
-
基于注解方式实现对象创建步骤
-
引入依赖(采用maven)
-
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.16</version> </dependency>
-
-
开启组件扫描
-
如果扫描多个包,多个包使用逗号隔开
-
<context:component-scan base-package="com.YS.spring5.service,com.YS.spring5.dao"/>
-
扫描包上层目录
-
<context:component-scan base-package="com.YS.spring5.service"/>
-
-
创建类,在类上面添加创建对象注解
-
@Controller(value = "userService") public class UserService { public void add(){ System.out.println("添加"); } }
-
在注解里面 value 属性值可以省略不写, 默认值是首字母小写的类名称
-
-
-
细节配置
-
示例 1
-
// use-default-filters="false" --表示现在不使用默认 filter,自己配置 filter // context:include-filter --设置扫描哪些内容 // type="annotation" --扫描注解类型(controller类型注解) <context:component-scan base-package="com.atguigu" use-defaultfilters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
示例2
-
//下面配置扫描包与示例1相反 // context:exclude-filter --设置哪些内容不进行扫描 <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
3.2 属性注入
-
@Autowired:根据属性类型进行注入
-
//bean: <context:component-scan base-package="com.YS.spring5"/> //interface: public interface UserDAO { public void add(); } //impl: //注意要在实现类添加注解 @Repository public class UserDAOImpl implements UserDAO { @Override public void add() { System.out.println("UserDAOImpl add"); } } //Service: //不需要添加set方法 @Service public class UserService { // @Autowired private UserDAO userDAO; public void add(){ System.out.println("UserService add"); userDAO.add(); } }
-
-
Qualifier
-
根据属性名称进行注入
-
//Impl: @Repository(value = "userDAOImpl1")//避免一个接口有多个实现类,仅仅依靠Repository找不到具体的实现类,所以添加value值 public class UserDAOImpl implements UserDAO { @Override public void add() { System.out.println("UserDAOImpl add"); } } //Service: @Service @Qualifier(value = "userDAOImpl1") public class UserService { @Autowired private UserDAO userDAO; public void add(){ System.out.println("UserService add"); userDAO.add(); } }
-
-
Resourse:根据属性类型、名称进行注入
-
根据属性类型
-
//impl @Repository //先创建对象 public class UserDAOImpl implements UserDAO { @Override public void add() { System.out.println("UserDAOImpl add"); } } //Service @Service //先创建对象 public class UserService { @Resource //在属性上添加注解 private UserDAO userDAO; public void add(){ System.out.println("UserService add"); userDAO.add(); } }
-
根据名称
-
//Impl @Repository(value = "userDAOImpl2")//避免一个接口有多个实现类,仅仅依靠Repository找不到具体的实现类,所以添加value值 public class UserDAOImpl implements UserDAO { @Override public void add() { System.out.println("UserDAOImpl add"); } } //Service @Service public class UserService { @Resource(name = "userDAOImpl2") //注意是name = "" , 不是 value = "" private UserDAO userDAO; public void add(){ System.out.println("UserService add"); userDAO.add(); } }
-
-
@Value
-
注入普通类型属性
-
@Value(value = "ys0316") private String name;
-
3.3 完全注解开发
-
创建配置类,代替配置文件
-
//配置类 @Configuration @ComponentScan(basePackages = {"com.YS.spring5"}) public class Config { }
-
-
编写测试类
-
//测试类中的测试方法 public void test02(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); UserService userService = context.getBean("userService", UserService.class); userService.add(); }
-
第二章 AOP面向切面编程
一、AOP原理
1.1 AOP概念
-
什么是 AOP
-
面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
-
-
使用登录例子说明 AOP
1.2 动态代理底层原理
补课地址:https://www.bilibili.com/video/BV1Kb411W75N?p=664
package com.YS.spring5.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//被代理类的接口
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperHuman implements Human{
@Override
public String getBelief() {
return "我会飞";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
//生产代理类的工厂
class ProxyFactory{
//调用此方法,返回一个代理类的对象
public static Object getProxyObject(Object obj){//传入一个被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler); //通过handler实现代理类对象调用方法时调用invoke方法
}
}
class MyInvocationHandler implements InvocationHandler{
private Object object; //Object为被代理类对象的类型
public void bind(Object obj){ //获取被代理类的对象
this.object = obj;
}
//如果通过代理类的对象调用方法时,会自动调用invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理类对象调用的方法,同时也是被代理类对象要调用的方法
//object为被代理类对象
Object returnValue = method.invoke(object,args);
return returnValue;
}
}
public class example {
public static void main(String[] args) {
SuperHuman superHuman = new SuperHuman();//实例化要被代理的类
//Object proxyObject = ProxyFactory.getProxyObject(superHuman);通用
Human proxyObject =(Human) ProxyFactory.getProxyObject(superHuman); //本例
proxyObject.eat("鸡腿");//当代理类调用方法时,会自动的调用被代理类的方法
System.out.println(proxyObject.getBelief());
}
}
-
有接口情况,使用jdk动态代理
- 创建接口实现类代理对象,增强类的方法
-
没有借口情况,使用CGLIB动态代理
- 创建子类的代理对象,增强类的方法
1.3 使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
//实体类
public class UserDAOImpl implements UserDAO {
@Override
public int add(int a, int b) {
System.out.println(a + b);
return a + b;
}
@Override
public void update(String id) {
System.out.println("新的ID为:" + id);
}
}
//
class proxyFactory{
public static Object getProxyObject(Object obj){
myInvocationHandler myInvocationHandler = new myInvocationHandler();
myInvocationHandler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),myInvocationHandler);
}
}
class myInvocationHandler implements InvocationHandler{
private Object object;
public void bind(Object o){
this.object = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前");
Object renturnValue = method.invoke(object,args);
System.out.println("方法之后");
return renturnValue;
}
}
public class JdkProxy {
public static void main(String[] args) {
UserDAO userDAO = new UserDAOImpl();
UserDAO proxyObject = (UserDAO) proxyFactory.getProxyObject(userDAO);
proxyObject.add(2, 3);
proxyObject.update("yangshuo");
}
}
二、AOP操作
2.1 操作术语
-
连接点:类中哪些方法可以被增强,这些方法称为连接点
-
切入点:真正被增强的方法,称为切入点
-
通知(增强):实际增强的逻辑部分
- 通知有多种类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
- 通知有多种类型:
-
切面:指把通知应用到切入点的过程(是一个动作)
2.2 操作准备
-
Spring框架一般都是基于AspectJ实现AOP操作
- AspectJ不是Spring的操作部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
-
基于AspectJ实现AOP操作
- 基于XML方式实现
- 基于注解方式实现
-
在项目工程里面引入AOP相关的依赖
-
maven: <dependency> <groupId>org.example</groupId> <artifactId>demo01</artifactId> <version>1.0-SNAPSHOT</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.9.5</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.0</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
-
切入点表达式
-
作用:知道对哪个类里面的哪个方法进行增强
-
语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
-
举例:
- 对com.YS.dao.BookDao里面的add方法进行增强:execution(* com.YS.dao.BookDao.add(…))
- 对com.YS.dao.BookDao里面所有方法进行增强:execution(* com.YS.dao.BookDao.*(…))
- 对com.YS.dao包里面所有类、类里所有方法进行增强:execution(* com.YS.dao..(…))
-
2.3 操作
-
创建被增强的类,在类内定义方法
-
//被增强的类 @Component public class User { public void add(){ System.out.println("我是被增强类的方法"); } }
-
-
创建增强类
-
//增强类 @Component @Aspect //生成代理对象 public class UserProxy { @Before(value = "execution(* com.YS.spring5.aop.User.add(..))") public void before(){ System.out.println("前置增强"); } }
-
-
进行通知的设置
-
在xml文件中开启注解扫描
-
<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.YS.spring5.aop"></context:component-scan> <aop:aspectj-autoproxy/> </beans>
-
-
使用注解创建增强类和被增强类的对象
-
在增强类前添加注解:@Aspect
-
在xml文件中开启生成代理对象
-
<aop:aspectj-autoproxy/>
-
-
-
配置不同类型的通知
-
前置通知
-
@Before(value = "execution(* com.YS.spring5.aop.User.add(..))") public void before(){ System.out.println("前置增强"); }
-
-
后置通知
-
//后置 @AfterReturning(value = "execution(* com.YS.spring5.aop.User.add(..))") public void after_note(){ System.out.println("后置通知"); }
-
-
最终通知
-
//最终 @After(value = "execution(* com.YS.spring5.aop.User.add(..))") public void final_note(){ System.out.println("最终通知"); }
-
-
异常通知
-
//异常 @AfterThrowing(value = "execution(* com.YS.spring5.aop.User.add(..))") public void ret_exception_note(){ System.out.println("异常通知"); }
-
-
环绕通知
-
//环绕 @Around(value = "execution(* com.YS.spring5.aop.User.add(..))") public void ground_note(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("前环绕通知"); proceedingJoinPoint.proceed();//用于执行被增强的方法 System.out.println("后环绕通知"); }
-
-
-
注意:@Around拥有优先执行权 ,@Before、@After无论如何有没异常都会执行(执行顺序与spring版本有关:在spring5.2.7后 通知优先级别从高到低 为@Around @Before @After @AfterReturning @AfterThrowing)
-
相同切入点的抽取
-
//切入点抽取 @Pointcut(value = "execution(* com.YS.spring5.aop.User.add(..))") public void cut(){}
-
-
设置增强类的优先级
-
@Component @Aspect //生成代理对象 @Order(1) //设置优先级,数字越小优先级越高 public class UserProxy { //切入点抽取 @Pointcut(value = "execution(* com.YS.spring5.aop.User.add(..))") public void cut(){} //前置 @Before(value = "cut()") public void before_note(){ System.out.println("UserProxyBefore"); } } @Component @Aspect @Order(0) public class PersonProxy { @Before(value = "execution(* com.YS.spring5.aop.User.add(..))") public void before_note(){ System.out.println("PersonProxyBefore"); } }
-
第三章 事务操作
一、事务概念及环境
1.1事务概念
- 什么是事务
- 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
- 典型场景:银行转账
- 事务的四个特性
- 原子性 - atomicity:一个事务的所有操作要么全部成功,要么全部失败,否则会回滚(Rollback)
- 一致性 - consistency:事务提交前后的数据完整性和状态保持一致
- 隔离性 - isolation:多事务操作之间不会产生影响,允许多个并发事务同时对其数据进行读写和修改的能力
- 持久性 - durability:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
1.2 搭建事务操作环境
-
创建数据库表,添加记录
-
创建service,搭建dao,完成对象创建和注入关系
-
// @Service public class UserService { @Autowired private UserDao userDao; public void accountMoney(){ userDao.send(); userDao.recive(); } } // public interface UserDao{ public void send(); public void recive(); } // @Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; @Override public void send() { String sql = "update t_account set money = money - ? where id = ?"; jdbcTemplate.update(sql,100,1); } @Override public void recive() { String sql = "update t_account set money = money + ? where id = ?"; jdbcTemplate.update(sql,100,2); } }
-
二、Spring事务管理
2.1 介绍
-
事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
-
在 Spring 进行事务管理操作
- 有两种方式:编程式事务管理和声明式事务管理(使用)
-
声明式事务管理
- 基于注解方式(使用)
- 基于 xml 配置文件方式
-
在 Spring 进行声明式事务管理,底层使用 AOP 原理
-
Spring事务管理api:
- 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
2.2 注解声明事务管理
-
在 spring 配置文件配置事务管理器
-
<!--创建事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
-
-
在 spring 配置文件,开启事务注解
-
<!--引入名称空间tx--> <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" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"/>
-
-
在 service 类上面(或者 service 类里面方法上面)添加事务注解
- Transactional,这个注解添加到类上面,也可以添加方法上面
- 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
- 如果把这个注解添加方法上面,为这个方法添加事务
2.2 声明式事务管理参数配置
- 在service类上添加注解@Transactional,在这个注解里面可以配置事务相关参数
(1) propagation:传播行为
-
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
-
伪代码说明(同时作为案例):
-
ServiceA { void methodA() { ServiceB.methodB(); } } @Transaction(Propagation=XXX) ServiceB { void methodB() { } } //代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。
-
-
七种事务传播行为:
传播行为 解释 PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务 PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行 PROPAGATION_MANDATORY 支持当前事务,假设当前没有事务,就抛出异常 PROPAGATION_REQUIRES_NEW 新建事务,假设当前存在事务。把当前事务挂起 PROPAGATION_NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 PROPAGATION_NEVER 以非事务方式运行,假设当前存在事务,则抛出异常 PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 -
PROPAGATION_REQUIRED
- 假如当前正要运行的事务不在另外一个事务里,那么就起一个新的事务,比方说,ServiceB.methodB的事务级别定义PROPAGATION_REQUIRED, 那么因为执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。这时调用ServiceB.methodB,ServiceB.methodB看到自己已经执行在ServiceA.methodA的事务内部。就不再起新的事务。而假如ServiceA.methodA执行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的不论什么地方出现异常。事务都会被回滚。即使ServiceB.methodB的事务已经被提交,可是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
-
PROPAGATION_SUPPORTS
- 假设当前在事务中。即以事务的形式执行。假设当前不在一个事务中,那么就以非事务的形式执行
-
PROPAGATION_MANDATORY
- 必须在一个事务中执行。也就是说,他仅仅能被一个父事务调用。否则,他就要抛出异常
-
PROPAGATION_REQUIRES_NEW
- 比方我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW。那么当运行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起。ServiceB.methodB会起一个新的事务。等待ServiceB.methodB的事务完毕以后,他才继续运行。
他与PROPAGATION_REQUIRED 的事务差别在于事务的回滚程度了。由于ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。假设ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚。ServiceB.methodB是不会回滚的。假设ServiceB.methodB失败回滚,假设他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
- 比方我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW。那么当运行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起。ServiceB.methodB会起一个新的事务。等待ServiceB.methodB的事务完毕以后,他才继续运行。
-
PROPAGATION_NOT_SUPPORTED
- 当前不支持事务。比方ServiceA.methodA的事务级别是PROPAGATION_REQUIRED 。而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时。ServiceA.methodA的事务挂起。而他以非事务的状态执行完,再继续ServiceA.methodA的事务。
-
PROPAGATION_NEVER
- 不能在事务中执行。如果ServiceA.methodA的事务级别是PROPAGATION_REQUIRED。 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。
-
PROPAGATION_NESTED
- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
(2) ioslation:隔离级别
-
脏读
- 如果一个事务A对数据进行了更改,但是还没有提交,而另一个事务B就可以读到事务A尚未提交的更新结果。这样,当事务A进行回滚时,那么事务B开始读到的数据就是一笔脏数据。
-
不可重复读
- 同一个事务在事务过程中,对同一个数据进行读取操作,读取到的结果不同。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据
-
幻(虚)读
- 幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。 幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
-
事务的隔离级别
-
脏读 不可重复读 幻读 READ UNCOMMITTED 有 有 有 READ COMMITTED 无 有 有 REPEATABLE READ 无 无 有 SERIALIZABLE 无 无 无 -
mysql默认隔离级别:REPEATABLE READ
-
(3) timeout:超时时间
- 事务需要在一定时间内进行提交,否则将进行回滚
- 默认值为-1(永不超时),设置时间以秒为单位
(4) readonly:是否只读
- 读:查询
- 写:增删改
- 默认值为false,即可以进行增删改查操作
- 如果修改为true,只可以进行查询操作
(5) rollbackFor:回滚
- 设置出现哪些异常进行事务的回滚
(6) norollbackFor:不回滚
- 设置出现哪些异常不进行事务的回滚
2.3 完全注解开发
@Configuration //配置类
@ComponentScan(basePackages = "com.YS") //组件扫描
@EnableTransactionManagement //开启事务
public class configuration {
@Bean
//创建数据库连接池
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建jdbctemple对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
//到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManger(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}