文章目录
Spring
本内容配套代码Gitee仓库链接:https://gitee.com/allureyu/spring_hx.git
第一章 Spring概述
1.Spring是轻量级的开源的J2EE框架
2.Spring可以解决企业应用开发的复杂性
3.Spring有两个核心部分:IOC和AOP
(1)IOC:控制反转,把创建对象的过程交给Spring容器进行管理
(2)AOP:面向切面,不修改原有代码进行功能增强
4.Spring框架的特点
(1)方便解耦,简化开发
(2)AOP编程支持
(3)方便程序测试
(4)方便和其他框架进行整合
(5)方便进行事务的操作
(6)降低API的开发难度(封装了JDBC)
第二章 IOC容器
2.1 IOC的底层原理
-
什么是IOC:
控制反转:inversion of control
把对象的创建和对象之间的调用都交给spring管理
使用IOC的目的,为了降低耦合度
入门案例就是IOC的实现 -
IOC的底层原理:
底层技术:XML解析技术,设计模式,反射
见图过程
2.2 IOC接口-BeanFactory
IOC思想基于IOC容器实现,IOC容器底层就是对象工厂
Spring提供IOC容器实现两种方式-两个接口
-
BeanFactory:
-
概念:是IOC容器的基本实现,是Spring内部的接口,一般不提供给开发人员使用
-
特点:在加载配置文件的时候,不会创建对象,在获取对象或者使用对象的时候才会创建
-
-
ApplicationContext:
- 概念:是BeanFactory的子接口,提供了更多更强大的功能,一般开发人员使用
- 特点:在加载配置文件的时候就会配置文件中的对象进行创建
2.3 IOC操作bean管理
什么是bean管理
1、bean管理指的是两个操作:Spring创建对象和Spring注入属性
2、Spring创建对象两种方式:基于xml配置文件和基于注解方式
第三章 XML方式操作bean
3.1 基于xml方式—创建对象
在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象的创建
bean标签中的常用属性:
id属性:唯一标识,表示给对象其标识或者别名
class属性:类的全路径(包类路径)
创建对象的时候,默认使用的是无参构造
<bean id="user" class="cn.diautowried.User"></bean>
3.2 基于xml方式—注入属性
DI:依赖注入,就是注入属性
- 方式1:使用set方法进行属性注入
在spring配置文件中配置对象创建,配置属性注入
创建User对象,set方法进行属性注入:如下
property:表示属性的标签
name:表示实体类中的属性名
value:表示为属性名赋值
<?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">
<!--xml方式配置bean,DI注入的两种方式-->
<bean id="user" class="com.hx.pojo.User" scope="prototype">
<!--DI注入:set注入,给对象的属性赋值-->
<property name="name" value="tom"/>
<property name="pwd" value="123"/>
</bean>
</beans>
- 方式2:使用有参构造进行属性注入
在spring配置文件中进行配置
使用有参构造注入属性:
constructor-arg:构造-参数
name:表示实体类中的属性名
value:表示为属性名赋值
<?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">
<!--xml方式配置bean,DI注入的两种方式-->
<bean id="user1" class="com.hx.pojo.User" scope="singleton">
<!--DI注入:构造注入,给对象的属性赋值-->
<constructor-arg name="name" value="Anny"/>
<constructor-arg name="pwd" value="111"/>
</bean>
</beans>
3.3 基于xml方式—注入外部bean属性
1 创建Dept和 Emp 对象
2 将Dept注入到Emp中
name属性值:类中的属性的名称
ref属性值:是Dept对象的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-->
<!--配置Emp的bean对象-->
<bean id="emp1" class="com.hx.pojo.Emp">
<property name="eno" value="1001"/>
<property name="ename" value="乌拉"/>
<property name="dept" ref="dept1"/>
</bean>
<!--外部bean:配置Dept的bean对象-->
<bean id="dept1" class="com.hx.pojo.Dept">
<property name="dno" value="10"/>
<property name="dname" value="瓦格纳"/>
</bean>
</beans>
3.4 基于xml方式—注入内部bean属性
1、创建Dept类 和 Emp类,建立一对多关系,
2、将Dept对象注入到Emp对象中
<?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-->
<!--配置Emp的bean对象-->
<bean id="emp1" class="com.hx.pojo.Emp">
<property name="eno" value="1001"/>
<property name="ename" value="乌拉"/>
<!--内部bean-->
<property name="dept">
<bean id="dept1" class="com.hx.pojo.Dept">
<property name="dno" value="20"/>
<property name="dname" value="华约"/>
</bean>
</property>
</bean>
</beans>
3.5 基于xml方式—注入集合属性
分别注入以下内容:
数组、List、Map、Set
设置实体类:Student
package com.hx.pojo;
import lombok.Data;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Data
public class Student {
//设置属性
//数组
private String[] courses;
//List集合
public List<String> list;
//Map集合
private Map<String, String> map;
//Set集合
private Set<String> set;
//课程的List集合
private List<Course> courseList;
}
配置spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress ALL -->
<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-->
<!--创建Student的bean-->
<bean id="stu" class="com.hx.pojo.Student">
<!--DI注入数组-->
<property name="courses">
<array>
<value>语文</value>
<value>数学</value>
<value>外语</value>
</array>
</property>
<!--DI注入List集合-->
<property name="list">
<list>
<value>跳舞</value>
<value>唱歌</value>
<value>弹奏</value>
</list>
</property>
<!--DI注入Map集合-->
<property name="map">
<map>
<entry key="01" value="A"/>
<entry key="02" value="B"/>
<entry key="03" value="C"/>
</map>
</property>
<!--DI注入Set集合-->
<property name="set">
<set>
<value>篮球</value>
<value>排球</value>
<value>足球</value>
</set>
</property>
<!--DI注入List中的Course-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
<ref bean="course3"></ref>
</list>
</property>
</bean>
<!--创建课程Course的bean对象-->
<bean id="course1" class="com.hx.pojo.Course">
<property name="cno" value="101"/>
<property name="cname" value="物理"/>
</bean>
<bean id="course2" class="com.hx.pojo.Course">
<property name="cno" value="102"/>
<property name="cname" value="化学"/>
</bean>
<bean id="course3" class="com.hx.pojo.Course">
<property name="cno" value="103"/>
<property name="cname" value="生物"/>
</bean>
</beans>
3.6 基于xml方式—使用FactoryBean
1 Spring有两种类型的bean
一种是普通bean
一种是工厂bean,也就是FactoryBean
2 普通bean:在spring配置文件中定义bean的类型,返回类型就是此类型
工厂bean:在spring配置文件中定义bean的类型,可以返回其他类
3、工厂bean案例
步骤1:创建类,设置这个类为工厂bean。实现接口FactoryBean
步骤2:实现接口中的方法,在此方法中定义返回的bean的类型
public class MyBean implements FactoryBean<Course> {
//定义返回的bean对象是其他对象
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCourseName("社会与科学");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="myBean" class="cn.factorybean.MyBean"></bean>
测试bean的返回类型是其他类型--FactoryBean.
应该返回MyBean类型的对象,实际返回时Course类型对象
@Test
public void testMyBean(){
//1、创建Spring的核心容器,并加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("spring4.xml");
//2、获取对象
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
7 返回Course类型对象
3.7 基于xml方式—bean的作用域
在spring中,设置创建bean实例是单实例还是多实例
在默认情况下,spring容器创建的对象是单实例
如何设置单实例和多实例:
在spring配置文件中bean标签里有属性scope,用于设置单实例和多实例
第一个值:默认值singleton,表示的是单实例对象
第二个值:设置protoType。表示的是多实例对象
<!--测试作用域 单实例 和 多实例-->
<bean id="user1" class="cn.pojo.User" scope="singleton">
<property name="username" value="张三"></property>
</bean>
<bean id="user2" class="cn.pojo.User" scope="prototype">
<property name="username" value="张三"></property>
</bean>
- singleton和protoType的区别:
- singleton是单实例对象,设置scope的属性值是singleton,
单实例对象会在spring配置文件加载的时候就创建 - prototy是多实例对象,设置scope的属性值是prototype,
不是在加载spring配置文件的时候创建对象,
而是在调用getBean方法的时候创建多实例对象
- singleton是单实例对象,设置scope的属性值是singleton,
3.8 基于xml方式—bean的生命周期
1、生命周期:从对象创建到对象销毁的过程
2、bean的生命周期—简化五步
(1)、通过构造器创建bean实例,也就是执行无参构造
(2)、为bean的属性设置值,以及对其他bean的调用(调用set方法)
(3)、调用bean的初始化方法,(初始化的方法需要进行配置)
(4)、bean可以使用,对象可以获取
(5)、当容器在关闭的时候,会调用bean的销毁方法(销毁的方法需要进行配置)
- 案例
package com.hx.pojo;
/*
bean的生命周期
*/
public class Phone {
//设置成员属性
private String brand;
//1、实例化bean
public Phone() {
System.out.println("第一步:调用无参构造,实例化bean");
}
//2、给bean的属性赋值
public void setBrand(String brand) {
this.brand = brand;
System.out.println("第二步:给bean的属性赋值");
}
//3、执行bean的初始化操作
public void initBean(){
System.out.println("第三步:执行bean的初始化操作");
}
//4、调用bean的方法
public void call(){
System.out.println("第四步:调用bean的方法正常工作");
}
//5、销毁bean
public void destoryBean(){
System.out.println("第五步:销毁bean");
}
}
在配置spring.xml文件中配置如下信息:
init-method="initBean" :配置初始化方法
destroy-method="destoryBean" :配置销毁方法
<bean id="phone" class="com.hx.pojo.Phone" init-method="initBean" destroy-method="destoryBean">
<property name="brand" value="华为"/>
</bean>
- 测试
@Test
public void m6(){
//1、获取IOC控制反转容器
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring06.xml");
//2、通过容器获取对象
Phone phone = context.getBean("phone", Phone.class);
//3、调用bean的方法
phone.call();
//4、关闭IOC容器
context.close();
}
- 结果
3.9 基于xml方式—自动装配
1、什么是自动装配?什么是手动装配
- 手动装配:
是程序员在配置文件中手动的进行属性的赋值,即value属性值和ref属性值 - 自动装配:
根据指定的装配规则,根据属性名称或者属性类型,Spring自动将匹配的属性值进行注入
2 案例
-
将Dept的对象注入到Emp中
-
spring.xml配置根据名称和根据类型注入
- 自动装配根据名称注入:保证注入的bean的id名和实体类中的属性名保持一致。如果不一致,注入失败
- 自动装配根据类型注入: 只能有一个该类型的bean对象,不能出现多个,不然容器不知道选择哪一个bean对象进行注入操作
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress ALL -->
<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名和实体类中的属性名保持一致
如果不一致,注入失败
-->
<bean id="emp" class="com.hx.pojo.Emp" autowire="byName">
<property name="eno" value="1001"/>
<property name="ename" value="刘能"/>
</bean>
<bean id="dept" class="com.hx.pojo.Dept">
<property name="dno" value="10"/>
<property name="dname" value="人事部"/>
</bean>
<!--自动装备方式二:根据类型注入
注意点:根据类型进行自动装配,只能有一个该类型的bean对象
不能出现多个,不然容器不知道选择哪一个bean对象进行注入操作
-->
<bean id="emp1" class="com.hx.pojo.Emp" autowire="byType">
<property name="eno" value="1002"/>
<property name="ename" value="赵四"/>
</bean>
</beans>
- 测试
@Test
public void m7(){
//1、获取IOC控制反转容器
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring07.xml");
//2、通过容器获取对象
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
@Test
public void m8(){
//1、获取IOC控制反转容器
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring07.xml");
//2、通过容器获取对象
Emp emp = context.getBean("emp1", Emp.class);
System.out.println(emp);
}
第四章 注解方式操作bean
4.1 常用创建bean对象的注解
1、什么是注解
注解是代码的特殊标记:
格式:@注解名(属性名=属性值,属性名=属性值)
使用注解:注解作用在类上面,方法上面,属性上面
使用注解的目的:简化xml设置
2、Spring针对Bean管理中创建对象提供注解
@Component:笔试普通的bean
@Service:表示业务层
@Controller:表示控制层
@Repository:表示持久层
以上四个注解功能是一样的,都可以用来创建bean实例。注解中的value值可以自定义,相当于bean标签中的id属性值。value值可以省略不写,默认值是类名称的首字母小写
4.2 基于注解方式–实现对象创建
1 创建类,在类上添加创建对象的注解
@Service(value = "userService")
public class UserService {
public void add(){
System.out.println("service add...");
}
}
2 配置spring.xml
- 1 开启组件扫描:
1、如果扫描多个包,多个包之间使用逗号隔开
2、扫描包的上层目录
<context:component-scan base-package="cn.anno"></context:component-scan>
- 2 设置组件扫描-设置包含哪些注解
use-default-filters=“false” : 表示不使用默认的filter,自行配置filter
context:include-filter : 设置扫描哪些内容
属性:type:annotation
属性:expression:只扫描对应表达式的注解
<context:component-scan base-package="cn.anno" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
- 3 设置组件扫描-设置排除哪些注解
默认扫描所有注解,可以排除execlude扫描指定注解
context:exclude-filter :设置不扫描哪些内容
属性:type:annotation
属性:expression:不扫描对应表达式的注解
<context:component-scan base-package="cn.anno">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
3、完整的配置文件(主要添加context约束)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描,扫描的是类上面Spring的注解-->
<context:component-scan base-package="com.hx">
<!--过滤指定的注解可以创建bean对象-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
<!--排除指定的注解不允许创建bean对象-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
4.3 基于注解方式–实现属性注入
- 1、@AutoWired :根据属性类型进行注入
@AutoWired :根据属性类型进行注入、按照类型(byType)装配依赖对象,
1、使用@Autowired,默认required = true。即表示被注入bean对象可以被实例化的
2、如果被注入的bean对象不能被实例化,即为null。需要设置@Autowired的required = false
- 2、@AutoWired:根据属性名称进行注入
1、在@AutoWired注解通过类型注入的前提下:如果被注入的接口有多个实现类对象,那么就可以按照名称(byName)来装配,可以结合@Qualifier 注解一起使用。
2、如果不使用@Qualifier注解进行value属性的标识,那么容器会为此接口的多个实现类创建bean对象。此时,容器不知道改注入哪个实现类的bean对象,会报如下异常
NoUniqueBeanDefinitionException:
No qualifying bean of type 'cn.anno.dao.UserDao'available:
expected single matching bean but found 3:userDaoImpl1,userDaoImpl2,userDaoImpl3
- .3、@Resource: 可以根据类型注入,可以根据名称注入
@Resource: 可以根据类型注入,可以根据名称注入
@Resource不是spring的注解,而是javax的注解。
@Resource默认按照byName自动注入
@Resource 有两个重要的属性:name和type
Spring将@Resource注解的:name属性解析为bean的名字,type属性则解析为bean的类型。
如果使用 name 属性,则使用 byName 的自动注入策略,
如果使用 type 属性时则使用 byType 自动注入策略。
如果既不制定 name 也不制定 type 属性,这时将通过反射机制使用 byName 自动注入策略。
- 4、@Value 注入普通类型属性
@Value注解:
注入普通类型属性,为普通属性赋值。属性value的值,会赋值给name
@Value(value = “Tom”)
private String name;
4.4 基于注解方式–实现完全注解开发
创建SpringConfig配置类,替代spring.xml文件
@Configuration:注解:表示标识此类是配置类
@ComponentScan(“cn.spring”)注解:表示开启注解扫描。括号中的值,表示开启扫描此包下的所有注解
@Configuration//作为配置类。替代xml核心配置文件
@ComponentScan("cn.spring")
public class SpringConfig {}
@Test
public void test1(){
//1、创建核心容器,加载配置类
AnnotationConfigApplicationContext ccontex =
new AnnotationConfigApplicationContext(SpringConfig.class);
//2、通过容器获取bean对象
UserService userService = ccontex.getBean("userService", UserService.class);
//3、调用方法
userService.add();
System.out.println(userService);
}
第五章 面向切面编程AOP
5.1 AOP概念
官方:面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率
通俗:不修改源代码的方式,在主干功能里添加新的功能
5.2 AOP原理
1、AOP底层使用动态代理,有两种情况的动态代理
第一种:有接口情况–使用JDK动态代理。创建接口实现类的代理对象,增强类的方法
第二种:没有接口情况–使用CGLIB动态代理。创建子类的代理对象,增强类的方法
JDK动态代理
CGLIB动态代理
2 JDK动态代理和CGLIB动态代理的区别
-
1、概念:
JDK代理:使用的是反射机制生成一个实现代理接口的匿名类,
在调用具体方法前调用InvokeHandler来处理。
CGLIB代理:使用字节码处理框架asm,
对代理对象类的class文件加载进来,
通过修改字节码生成子类。 -
2、效率:
JDK创建代理对象效率较高,执行效率较低;
CGLIB创建代理对象效率较低,执行效率高。 -
3、关系:
JDK动态代理机制是委托机制,只能对实现接口的类生成代理,
通过反射动态实现接口类;
CGLIB则使用的继承机制,针对类实现代理,
被代理类和代理类是继承关系,
所以代理类是可以赋值给被代理类的,
因为是继承机制,不能代理final修饰的类。 -
4、实现:
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
1.实现InvocationHandler接口,重写invoke()
2.使用Proxy.newProxyInstance()产生代理对象
3.被代理的对象必须要实现接口
CGLIB 必须依赖于CGLIB的类库,需要满足以下要求:
1.实现MethodInterceptor接口,重写intercept()
2.使用Enhancer对象.create()产生代理对象 -
5、使用场景:
1)如果目标对象实现了接口,
默认情况下会采用JDK的动态代理实现AOP,
可以强制使用CGLIB实现AOP
2)如果目标对象没有实现了接口,
必须采用CGLIB库,
spring会自动在JDK动态代理和CGLIB之间转换
5.3 AOP相关术语
- 连接点:类里面的那些方法可以增强,这些方法就被成为连接点
- 切入点:实际被增强的方法,称为切入点
- 通知(增强):实际增强的逻辑部分,被称为通知,有五种类型的通知
- 切面:把通知应用到切入点的过程,就是切面
通知类型 | 执行点 |
---|---|
前置通知 | 在目标方法执行之前执行 |
后置通知 | 在目标方法执行之后执行 |
环绕通知 | 在目标方法执行之前后执行 |
异常通知 | 在目标方法发生异常时执行 |
返回通知 | 在目标方法执行返回值时执行 |
5.4 AOP操作准备
- 1、Spring和AspectJ的关系
1、Spring框架一般基于AspectJ实现AOP操作
2、AspectJ不是Spring的组成部分,独立的AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
- 2、引入AOP依赖
<!--spring-AOP依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--Aspect切面依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
- 3、切入点表达式
切入点表达式的作用:知道对哪个类里面的哪个方法进行增强
切入点表达式语法:
execution([权限修饰符][返回类型][类的路径][方法名称][参数列表])
举例1:对cn.aop.hx包中的UserDao类里面的add方法做增强
execution(* cn.aop.hx.UserDao.add(..))
举例2:对cn.aop.hx包中的UserDao类里面的所有方法做增强
execution(* cn.aop.hx.UserDao. * (..))
举例3:对cn.aop.hx包中的所有类,以及里面的所有方法做增强
execution(* cn.aop.hx. * . * (..))
5.5 基于@Aspect注解的操作AOP
- 1、创建目标类
package com.hx.strong;
import org.springframework.stereotype.Service;
//目标类
@Service
public class UserServiceImpl {
//定义成员方法---目标方法
public String checkData(){
System.out.println("我是目标方法,即切入点!");
String s = new String("检查通过!");
return s;
}
public void add(){
System.out.println("目标方法发生异常!");
int i = 1/0;
}
}
- 2、创建切面类–通知所在的类
package com.hx.strong;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//可以被组件扫描
@Component
//表示此类是切面类
@Aspect
//开启生成代理对象的配置
@EnableAspectJAutoProxy
@Order(value = 2)
public class UserServiceAspect1 {
//提取相同的切入点表达式
//承载方法
@Pointcut(value = "execution(* com.hx.strong.UserServiceImpl.checkData(..))")
public void pointCut(){
}
//后置通知 After 细粒度:扫描指定包类下的所有方法
@After(value = "pointCut()")
public void after(){
System.out.println("后置通知:在目标方法执行之后执行");
}
//返回通知 AfterReturning
@AfterReturning( value = "pointCut()")
public void AfterReturning(){
System.out.println("返回通知:在目标方法返回结果前时执行");
}
//异常通知 AfterThrowing
@AfterThrowing(value = "execution(* com.hx.strong.UserServiceImpl.add(..))")
public void afterThrowing(){
System.out.println("异常通知:在目标方法执行出现异常时执行!");
}
//环绕通知 Around
@Around(value = "pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
//pj:封装了目标方法
//执行前增强功能
System.out.println("环绕通知前:目标方法执行前增强功能");
//目标方法执行
Object result = jp.proceed();
System.out.println(result);
//执行后增强功能
System.out.println("环绕通知后:目标方法执行后增强功能");
}
}
package com.hx.strong;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//可以被组件扫描
@Component
//表示此类是切面类
@Aspect
//开启生成代理对象的配置
@EnableAspectJAutoProxy
@Order(value = 1)
public class UserServiceAspect2 {
//提取相同的切入点表达式
//承载方法
@Pointcut(value = "execution(* com.hx.strong.UserServiceImpl.checkData(..))")
public void pointCut(){
}
//前置通知 Before 细粒度:准确定位指定包类下的方法
@Before(value = "pointCut()")
public void before(){
System.out.println("前置通知:在目标方法执行之前执行");
}
}
- 3、创建配置类
package com.hx.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/*
配置类
*/
@ComponentScan(basePackages = "com.hx")
@Configuration
public class SpringConfig {
}
- 4、测试操作
import com.hx.config.SpringConfig;
import com.hx.strong.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAOP {
@Test
public void m1() {
//1、创建容器,加载核心配置类
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//2、创建bean对象
UserServiceImpl userServiceImpl = context.getBean(UserServiceImpl.class);
//3、调用方法
userServiceImpl.checkData();
}
@Test
public void m2() {
//1、创建容器,加载核心配置类
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//2、创建bean对象
UserServiceImpl userServiceImpl = context.getBean(UserServiceImpl.class);
//3、调用方法
userServiceImpl.add();
}
/*
默认的通知优先级
环绕通知前:目标方法执行前增强功能
前置通知:在目标方法执行之前执行
我是目标方法,即切入点!
检查通过!
环绕通知后:目标方法执行后增强功能
后置通知:在目标方法执行之后执行
返回通知:在目标方法返回结果前时执行
*/
}
- 5、测试结果
目标方法无异常时,通知的执行顺序:
around 前。。。目标方法执行之前执行
before。。。在目标方法执行之前执行
我是目标方法,也就是被增强的方法。。。。我执行了
around 后。。目标方法执行之后执行
after。。。在目标方法执行之后执行
afterReturning。。。在目标方法执行返回之后执行
目标方法发生异常时,通知的执行顺序:
around 前。。。目标方法执行之前执行
before。。。在目标方法执行之前执行
after。。。在目标方法执行之后执行
afterThrowing。。。在目标方法执行发生异常执行
- 6、相同切入点提取
//提取相同的切入点表达式
//承载方法
@Pointcut(value = "execution(* com.hx.strong.UserServiceImpl.checkData(..))")
public void pointCut(){
}
- 7、切面优先级的设置
@Order(value = 1)
设置增强的优先级(同一个目标方法有多个切面增强的情况)
在增强类上面添加注解@Order(数字类型值),数字类型值越小,优先级越高
第六章 JdbcTemplate
6.1 JdbcTemplate—介绍
spring框架对JDBC进行封装,
使用JDBCTemplate方便实现对数据库操作
6.2 JdbcTemplate—依赖
- 1 引入JDBCTemplate和事务的依赖
<!-- 日志 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<!-- spring的核心上下文 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- spring测试 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<!--事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
- 2、配置数据源和JDBCTemplate
方式1:xml方式
在spring.xml配置文件中配置数据库连接池
<!--配置德鲁伊连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///user_db"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
配置JDBCTemplate对象,为其注入DataSource
<!--配置JDBCTemplate 查看源码-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入DataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
方式2:配置类方式
package com.hx.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
//标识此类是配置类
@Configuration
public class JdbcTemplateConfig {
//IOC容器实例化DruidDataSource的bean对象
@Bean
public DruidDataSource getDruidDataSource(){
//创建连接池对象
DruidDataSource dataSource = new DruidDataSource();
//设置数据库驱动
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//设置数据库连接地址
dataSource.setUrl("jdbc:mysql:///user_db");
//设置数据库用户名
dataSource.setUsername("root");
//设置数据库密码
dataSource.setPassword("root");
//返回数据库连接池对象
return dataSource;
}
//IOC容器实例化JdbcTemplate的bean对象
@Bean
public JdbcTemplate getJdbcTemplate(){
//创建JdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//设置数据源属性
jdbcTemplate.setDataSource(getDruidDataSource());
//返回对象
return jdbcTemplate;
}
}
6.3 JdbcTemplate—新增
API方法:
update方法用于增删改的操作
update(String sql,Object[]… args)
有两个参数:
第一个参数:sql语句
第二个参数:可变参数,设置sql语句的值
@Repository
public class UserDaoImpl implements UserDao {
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//添加的方法
@Override
public void add(User user) {
//编写SQL语句
String sql1 = "insert into t_user values(null ,?,?)";
//获取参数值
Object[] args = {user.getUserName(), user.getUserStatus()};
//进行新增操作,返回操作数
int update = jdbcTemplate.update(sql1,args);
//输出行记录
System.out.println(update);
}
}
6.4 JdbcTemplate—修改和删除
API方法:
update方法用于增删改的操作
update(String sql,Object[]… args)
有两个参数:
第一个参数:sql语句
第二个参数:可变参数,设置sql语句的值
@Repository
public class UserDaoImpl implements UserDao {
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//修改的方法
@Override
public void update(User user) {
//注意:sql语句中的字段名称要与数据库表中的字段名称一致
String sql2 = "update t_user set user_name=?,user_status=? where user_id=?";
Object[] args = {user.getUserName(), user.getUserStatus(),user.getUserId()};
int update = jdbcTemplate.update(sql2,args);
System.out.println(update);
}
//删除的方法
@Override
public void delete(Integer id) {
String sql3 = "delete from t_user where user_id=?";
int update = jdbcTemplate.update(sql3,id);
System.out.println(update);
}
}
6.5 JdbcTemplate—查询
1 查询返回某个值—查询所有记录总数
API方法:
jdbcTemplate.queryForObject(String sql, Class requiredType)
此方法有连个参数:
第一个参数:sql语句
第二个参数:返回类型Class
//查询所有记录
@Override
public int getAll() {
//编写sql语句
String sql4 = "select count(*) from t_user";
//执行查询操作,第一个参数是sql。第二个参数是返回值的类型class
Integer count = jdbcTemplate.queryForObject(sql4, Integer.class);
//返回结果
return count;
}
2 查询返回某对象
API方法:
jdbcTemplate.queryForObject(String sql, RowMapper rowMapper ,Object… args )
此方法有三个参数:
第一个参数:sql语句
第二个参数:RowMapper是接口,可以返回不同类型的数据,使用接口中的实现类,可以完成数据的封装
第三个参数:传递sql语句中问号的值
//通过id查询
@Override
public User selectUserById(Integer id) {
//编写sql
String sql5 = "select * from t_user where user_id=?";
//执行查询操作
/*
jdbcTemplate.queryForObject(String sql, RowMapper<T> rowMapper ,Object... args )
第一个参数:sql语句
第二个参数:RowMapper是接口,可以返回不同类型的数据,使用接口中的实现类,可以完成数据的封装
第三个参数:传递sql语句中问号的值
*/
User user =
jdbcTemplate.queryForObject(sql5, new BeanPropertyRowMapper<User>(User.class), id);
return user;
}
3 查询返回某集合
API方法:
jdbcTemplate.query(String sql, RowMapper rowMapper ,Object… args )
此方法有三个参数:
第一个参数:sql语句
第二个参数:RowMapper是接口,可以返回不同类型的数据,使用接口中的实现类,可以完成数据的封装
第三个参数:传递sql语句中问号的值
//返回集合数据
@Override
public List<User> findAllUser() {
//编写sql
String sql6 = "select * from t_user";
//执行查询操作
List<User> userList = jdbcTemplate.query(sql6, new BeanPropertyRowMapper<User>(User.class));
return userList;
}
4 批量操作—新增
API方法:
batchUpdate(String sql,List<Object[]> batchArgs)
两个参数:
第一个参数:sql语句
第二个参数:list集合,添加多条记录数据
//批量添加
@Override
public void batchAddUser(List<Object[]> batchArgs) {
String sql7 = "insert into t_user values(null,?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql7,batchArgs);
System.out.println(Arrays.toString(ints));
}
5 批量操作—修改和删除
API方法:
batchUpdate(String sql,List<Object[]> batchArgs)
两个参数:
第一个参数:sql语句
第二个参数:list集合,添加多条记录数据
//批量修改
@Override
public void batchUpdateUser(List<Object[]> batchArgs) {
String sql8 = "update t_user set user_name=?,user_status=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql8,batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量删除
@Override
public void batchDelete(List<Object[]> batchArgs) {
String sql9 = "delete from t_user where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql9,batchArgs);
System.out.println(Arrays.toString(ints));
}
第七章 事务
7.1 事务的概念
事务是数据库操作的最基本的单元,是逻辑上的一组操作,要么都成功,如果有一个失败,所有操作都失败。典型案例银行转账
典型场景:
lucy转账100给mary。lucy少100,mary多100
执行成功:lucy少了100 , mary多了100, 表示的是逻辑上的一组操作
执行失败:转账的过程中出点断电,断网导致异常,那么lucy的钱不会少,mary的钱也不会多
7.2 事务的特性
特性 | 含义 |
---|---|
原子性 | 原子性意味着数据库中的事务执行是作为原子。即不可再分,整个语句要么执行,要么不执行 |
一致性 | 一致性即在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏 |
隔离性 | 事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据 |
持久性 | 意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚 |
7.3 事务的管理
1、事务一般添加到JavaEE三层架构里的Service层,也就是业务逻辑层
2、在Spring中进行事务管理,提供了两种方式,即编式事务和声明式事务。编程时事务:不建议使用,代码臃肿,不易维护。而声明式事务:使用方便
3、声明式事务:
可以基于注解方式实现事务管理,也可以基于xml配置文件方式
在Spring进行声明式事务管理。底层用到了AOP原理,提供可一个接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类
7.4 事务的隔离级别
1、事务问题
在多事务操作之间如果不考虑隔离会产生很多问题,如下:
脏读 | 一个未提交的事务读取到了另一个未提交事务的数据 |
幻读 | 一个事务读取到另一个提交事务的添加数据 |
不可重复读 | 一个未提交的事务读取到了另一个已提交事务修改的数据 |
2、解决方案:
通过设置事务隔离级别,解决读的问题
7.5 基于注解方式的声明式事务
- 1、创建Module,引入依赖
<dependencies>
<!--spring的核心上下文-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<!--事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
- 2、创建配置类代替核心配置文件
@Configuration
@ComponentScan(basePackages = "com.hx")
public class SpringConfig {
//获取数据源对象
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybase?ServerTimeZone = UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//获取JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(getDruidDataSource());
return jdbcTemplate;
}
}
- 3、创建数据库表 account
- 4、创建接口UserDao,并提供方法
public interface UserDao {
//多钱的方法
void addMoney();
//少钱的方法
void reduceMoney();
}
- 5、创建类UserService类,并注入UserDao
@Service
public class UserService {
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney(){
//lucy少100
userDao.reduceMoney();
//mary多100
userDao.addMoney();
}
}
- 6、创建实现类UserDaoImpl
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//少钱操作:lucy转钱给mary100
@Override
public void reduceMoney() {
String sql = "update account set money = money-? where name = ?";
jdbcTemplate.update(sql, 100, "lucy");
}
//多钱操作:mary收到转账100
@Override
public void addMoney() {
String sql = "update account set money = money+? where name = ?";
jdbcTemplate.update(sql, 100, "mary");
}
}
- 7、编写测试类执行转账操作
public class TestAccount {
@Test
public void test1(){
//创建容器对象
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
//获取bean对象
UserService userService = context.getBean(UserService.class);
//调用方法
userService.accountMoney();
}
}
-
8、查看结果
转账成功,lucy少100,mary多100
-
9、当程序发生异常(手动设置异常)
-
10 、再次测试,发生异常,查看数据库结果
看数据库变化-----不符合逻辑
其中:mary的money没有改变
但是,lucy的money的钱少了100
-
11、解决方案—添加事务
-
12、在配置类SpringConfig中创建事务管理器bean对象,
在配置类SpringConfig中创建事务管理器bean对象
并在配置类上添加注解@EnableTransactionManagement,用于开启事务管理
@Configuration//配置类
@ComponentScan(basePackages = "com.hx")//注解扫描
@EnableTransactionManagement//开启事务
public class SpringConfig {
//创建DruidDataSource对象
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql:///mybase");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
return druidDataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//在IOC容器中根据数据类型,找到DruidDataSource,并注入
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
//创建事务管理器DataSourceTransactionManager对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager dataSourceTransactionManager =
new DataSourceTransactionManager();
//在IOC容器中根据数据类型,找到DruidDataSource,并注入
dataSourceTransactionManager.setDataSource(druidDataSource);
return dataSourceTransactionManager;
}
}
- 13、在service类上面添加注解@Transactional
@Tranasctional注解是Spring 框架提供的声明式注解事务解决方案,在开发中使用事务保证方法对数据库操作的原子性,要么全部成功,要么全部失败.
- 14、再次测试,查看数据库结果
发生异常,数据不发生变化,执行事务的回滚操作。
7.6 @Transactional
1 @Transactional概念
@Tranasctional注解是Spring 框架提供的声明式注解事务解决方案,在开发中使用事务保证方法对数据库操作的原子性,要么全部成功,要么全部失败。
2 @Transactional作用范围
作用范围 | 含义 |
---|---|
作用于类 | 当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息 |
作用于方法 | 当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息 |
作用于接口 | 不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效 |
3 @Transactional 注解的属性
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
- 1、事务的传播行为—propagation属性
代表事务的传播行为,共有七种。默认值为 Propagation.REQUIRED
传播行为 | 含义 |
---|---|
Propagation.REQUIRED | 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务 |
Propagation.SUPPORTS | 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行 |
Propagation.MANDATORY | 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常 |
Propagation.REQUIRES_NEW | 重新创建一个新的事务,如果当前存在事务,暂停当前的事务 |
Propagation.NOT_SUPPORTED | 以非事务的方式运行,如果当前存在事务,暂停当前的事务 |
Propagation.NEVER | 以非事务的方式运行,如果当前存在事务,则抛出异常 |
Propagation.NESTED | 和 Propagation.REQUIRED 效果一样 |
- 2 事务的隔离界别–isolation 属性
isolation :事务的隔离级别,默认值为 Isolation.DEFAULT
隔离级别 | 含义 |
---|---|
Isolation.DEFAULT | 使用底层数据库默认的隔离级别 |
Isolation.READ_UNCOMMITTED | 读未提交 |
Isolation.READ_COMMITTED | 读已提交 |
Isolation.REPEATABLE_READ | 可重复读 |
Isolation.SERIALIZABLE | 序列化 |
-
3 timeout 属性
timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 -
4 readOnly 属性
readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 -
5 rollbackFor 属性
rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。 -
6 noRollbackFor属性
noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。