学习:Sping Framework
概念:Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework 为基础的。
特性:
非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。
控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
声明式(事务解决):很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
功能模块
Core Container:核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
AOP&Aspects:面向切面编程
Testing:提供了对 junit 或 TestNG 测试框架的整合。
Data Access/Integration:提供了对数据访问/集成的功能。
Spring MVC(设计思想):提供了面向Web应用程序的集成功能。
IOC容器
1.思想:反转控制
2..概念:反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
3.DI:依赖注入--->为Spring管理的对象进行赋值(①setter赋值;②构造器赋值)--->优点:方便维护
4.DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
5.总结:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。
6.IOC的功能:管理对象--->对象由类产生
7.编码规则:对修改关闭,对扩展开放(不要修改原有的功能(防止新功能缺陷),可以扩展)
8.IOC的顶层接口:BeanFactory
9.IOC的常用类型ApplicationContext(接口)
10.创建IOC容器
①ClassPathXmlApplicationContext(这是通过xml路径XXX.xml)
②FileSystemXmlApplicationContext(这是通过文件来获取)
创建对象 bean,存入IOC,并调用
1.创建Maven项目
2.导入依赖(maven的jar依赖性)
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
3.创建spring核心配置类applicationContext.xml和加载主键
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件是什么
id属性:bean的唯一标识
class属性:组件类的全类名
-->
<bean id="user" class="com.atguigu.ioc.component.User"/>
</beans>
4.创建组件类(bean)
package com.atguigu.ioc.component;
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public void run(){
System.out.println("跑步");
}
}
5.创建测试类
public class IOCTest {
// 创建 IOC 容器对象,为便于其他实验方法使用声明为成员变量
ApplicationContext users = new ClassPathXmlApplicationContext("applicationContext.xml");
/**
* 设置ioc,调用bean的方法
*/
@Test
public void testExperiment01() {
// 从 IOC 容器对象中获取bean,也就是组件对象
User user = (User) this.users.getBean("user");
user.run();
}
}
原理:Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要无参构造器时,没有无参构造器,则会抛出下面的异常:
org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'happyComponent1' defined in class path resource
[applicationContext.xml]: Instantiation of bean failed; nested exception
is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [com.atguigu.ioc.component.HappyComponent]:
No default constructor found; nested exception is
java.lang.NoSuchMethodException: com.atguigu.ioc.component.HappyComponent.()
此异常表示创建的bean没有创建
Instantiation of bean failed; ---》实例化失败,也就是创建对象失败,
SpingIOC是通过无参构造创建的,所以无参构造器没有
bean创建的条件:对一个JavaBean来说,无参构造器和属性的getXxx()、setXxx()方法是必须存在的,特别是在框架中
获取bean(在IOC容器中创建一个被管理的bean对象)的操作--->bean的属性赋值是用的setter方法;
测试类中的公共代码题前(下面测试中的users都是该代码的对象)
private ApplicationContext users = new ClassPathXmlApplicationContext("applicationContext.xml");
1.配置类中的id获取(上面的哪个例子)-->bean的id具有唯一性(id指向的是一个对象,对象由类而来,所以必须要先有类)
2.根据对应类的class获取,类型获取(此class类型仅且只使用过一次(IOC容器中此类型就一个bean),才能使用,不然报错:NoUnqueBeanDefinieionException--->不唯一bean错误)
xml:
<bean id="user" class="com.atguigu.ioc.component.User"/>
java:
@Test
public void testExperiment02() {
//必须要与bean对应
User user = users.getBean(User.class);
user.run();
}
2.2.兼容类型的获取(前提:①bean是唯一 ②该类是接口的实现类--->就可以通过该类来获取该类接口对象)---->向上转型
public interface TryInterface{
}
public class User implements {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
@Test
public void Test2(){
// TryInterface bean = users.getBean(TryInterface.class);
TryInterface bean = users.getBean(User.class);
System.out.println(bean);
}
结果:User{id=null,name=null}
2.3.通过id和class类型获取
//xml中bean的id为dept,类型为Dept
Dept bean = users.getBean("dept", Dept.class);
3.bean中类的值的设置与获取(setter注入,标签<property>)
①spring.xml设置
<!-- 实验三 [重要]给bean的属性赋值:setter注入 -->
<bean id="user" class="com.atguigu.ioc.component.User">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
<!-- value属性:指定属性值 -->
<property name="id" value="1"/>
<property name="name" value="baby"/>
</bean>
②获取方式(类型获取)
/**
* 获取bean的赋值
*/
@Test
public void Test3(){
User user = users.getBean(User.class);
System.out.println(user.getName());
}
4.引用外部已声明的bean,属性是实体类的时候,才会用ref引用外部bean
①spring.xml设置(这是使用ref属性引用IOC中类型兼容的bean的id)
<!--
bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件是什么
id属性:bean的唯一标识
class属性:组件类的全类名
-->
<!-- 实验三 [重要]给bean的属性赋值:setter注入 -->
<bean id="user" class="com.atguigu.ioc.component.User">
<!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
<!-- name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关) -->
<!-- value属性:指定属性值 -->
<property name="id" value="1"/>
<property name="name" value="baby"/>
</bean>
<bean id="role" class="com.atguigu.ioc.component.Role">
<property name="r_id" value="11"></property>
<!-- ref 属性:通过 bean 的 id 引用另一个 bean -->
<property name="user" ref="user"></property>
</bean>
①这是级联赋值(先关联,在赋值)
<bean id="car" class="com.gong.spring.beans.Car"></bean>
<bean id="student" class="com.gong.spring.beans.Student">
<property name="name" value="tom"></property>
<property name="age" value="12"></property>
<property name="score" value="98.00"></property>
<property name="car" ref="car"></property>
<property name="car.name" value="baoma"></property>
</bean>
②获取
/**
* 引用外部已声明的bean
* 在bean的加入实例类
*/
@Test
public void Test4(){
Student stu= users.getBean(Student.class);
System.out.println(stu.getCar.getName());
}
易错点:
①引用的必须已有的bean,就是已经存入IOC的bean
②写错标签属性
如果错把ref属性写成了value属性,会抛出异常:org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'role' defined in class
path resource [applicationContext.xml]: Initialization
of bean failed; nested exception is org.springframework.
beans.ConversionNotSupportedException: Failed to convert
property value of type 'java.lang.String' to required type
'com.atguigu.ioc.component.User' for property 'user';
nested exception is java.lang.IllegalStateException:
Cannot convert value of type 'java.lang.String' to
required type 'com.atguigu.ioc.component.User' for
property 'user': no matching editors or conversion
strategy found
意思是不在类路径资源[applicationContext.xml]中定义了一个名为“role”的bean,
但在创建bean时出现了错误。---->'user;
不能转换类型-->属性'user'的com.atguigu.ioc.component.User':没有找到匹配的编辑器或转换策略
5.bean标签里面的内部bean(1.在bean里面;2.必须在<property>标签里面;3.内部bean的作用域只在外部bean内,所以内部bean只能给对应的外部bean使用,别的bean无权访问)
<bean id="user" class="com.atguigu.ioc.component.User">
<property name="id" value="1"/>
<property name="name" value="baby"/>
<!-- 实验五 [重要]给bean的属性赋值:内部bean -->
<!-- 在一个 bean 中再声明一个 bean 就是内部 bean -->
<!-- 内部 bean 可以直接用于给属性赋值,可以省略 id 属性 -->
<property name="hobby">
<bean class="com.atguigu.ioc.component.Hobby">
<property name="love" value="宝宝"></property>
</bean>
</property>
</bean>
6.给bean的属性赋值:引入外部属性文件(以druid为例)
1.加入依赖
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
2.写jdbc外部文件--->jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/crud?serverTimezone=UTC
jdbc.username=root
jdbc.password=password
3.引入外部文件,配置数据源bean
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
4.写测试类
@Test
public void Test() throws SQLException {
DataSource dataSource = (DataSource)users.getBean("druidDataSource");
Connection connection = dataSource.getConnection();
System.out.println("connection = " + connection);
}
7 给bean的属性赋值:级联属性赋值(在一个bean中,引用另一个bean,并赋值)
<!-- 实验七 给bean的属性赋值:级联属性赋值 -->
<bean id="user" class="com.atguigu.ioc.component.User">
<property name="id" value="5"></property>
<property name="name" value="5"></property>
<!-- 装配关联对象 -->
<property name="role" ref="role"></property>
<!-- 对User来说,Role的r_id属性就是级联属性 -->
<property name="role.r_id" value="6"></property>
</bean>
<bean id="role" class="com.atguigu.ioc.component.Role">
</bean>
8.给bean的属性赋值:(构造器注入(类中必须有相对应的有参构造,而且值应该和有参构造器的参数数目一致),<constructor-arg>)-->和第3个测试相对应的不同方法赋值
1、声明组件类(实体类)--->创建bean
2.配置以及test类<constructor-arg >
<!-- 实验八 给bean的属性赋值:构造器注入 -->
<bean id="hobby" class="com.atguigu.ioc.component.Hobby">
<constructor-arg value="哲儿"></constructor-arg>
</bean>
/**
* 给bean的属性赋值:构造器注入
*/
@Test
public void Test2(){
Hobby hobby = (Hobby) users.getBean("hobby");
System.out.println(hobby.getLove());
}
补充:
constructor-arg标签还有三个个属性可以进一步描述构造器参数:
index属性:指定参数所在位置的索引(从0开始)
name属性:指定参数名
type属性:参数的类型
9.给bean的属性赋值:特殊值处理
null值 (有相对应的标签)
<property name="commonValue">
<!-- null标签:将一个属性值明确设置为null -->
<null/>
</property>
特殊符号(大于符号,小于符合等等)
①.XML实体(<,>等等)
<!-- 实验九 给bean的属性赋值:特殊值处理 -->
<bean id="propValue" class="com.atguigu.ioc.component.PropValue">
<!-- 小于号在XML文档中用来定义标签的开始,不能随便使用 -->
<!-- 解决方案一:使用XML实体来代替 -->
<property name="expression" value="a < b"/>
</bean>
XML实体解析:除参数实体外)都以一个与字符(&)开始,以一个分号(;)结束。XML 标准定义了所有 XML 解析器都必须实现的 5 种标准实体
②.CDATA区(必须在单独的value标签中,快捷键CD(大写))--->CDATA区里面的东西都会原样输出
<!-- 实验九 给bean的属性赋值:特殊值处理 -->
<bean id="hobby2" class="com.atguigu.ioc.component.Hobby">
<property name="love">
<!-- 解决方案二:使用CDATA节 -->
<!-- CDATA中的C代表Character,是文本、字符的含义,CDATA就表示纯文本数据 -->
<!-- XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签或属性来解析 -->
<!-- 所以CDATA节中写什么符号都随意 -->
<value><![CDATA[a < b]]></value>
</property>
</bean>
10.给bean的属性赋值:使用p名称空间
作用:使用 p 名称空间的方式可以省略子标签 property,将组件属性的设置作为 bean 标签的属性来完成。
①.使用 p 名称空间需要导入相关的 XML 约束,在 IDEA 的协助下导入即可:
②.配置bean(1.在属性中配置 2.语法:p:实体类(bean类)中的成员名)
<bean id="hobby3" class="com.atguigu.ioc.component.Hobby" p:love="hello">
</bean>
③.测试类
/**
* 给bean的属性赋值:使用p名称空间
*/
@Test
public void Test4(){
Hobby hobby = (Hobby) users.getBean("hobby3");
System.out.println(hobby.getLove());
}
11.给bean的属性赋值:集合属性(setter赋值)
<!-- 实验十一 给bean的属性赋值:集合属性-->
<bean id="hobby4" class="com.atguigu.ioc.component.Hobby">
<property name="list">
<!-- -->
<list>
<value>member01</value>
<value>member02</value>
<value>member03</value>
</list>
</property>
<property name="map">
<!-- 给Map类型的属性赋值 -->
<!--<map>
<entry key="财务部" value="张三"/>
<entry key="行政部" value="李四"/>
<entry key="销售部" value="王五"/>
</map>-->
<!-- 也可以使用props标签 -->
<props>
<prop key="财务部">张三2</prop>
<prop key="行政部">李四2</prop>
<prop key="销售部">王五2</prop>
</props>
</property>
</bean>
11.1bean( 内部赋值:list,set,array类型,都是在相对应标签下<list><set><array>)
11.2.bean (外部赋值:)
①添加相关约束
②具体配置
<bean id="hobby" class="com.atguigu.ioc.component.Hobby">
<property name="love" value="宝宝"></property>
<property name="list" ref="list"></property>
<property name="map" ref="map"></property>
</bean>
<util:list id="list">
<value>开心</value>
<value>快乐</value>
</util:list>
<util:map id="map">
<entry key="me" value="baby"></entry>
<entry key="meToo" value="huihui"></entry>
</util:map>
11.2.测试类
/**
* 给bean的属性赋值:集合属性
*/
@Test
public void Test5(){
Hobby hobby = (Hobby) users.getBean("hobby4");
List<String> list = hobby.getList();
System.out.println(list);
System.out.println(hobby.getMap());
}
11-2 11.给bean的属性赋值:集合属性(构造器赋值)
<bean id="d" name="d" class="java.util.Date"></bean>
<!-- 一 -->
<bean class="com.jd.vo.UserInfo">
<constructor-arg type="java.lang.String" value="li"></constructor-arg>
<constructor-arg type="int" value="20"></constructor-arg>
</bean>
<!-- 二 -->
<bean class="com.jd.vo.UserInfo">
<constructor-arg ref="d"></constructor-arg>
</bean>
<!-- 三 -->
<bean class="com.jd.vo.UserInfo">
<constructor-arg>
<array>
<value>字符串1</value>
<bean class="java.lang.String">
<constructor-arg value="字符串2"></constructor-arg>
</bean>
</array>
</constructor-arg>
</bean>
<!-- 四 -->
<bean class="com.jd.vo.UserInfo">
<constructor-arg>
<list>
<value>265.2</value>
<value>265.222</value>
<value>265.2333</value>
<value>265.2444</value>
</list>
</constructor-arg>
</bean>
<!-- 五 -->
<bean class="com.jd.vo.UserInfo">
<constructor-arg>
<set>
<ref bean="d"/>
<bean class="java.util.Date"></bean>
</set>
</constructor-arg>
</bean>
<!-- 六 -->
<bean id="ui" name="i,sadsadsadsadas,paa" class="com.jd.vo.UserInfo">
<constructor-arg>
<map>
<entry key="wawaaw" value="26"></entry>
<entry key-ref="li" value="96"></entry>
</map>
</constructor-arg>
</bean>
<!-- 七 -->
<bean class="com.jd.vo.UserInfo">
<constructor-arg>
<!--map-->
<props>
<prop key="name">root</prop>
</props>
</constructor-arg>
</bean>
12.自动装配(由xml和注解,这里使用注解)
IOC中最基本的组件时bean,Spring提供了四个注解将类标识为组件(即在某个类上面添加注解后,扫描之后,该类型会自动在IOC容器中生成相应的bean)
下面四个注解的功能是一模一样的,都是为了生成一个bean,但是是为了我们开发好分辨,仅仅将组件标识为不同功能的组件
@Component:将类标识为一个普通组件
@Controller:将类标识为一个控制层组件
@Service:将类标识为一个业务层组件
@Repository:将类标识为一个持久层组件
自动装配注解开发分为两步:①在需要的类上面添加注解组件标签②在spring配置类中开启自动扫描组件(<context:component-scan base-package="com.atguigu.ioc.autowired"></context:component-scan>)-->注意包的范围
注解添加的位置:spring的这四个标识组件的注解只能添加在类上面
注解标识的bean的默认id为类名的小驼峰(类名:UserDao--->默认id:userDao);注解的id也可以更改,通过注解的value值(@Controller(value=“abc ”))
Spring的配置类中自动扫描主键设置:
<!-- 自动扫描-->
<!-- use-default-filters默认为true,意思为扫描所有的组件-->
<context:component-scan base-package="com.atguigu.ioc.autowired" use-default-filters="true">
<!-- 注解排除-(用的最多)==SSM整合的时候Controll是springMVC扫描的-->
<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>-->
<!-- 类的类型排除-->
<!-- <context:exclude-filter type="assignable" expression="com.atguigu.ioc.autowired.servlet.CrudServlet"/>-->
<!-- 类的包含(添加):
条件一,必须把自动扫描组件context:component-scan的use-default-filters属性设置为false;
条件二:排除和包含不能再一个自动扫描组件中
-->
<context:include-filter type="assignable" expression="com.atguigu.ioc.autowired.servlet.CrudServlet"/>
</context:component-scan>
自动装配的注解@Autowired
注解的位置:成员变量,set方法,构造器
默认的自动装配的方式:byType
两种自动装配的特殊情况以及解决方法
1.若在IOC容器中没有任何一个bean能够通过类型匹配为属性赋值,则报错:NoSuchBeanDefinitionException(就是没有注解和xml某个类,让它生成相应的bean)该错误是spring的错误,因为在@Autoeired中由属性required默认为true,表示必须自动装配,若装配不成功,则直接报错NoSuchBeanDefinitionException;若修改属性required的值为false,则表示能装配则装配,如果不能装配,则使用该属性的默认值null
2、若在IOC容器中有多个bean能够通过类型匹配成功,此时会自动转换为byName模式(bean的id),将要赋值的属性的属性名作为bean的id匹配一个指定的bean,若通过byName也无法实现自动装配,即要赋值的属性的属性名和任何一个bean的id都不一致,此时会报错:NoUniqueBeanDefinitionException;
①解决方案:通过标识组件的四个注解的value属性设置bean的id和要赋值的属性的属性名保持一致
②通过@Qualifier(添加再@Autowired下面)注解指定为属性赋值的bean的id
Bean的作用域(分为4种情况,Spring默认为单例,scope属性可以在配置文件中设置)
1.单例(singleton)--->一直有效
2.多例(prototype)--->对象消失结束
3.request--->请求结束
4.session--->会话结束
Bean的生命周期(如果加后置处理器,那就与7个步骤,分别在初始化之前和初始化之后)
1.创建对象(无参构造器)
2.依赖注入(setter注入或者构造器注入)
3.初始化(注意必须在bean标签里面添加属性init-method属性指定初始化方法,但是分为2种情况)
单例:1,2,3步骤生命周期在IOC容器中执行
多例:1,2,3步骤生命周期在获取对象的时候执行
4.使用对象
5.销毁对象(注意必须在bean标签里面添加属性destroy-method属性指定初始化方法,但是分为2种情况)-->close()
单例:bean的销毁由IOC容器管理
多例:bean的销毁由Java的垃圾回收机制管理
Spring完全注解开发
1.用类代替配置文件(SpringBoot用的多)
@Configurable //将一个类标识为配置类,用来代替spring的配置文件xml
@ComponentScan("com.atguigu.spring") //自动扫描组件,还有一个@ComponentScans
public class SpringConfig {
//返回一个bean,交给IOC容器
@Bean
public User getUser(){
return new User();
}
}
2.读取配置文件从ClassPathXmlApplicationContext换为AnnotationConfigApplicationContext
@Test
public void Test(){
//获取iop容器
ApplicationContext userIop = new AnnotationConfigApplicationContext(SpringConfig.class);
User bean = userIop.getBean(User.class);
System.out.println(bean);
}
Spring整合Junit4
目的:在test包中可以调用IOC容器
好处:
①.不需要自己再去获取IOC容器对象
②.在需要任何bean的时候可以直接自动装配
1.配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.atguigu.spring.controller"></context:component-scan>
</beans>
2.创建一个bean测试
@Controller
public class UserController {
public void run(){
System.out.println("我在跑步");
}
}
3.测试(整合后,test文件中就可以自动装配;@RunWith;@ContextConfiguration)
//junit的@RunWith注解:指定Spring为Junit提供的运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置spring的配置文件
@ContextConfiguration("classpath:spring.xml")
public class UserTest {
/**
* 整合spring与junit4的 架包
*/
@Autowired
private UserController controller;
@Test
public void test1(){
//优点:1.不需要再获取IOC容器 2.不需要再获取需要的bean(直接自动装配)
controller.run();
}
}
代理模式
概念:代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
意义:把核心代码和非核心代码分离,解耦作用
静态代理:
①首先要有一个接口,通用的接口是代理模式实现的基础
/**
* 目标对象
*/
public interface Calua {
public int add(int x,int y);
}
②由一个真正的接口实现类,实现目标接口
public class CaluaImpl implements Calua {
@Override
public int add(int x, int y){
int result = x + y;
return result;
}
}
③有一个代理类,实现目标接口
public class CaluaProxy implements Calua {
//目标类
private Calua calua;
public CaluaProxy(Calua calua) {
this.calua = calua;
}
@Override
public int add(int x, int y) {
System.out.println("参数是==>"+x+",参数是==>"+y);
//目标类调用目标方法
int add = calua.add(x, y);
System.out.println("结果是==>"+add);
return add;
}
}
④测试类
/**
* 静态代理
*/
@Test
public void jingtaiTest(){
// 这里实例化的是代理类,参数是目标类的实现类
CaluaProxy caluaProxy = new CaluaProxy(new CaluaImpl());
int add = caluaProxy.add(1, 2);
}
⑤结果
参数是==>1,参数是==>2
结果是==>3
总结:静态代理类,代理对象(目标对象)固定;
动态代理
概念:每次访问目标对象实现功能,都会通过代理对象访问目标对象,此时在代理对象中就可以管理目标对象实现功能的过程,并且添加一些额外操作
动态代理的方式有两种:(这里以jdk,因为我们以后很大概率是面向接口编程)
目标类为接口的时候,使用jdk动态代理
1.创捷一个接口(目标接口)
/**
* 目标对象
*/
public interface Caluas {
public int add(int x, int y);
public int div(int x, int y);
}
2.为目标接口创建一个实现类
public class CaluasImpl implements Caluas {
@Override
public int add(int x, int y){
//这些就是非核心代码---->会放到代理类中
// System.out.println("参数是==>"+x+",参数是==>"+y);
int result = x + y;
// System.out.println("结果是==>"+result);
return result;
}
@Override
public int div(int x, int y) {
int result = x / y;
return result;
}
}
3.重新jdk自带的动态代理中的public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法中的接口InvocationHandler中的方法 public Object invoke(Object proxy, Method method, Object[] args)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyFactory implements InvocationHandler {
//因为目标接口类型不确定 用object来表示
private Object target;
//获取目标
public ProxyFactory(Object target) {
this.target = target;
}
/**
* proxy:表示代理对象
* method:代理对象实现了目标对象所实现接口中的各个方法
* args:方法的参数
*/
//这是重新接口的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
//目标位置之前
System.out.println("参数是==>"+args[0]+",参数是==>"+args[1]);
invoke = method.invoke(target, args);
//目标位置(返回值)之后
System.out.println("结果是==>"+invoke);
} catch (Exception e) {
//出现异常之后
System.out.println("异常出现==>"+e.getMessage());
e.printStackTrace();
} finally {
//最终要加入的操作
System.out.println("执行 完成");
}
return invoke;
}
}
4.测试创建动态代理
public class DynamicTest {
@Test
public void Test() throws Exception{
//目标的实现类对象
CaluasImpl calua = new CaluasImpl();
//重新接口InvocationHandler后的对象
InvocationHandler factory = new ProxyFactory(calua);
/**
* Proxy.newProxyInstance():通过jdk动态代理创建代理对象
* 此方法有三个参数:
* 1、类加载器:用来加载动态生成的代理类
* 2、目标对象实现的 所有接口的 class对象的 数组
* 3、代理对象实现接口时如何重写接口中的抽象方法,即代理对象如何管理目标对象方法的实现过程
*/
Caluas o =(Caluas) Proxy.newProxyInstance(calua.getClass().getClassLoader(), calua.getClass().getInterfaces(), factory);
int div = o.div(1, 0);
System.out.println(div);
}
@Test
public void Test3(){
B b = new B();
InvocationHandler c = new ProxyFactory(b);
A a = (A) Proxy.newProxyInstance(b.getClass().getClassLoader(), b.getClass().getInterfaces(), c);
a.run(5,6);
}
}
目标类为类的时候(继承),使用cglib的动态代理
AOP的专业术语(根据动态代理来理解)
1.横切关注点:就是再目标对象抽象的非核心业务代码
public class CaluasImpl implements Caluas {
@Override
public int add(int x, int y){
//这些就是非核心代码---->会放到代理类中
// System.out.println("参数是==>"+x+",参数是==>"+y);
int result = x + y;
// System.out.println("结果是==>"+result);
return result;
}
}
2.切面:封装了横切关注点的类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyFactory implements InvocationHandler {
//因为目标接口类型不确定 用object来表示
private Object target;
//获取目标
public ProxyFactory(Object target) {
this.target = target;
}
/**
* proxy:表示代理对象
* method:代理对象实现了目标对象所实现接口中的各个方法
* args:方法的参数
*/
//这是重新接口的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
//目标位置之前
System.out.println("参数是==>"+args[0]+",参数是==>"+args[1]);
invoke = method.invoke(target, args);
//目标位置(返回值)之后
System.out.println("结果是==>"+invoke);
} catch (Exception e) {
//出现异常之后
System.out.println("异常出现==>"+e.getMessage());
e.printStackTrace();
} finally {
//最终要加入的操作
System.out.println("执行 完成");
}
return invoke;
}
}
3.通知:每个横切关注点在切面中都表示为一个通知方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyFactory implements InvocationHandler {
//因为目标接口类型不确定 用object来表示
private Object target;
//获取目标
public ProxyFactory(Object target) {
this.target = target;
}
/**
* proxy:表示代理对象
* method:代理对象实现了目标对象所实现接口中的各个方法
* args:方法的参数
*/
//这是重新接口的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
//目标位置之前 --->前置通知
System.out.println("参数是==>"+args[0]+",参数是==>"+args[1]);
invoke = method.invoke(target, args);
//目标位置(返回值)之后 --->返回通知
System.out.println("结果是==>"+invoke);
} catch (Exception e) {
//出现异常之后 --->异常通知
System.out.println("异常出现==>"+e.getMessage());
e.printStackTrace();
} finally {
//最终要加入的操作 --->后置通知
System.out.println("执行 完成");
}
return invoke;
}
}
4.目标:目标对象,如果目标对象为接口,就使用jdk自带的动态代理;如果目标对象为类(继承),那么就使用cglib自带的动态代理
5.代理:代理类(切面)
6.连接点:原类中抽取横切关注点的位置,即通知需要作用的位置
7.切入点:定位连接点的方式
AOP(面向切面编程)
AOP是一种思想,它的实现是AspectJ(提供注解)
spectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
AOP是一种横向抽取机制(可以抽取不连续的代码)--->面向切面编程;面向对象编程封装的是一段连续的代码
基于注解的AOP
1.导入依赖(切面管理依赖)
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 整合spring和junit4 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver:管理切面的 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
2.写一个接口为目标对象Calua
/**
* 目标对象
*/
public interface Calua {
public int add(int x, int y);
public int div(int x, int y);
}
3.为接口写一个实现类CaluaImpl (并把接口转化为bean,存入IOC容器)
import org.springframework.stereotype.Component;
@Component //标识为一个普通组件
public class CaluaImpl implements Calua {
@Override
public int add(int x, int y){
int result = x + y;
return result;
}
@Override
public int div(int x, int y) {
int result = x / y;
return result;
}
}
4.写一个切面类AnntationAop (标记为组件,标记为切面,并在此类中写入通知--->一般环绕通知并不和其他通知一起用,此处是为了实验)
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component //虽然我们把这个当作一个切面,但是也要存在ioc容器中
@Aspect //将IOC容器中的组件标识为一个切面
public class AnntationAop {
//提取公共切入点方法,为下面的前置,后置,异常通知调用
@Pointcut("execution(* com.atguigu.spring.aop.*.*(..))")
public void pointcut(){}
//此注解标识为前置通知
// @Before("execution(int com.atguigu.spring.aop.CaluaImpl.add(int,int))")
//第一步优化:execution执行(切入点)里面的内容-->这表示目标类中CaluaImpl的所有方法都可以(参数要一致,返回值一致)
// @Before("execution(int com.atguigu.spring.aop.CaluaImpl.*(int,int))")
//第二步优化:execution执行(切入点)里面的内容-->这表示aop包类中的所有方法都可以(参数要一致,返回值一致)
// @Before("execution(int com.atguigu.spring.aop.*.*(int,int))")
//第三步优化:execution执行(切入点)里面的内容-->这表示aop包类中的所有方法都可以(参数要一致)
// @Before("execution(* com.atguigu.spring.aop.*.*(int,int))")
//第三步优化:execution执行(切入点)里面的内容-->这表示aop包类中的所有方法都可以
// @Before("execution(* com.atguigu.spring.aop.*.*(..))")
//第四步优化(最终):写一个公共的切入点方法
@Before("pointcut()")
public void getBefore(JoinPoint joinPoint){
//JoinPoint --->连接点execution(int com.atguigu.spring.aop.Calua.add(int,int))
//joinPoint.getSignature()--->获取完整的信息int com.atguigu.spring.aop.Calua.add(int,int)
Signature signature = joinPoint.getSignature();
System.out.println("前置通知的方法名"+signature.getName());
}
/**
* 此注解标识为后置通知
* @param joinPoint
*/
@After("pointcut()")
public void getAfter(JoinPoint joinPoint){
System.out.println("后置通知的方法名"+joinPoint.getSignature().getName());
}
/**
* public class Exception extends Throwable
* Throwable是Exception父类(最大类)
* value属性设置切入点表达式
* throwing属性将参数列表中的某个参数设置为接收目标对象方法所出现的异常信息
* @param joinPoint
* @param ex
*/
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void getAfterThrowing(JoinPoint joinPoint,Throwable ex){
System.out.println("异常通知的方法名"+joinPoint.getSignature().getName());
System.out.println("异常通知的异常信息"+ex);
}
/**
*
* @AfterReturning将方法标识为返回通知方法
* value属性设置切入点表达式
* returning属性将参数列表中的某个参数设置为接收目标对象方法返回值
* 返回值因为不确定类型,所有是objec
*/
@AfterReturning(value = "pointcut()",returning = "result")
public void getAfterReturning(JoinPoint joinPoint, Object result){
System.out.println("返回通知的方法名"+joinPoint.getSignature().getName());
System.out.println("返回通知的返回值"+result);
}
/**
* @Around将方法标识为环绕通知方法
* public interface ProceedingJoinPoint extends JoinPoint {}
* 环绕通知的要求:返回值必须为Object,参数必须是ProceedingJoinPoint(它继承JoinPoint,方法多余JoinPoint)
* 环绕通知不和其他通知连用,要么用其他4个通知,要么单独用环绕通知
* @param joinPoint
* @return
*/
@Around("pointcut()")
public Object getAround(ProceedingJoinPoint joinPoint){
Object proceed=null;
try {
System.out.println("前置通知11");
//表示当前的方法执行
proceed= joinPoint.proceed();
System.out.println("返回通知11");
} catch (Throwable throwable) {
System.out.println("异常通知11");
throwable.printStackTrace();
} finally {
System.out.println("后置通知11");
}
return proceed;
}
}
5.这里没有写配置类,而是用了一个AnntationAop.xml来代替(注意:只有当切面用注解标识的时候,才会用 <aop:aspectj-autoproxy />)
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 扫描组件-->
<context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>
<!--开启aspectj的自动代理功能,即通过aspectj创建代理类,这只针对注解AOP-->
<aop:aspectj-autoproxy />
</beans>
6.在写一个切面类TestAop 来测试优先级(优先级主要是看前置通知)
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Order(1)//值越小优先级越高,默认为2147483647(integer的最大值)
public class TestAop {
@Before("execution(int com.atguigu.spring.aop.CaluaImpl.*(int,int))")
public void getBes(){
System.out.println("测试切面的优先级");
}
}
7,测试类来显示
public class AnntationAopTest {
@Test
public void getBeforTest(){
//1.获取ioc容器
ApplicationContext app = new ClassPathXmlApplicationContext("AnntationAop.xml");
//这里我们应该获取是目标对象的实现类,但是我们用了代理模式,所有我们每次都是通过代理对象(动态生成)来控制目标对象
// 而代理对象的类型应该是目标对象(接口)确定
Calua bean = app.getBean(Calua.class);
int add = bean.div(1, 1);
System.out.println(add);
}
}
8,结果
结果:
测试切面的优先级
前置通知11
前置通知的方法名div
返回通知的方法名div
返回通知的返回值1
后置通知的方法名div
返回通知11
后置通知11
最后的结果为1
基于xml的AOP
1.导入依赖jar(同上)
2.写一个接口作为目标类(代码同上,为了简洁出来@Component组件标签不取消外,别的标签都取消)
3.写接口 的实现类(代码同上,为了简洁出来@Component组件标签不取消外,别的标签都取消)
4.写一个切面(代码同上,为了简洁出来@Component组件标签不取消外,别的标签都取消)
5.在写一个切面里测试优先级(代码同上,为了简洁出来@Component组件标签不取消外,别的标签都取消)
6.配置spring的配置spring-xml.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.atguigu.spring.xml"></context:component-scan>
<!--
<aop:config>:aop的设置标签,里面可以设置aop相关属性
<aop:pointcut>:aop提取的公共切入发,下面的标签好使用
<aop:aspect>:切面标签
属性:ref:组件名;
order:切面的优先级
<aop:before>:前置通知标签
<aop:after>:后置通知标签
<aop:after-returning>:返回通知标签
<aop:after-throwing>:异常通知标签
<aop:around>:环绕通知标签
属性:method:方法名
pointcut-ref:引用公共切入点
returning:返回值属性
throwing:异常值属性
-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring.xml.*.*(..))"/>
<aop:aspect ref="anntationAop">
<aop:before method="getBefore" pointcut-ref="pointcut"></aop:before>
<aop:after method="getAfter" pointcut-ref="pointcut"/>
<aop:after-returning method="getAfterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="getAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
<aop:around method="getAround" pointcut-ref="pointcut" />
</aop:aspect>
<aop:aspect ref="testAop" order="1">
<aop:before method="getBes" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
7.测试类测试结果(同上)
AOP专业术语中(基于注解版的AOP理解)
1.通知分类
①前置通知(@Before):在目标对象方法执行之前添加的操作
@Before("execution(int com.atguigu.spring.aop.CaluaImpl.add(int,int))")
public void getBefore(JoinPoint joinPoint){
//JoinPoint --->连接点execution(int com.atguigu.spring.aop.Calua.add(int,int))
//joinPoint.getSignature()--->获取完整的信息int com.atguigu.spring.aop.Calua.add(int,int)
Signature signature = joinPoint.getSignature();
System.out.println("前置通知的方法名"+signature.getName());
}
②后置通知(@After):在目标对象方法执行的finally语句中添加的操作
/**
* 此注解标识为后置通知
* @param joinPoint
*/
@After("pointcut()")
public void getAfter(JoinPoint joinPoint){
System.out.println("后置通知的方法名"+joinPoint.getSignature().getName());
}
③异常通知(@AfterThrowing):在目标对象方法执行的catch语句中添加的操作--->有两个参数
/**
* public class Exception extends Throwable
* Throwable是Exception父类(最大类)
* value属性设置切入点表达式
* throwing属性将参数列表中的某个参数设置为接收目标对象方法所出现的异常信息
* @param joinPoint
* @param ex
*/
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void getAfterThrowing(JoinPoint joinPoint,Throwable ex){
System.out.println("异常通知的方法名"+joinPoint.getSignature().getName());
System.out.println("异常通知的异常信息"+ex);
}
④返回通知(@AfterReturning):在目标对象方法执行之后返回值之后添加的操作--->有两个参数
/**
*
* @AfterReturning将方法标识为返回通知方法
* value属性设置切入点表达式
* returning属性将参数列表中的某个参数设置为接收目标对象方法返回值
* 返回值因为不确定类型,所有是objec
*/
@AfterReturning(value = "pointcut()",returning = "result")
public void getAfterThrowing(JoinPoint joinPoint, Object result){
System.out.println("返回通知的方法名"+joinPoint.getSignature().getName());
System.out.println("返回通知的返回值"+result);
}
⑤环绕通知(@Around):在目标对象方法执行之前、之后、有异常时、finally中添加的操作
/**
* @Around将方法标识为环绕通知方法
* public interface ProceedingJoinPoint extends JoinPoint {}
* 环绕通知的要求:返回值必须为Object,参数必须是ProceedingJoinPoint(它继承JoinPoint,方法多余JoinPoint)
* 环绕通知不和其他通知连用,要么用其他4个通知,要么单独用环绕通知
* @param joinPoint
* @return
*/
@Around("pointcut()")
public Object getAround(ProceedingJoinPoint joinPoint){
Object proceed=null;
try {
System.out.println("前置通知11");
//表示当前的方法执行
proceed= joinPoint.proceed();
System.out.println("返回通知11");
} catch (Throwable throwable) {
System.out.println("异常通知11");
throwable.printStackTrace();
} finally {
System.out.println("后置通知11");
}
return proceed;
}
2.获取连接点的信息
那些通知可以获取连接点:前置通知,后置通知,异常通知,返回通知
获取的方法:JoinPoint参数获取
@Before("execution(int com.atguigu.spring.aop.CaluaImpl.add(int,int))")
public void getBefore(JoinPoint joinPoint){
//JoinPoint --->连接点execution(int com.atguigu.spring.aop.Calua.add(int,int))
//joinPoint.getSignature()--->获取完整的信息int com.atguigu.spring.aop.Calua.add(int,int)
Signature signature = joinPoint.getSignature();
System.out.println("前置通知的方法名"+signature.getName());
}
连接点JoinPoint常用的方法
joinPoint.getSignature().getName():获取连接点所对应的方法的方法名
joinPoint.getArgs():获取连接点所对应的方法的参数
3.切入点表达式
在标识各种通知的注解中,设置切入点表达式("execution(* com.atguigu.aop.annotation.*.*(..))")
@Before("execution(* com.atguigu.aop.annotation.*.*(..))")
其中第一个*表示任意的返回值类型
第二个*表示包下任意的类
第三个*表示类中任意的方法
..表示任意的参数列表
切入点表达式的重用
//提取公共切入点方法,为下面的前置,后置,异常通知调用
@Pointcut("execution(* com.atguigu.spring.aop.*.*(..))")
public void pointcut(){}
4.切面的优先级
切面的优先级用来指定切面中的通知作用于连接点的顺序,每个切面都有默认的优先级,Integer类型的最大值
可以通过@Order注解设置切面的优先级,值越小优先级越高
@Component
@Aspect
@Order(1)//值越小优先级越高,默认为2147483647(integer的最大值)
public class TestAop {
@Before("execution(int com.atguigu.spring.aop.CaluaImpl.*(int,int))")
public void getBes(){
System.out.println("测试切面的优先级");
}
}
5.AOP对IOC容器中bean的影响(通俗来讲,我们虽然把目标对象的实现类放入了IOC,但我们获取不到,我们调用只能是代理对象,也就是目标对象(接口)的类型)
在使用了AOP之后,IOC容器中不会存在目标对象的bean,只会有目标对象所对应的代理对象的bean
例如:在CalculatorImpl上加注解@Component,并且通过扫描扫描组件,
若此时通过AOP将CalculatorImpl作为了目标对象,那么通过IOC容器是无
法直接获取CalculatorImpl所对应的bean,只能获取CalculatorImpl所对应的代理对象的bean
事务管理
什么是事务?
概念:事务时属于关系型数据库独有的,默认情况一个sql语句独占事务,并且自动提交
事务管理:用一个事务表示一个完整功能,这个功能中可能会有多个sql语句组成,事务管理要求它们要么一起执行成功,要么(有一个没有执行)都不执行
特性(ACID4个):
原子性:事务时关系型中最小的单元
一致性:当前事务以执行和回滚,如果都成功)
隔离性:事务与事务直接--->并发(与隔离级别有关)
隔离级别(4种)
读未提交:脏读,不可重复读,幻读
读已提交:不可重复读,幻读(Oracle的默认级别)
可重复读:幻读(MySQL的默认级别)
序列化:效率慢
注意:mysql中可重复读的隔离级别不会出现幻读的问题
持久性:事务一旦提交,它对数据库的改变就应该是永久性的
编程式事务:通过编写事务代码实现处理某个功能的事务
声明式事务::sprin中式通过AOP来实现事务管理的,因此只需要将事务的功能作用于需要被事务管理的方法即可
1.学习Spring封装的jdbc--->jdbcTemplate
①导入依赖
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 整合spring和junit4 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver:管理切面的 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
②配置spring配置文件spring-jdbc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:comtext="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部资源 -->
<comtext:property-placeholder location="classpath:jdbc.properties"/>
<!--封装数据源bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--把spring自带的jdbcTemplate存入ioc容器-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
③测试jdbcTemplate
import com.atguigu.spring.jdbcTemplate.Dept;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)//整合junit4
@ContextConfiguration("classpath:spring-jdbc.xml")//读取配置类
public class JdbcTemplateTest {
//因为已经在ioc里面存入了相对应的bean,并且已经和junit4整合
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 查询一条
*/
@Test
public void getBySelect(){
String sql="SELECT * FROM c_dept WHERE d_id = ?";
Dept dept = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Dept.class),1);
System.out.println(dept);
}
/**
* 修改一条
*/
@Test
public void getByUpDept(){
String sql ="UPDATE c_dept SET d_name=? WHERE d_id=?";
int update = jdbcTemplate.update(sql,"baby",1);
System.out.println(update);
}
/**
* 查询所有
*/
@Test
public void getSecletByAll(){
String sql="select * from c_dept";
//这里面式一个实体类.calss
List<Dept> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Dept.class));
System.out.println(query);
}
}
Spring基于注解的声明式事务
1.导入jar
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!--事务管理的-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 整合spring和junit4 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver:管理切面的 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
2.在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:comtext="http://www.springframework.org/schema/context" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<comtext:component-scan base-package="com.atguigu.spring.tx"/>
<!--引入外部资源 -->
<comtext:property-placeholder location="classpath:jdbc.properties"/>
<!--封装数据源bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--把spring自带的jdbcTemplate存入ioc容器-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器对象(切面类),对事务进行统一管理 DataSourceTransactionManager(配置一个数据源管理器)-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
开启事务的注解驱动
可以将通过@Transactional注解所标识的方法作为连接点,使用事务通知进行事务管理
@Transactional注解的位置(service层):
标识在方法上,则该方法会被事务管理
标识在类上,则该类中所有的方法都会被事务管理
-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
3.在业务层上添加事务注解@Transactional
import com.atguigu.spring.tx.dao.BuyDao;
import com.atguigu.spring.tx.service.BuyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BuyServiceImpl implements BuyService {
@Autowired
private BuyDao buyDao;
@Override
@Transactional
public void buyBooks(Integer bookId, Integer clientId) {
//1.回显图书价格
int price=buyDao.getSellById(bookId);
//2.修改图书的销量
int bookSucceed =buyDao.getBookStock(bookId);
//3.减少用户的余额
int clientSucceed=buyDao.getClient(clientId,price);
}
}
4.结果虽然报异常,但是数据库的数据没有更改,事务成功
事务的隔离级别(@Transactional注解的属性isolation )
1.isolation属性时用来设置数据库的事务隔离级别(5种)
isolation = Isolation.DEFAULT--->使用当前数据库的默认级别
isolation = Isolation.READ_UNCOMMITTED--->读未提交
isolation = Isolation.READ_COMMITTED---->读已提交
isolation = Isolation.REPEATABLE_READ--->不可重复读
isolation = Isolation.SERIALIZABLE--->序列化
事务的传播行为(@Transactional注解的属性propagation )--->只有当该事务被调用的时候,传播行为才会产生作用
1.当事务方法A里面调用事务方法B,A就会将本身的事务传播到B方法中,而B(上面添加注解属性)如何使用事务就是事务的传播行为
propagation=Propagation.REQUIRED:默认的传播行为,使用A事务
结果:只要当前的操作有一个失败的情况,则全部回滚
propagation:Propapgation.REQUIRES_NEW:使用B的事务
结果:当前的事务回滚只在B范围内执行,对A中其他操作没有影响
测试案例:
buyBooks()此事务的意思时买一本书,buyBookChecks是另一个新事物,意思是买2本不同id的书籍,它调用了两次buyBooks()事务,当我们在被调用事务上buyBooks()上添加propagation=Propagation.REQUIRES_NEW的时候,当我们的钱只够第一次购买书籍的时候,第一次完美执行,第二次钱不够会报异常,此时数据库也会把第一次执行的数据修改。
@Service
public class BuyServiceImpl implements BuyService {
@Autowired
private BuyDao buyDao;
@Autowired
private BuyController buyController;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW) //当此事务是被调用者才会产生作用
public void buyBooks(Integer bookId, Integer clientId) {
//1.回显图书价格
int price=buyDao.getSellById(bookId);
//2.修改图书的销量
int bookSucceed =buyDao.getBookStock(bookId);
//3.减少用户的余额
int clientSucceed=buyDao.getClient(clientId,price);
}
@Transactional(isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED
)
public void buyBookChecks(int[] bookIds, Integer clientId) {
//我这是买2本不同的书,每次也就买一本,刚好调用上一次的买书的方法
for (int bookId : bookIds) {
buyController.buyBooks(bookId,clientId); //A调用B事务,B事务传播行为propagation才会产生作用
}
}
}
事务的回滚策略(4种,但是前两种一般不用,因为事务回滚的出发点就是异常,任何异常都会造成事务回滚)
1.设置导致回滚的异常(一般不用)
rollbackFor:设置导致回滚的异常的class对象
rollbackForClassName:设置导致回滚的异常的全类名
2.设置不导致回滚的异常
noRollbackFor:设置不导致回滚的异常的class对象(jArithmeticException。class)
noRollbackForClassName:设置不导致回滚的异常的全类名(eg:java.lang.ArithmeticException)
事务的超时(timeout)
超过指定时间,若事务还没有执行完毕,则强制回滚,并抛出异常:TransactionTimedOutException
通过timeout设置事务的超时时间,默认值为-1,表示永不超时
这里是用Thread.sleep()线程休眠测试
@Transactional(isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED
noRollbackFor = {ArithmeticException.class}
noRollbackForClassName = "java.lang.ArithmeticException"
timeout = 3 //时间3秒之内
)
public void buyBookChecks(int[] bookIds, Integer clientId) {
我这是买2本不同的书,每次也就买一本,刚好调用上一次的买书的方法
try {
Thread.sleep(5000);//线程休眠5000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int bookId : bookIds) {
buyController.buyBooks(bookId,clientId);
假设我们要保留50元,作为回家路费
每次买书后都要进行余额比较
int money=buyDao.getClientByMoney(clientId);
if (money<50){
throw new RuntimeException("路费不够");
}
}
}
事务的只读(readOnly )---->只针对查询功能
作用:提高数据的性能,这是mysql层面上优化查询功能
通过readOnly属性设置事务的只读,默认为false;纯查询的事务很少
基于XML的声明式事务(要导入切面管理的jar--->spring-aspects)
1.代码内容和基于注解版的声明式事务意义,只是去掉了service层所有的@Transactional注解
@Service
public class BuyServiceImpl implements BuyService {
@Autowired
private BuyDao buyDao;
@Autowired
private BuyController buyController;
@Override
public void buyBooks(Integer bookId, Integer clientId) {
//1.回显图书价格
int price=buyDao.getSellById(bookId);
//2.修改图书的销量
int bookSucceed =buyDao.getBookStock(bookId);
//3.减少用户的余额
int clientSucceed=buyDao.getClient(clientId,price);
}
public void buyBookChecks(int[] bookIds, Integer clientId) {
for (int bookId : bookIds) {
//购买2本不同的图书,
buyController.buyBooks(bookId,clientId);
}
}
}
2.配置spring的配置文件xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.atguigu.spring.xml"></context:component-scan>
<!--
<aop:config>:aop的设置标签,里面可以设置aop相关属性
<aop:pointcut>:aop提取的公共切入发,下面的标签好使用
<aop:aspect>:切面标签
属性:ref:组件名;
order:切面的优先级
<aop:before>:前置通知标签
<aop:after>:后置通知标签
<aop:after-returning>:返回通知标签
<aop:after-throwing>:异常通知标签
<aop:around>:环绕通知标签
属性:method:方法名
pointcut-ref:引用公共切入点
returning:返回值属性
throwing:异常值属性
-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring.xml.*.*(..))"/>
<aop:aspect ref="anntationAop">
<aop:before method="getBefore" pointcut-ref="pointcut"></aop:before>
<aop:after method="getAfter" pointcut-ref="pointcut"/>
<aop:after-returning method="getAfterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="getAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
<aop:around method="getAround" pointcut-ref="pointcut" />
</aop:aspect>
<aop:aspect ref="testAop" order="1">
<aop:before method="getBes" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
3.写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-tx-xml.xml")
public class BuyXmlTest {
@Autowired
private BuyController buyController;
@Test
public void buyTest(){
int[] bookIds = new int[]{1,2};
buyController.checkBooks(bookIds,1);
}
}
JSR305标准相关注解
JSR:Java规范提案,JSR已成为Java界的一个重要标准
Spring是从5.0以后开始支持JSR 305规范
相关注解(4个)
Spring整合junit5
1.导入相关依赖
<!--整合junit5的相关2个jar-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
2.创建测试类
①用单标签注解
//@ExtendWith(SpringExtension.class) 表示使用 Spring 提供的扩展功能。
//@ContextConfiguration(value = {"classpath:spring-context.xml"}) 还是用来指定 Spring 配置文件位置,和整合 junit4 一样。
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring-tx-xml.xml")
public class SpringAndJunitTest {
@Autowired
private BuyController buyController;
@Test
public void buyTest(){
int[] bookIds = new int[]{1,2};
buyController.checkBooks(bookIds,1);
}
}
②用复合标签注解
//@SpringJUnitConfig 注解综合了前面两个注解的功能,此时指定 Spring 配置文件位置即可。
// 但是注意此时需要使用 locations 属性,不是 value 属性了。
@SpringJUnitConfig(locations = {"classpath:spring-tx-xml.xml"})
public class SpringAndJunitTest {
@Autowired
private BuyController buyController;
@Test
public void buyTest(){
int[] bookIds = new int[]{1,2};
buyController.checkBooks(bookIds,1);
}
}
注意:当一个maven项目中有多个不同版主的Junit的时候,要看导入import的jar是否导入错误
import org.junit.jupiter.api.Test; //这是Junit5的
import org.junit.Test; //这是Junit4的