文章目录
1.1框架简介
什么是框架?
框就是把东西框起来,也就是“约束性”
架就是支撑,所以可以把架理解成“支撑性”
所以说框架就是具有约束性的去支撑我们完成各种项目的半成品软件
约束性就是框架会定义一些标准,我们要使用框架,就得遵循这些标准
Spring的底层就是对Java底层的各种功能实现了封装
半成品软件是指,他具体的功能都有,但是缺少业务逻辑
所以框架+业务逻辑就是一个完整的项目
MVC:客户端发送一个请求,由控制层进行处理,由模型层装载并传输数据,并且在视图层进行展示
常见的框架有:
Struts1、Struts2、hibernate、spring、springMVC、mybatis
MVC 框架:struts1、struts2、springMVC
持久层框架:hibernate、mybatis
整合型框架,设计型框架:spring
1.2 Spring 概述
-
Spring 是一个开源框架
-
Spring 为简化企业级开发而生,使用Spring, JavaBean 就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁。
-
Spring 是一个IOC(DI)和AOP容器框架。
-
Spring 的优良特性
①非侵入式: 基于Spring开发的应用中的对象可以不依赖于Spring的API②依赖注入: DI------Dependency Injection,反转控制(IOC)最经典的实现。 DI 和 IOC的关系就是:IOC是一种思想,DI 是 IOC的具体实现
③面向切面编程 : Aspect Oriented Programming-- AOP
④容器: Spring是一个容器,因为它包含并且管理应用对象的生命周期
⑤组件化: Spring 实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象。
⑥ 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
- 所谓轻量级就是你使用了之后对你原来的技术不会造成影响
- OOP叫做面向对象编程,因为存在某些缺陷,所以就有了AOP对OOP进行补充
- Spring里面的组件,指的就是Spring管理的对象
- Spring模块
- 在开发中尽量使用包装类型,因为数据库里面的东西会有NULL,而基本数据类型没有
1.3 环境配置
新建一个正常的Java项目导入依赖即可
在pom.xml中导入依赖步骤:
- 百度 Spring Maven(直接进也可以:https://mvnrepository.com/search?q=spring)
- 然后点开第五个Spring Web MVC
然后下面图片里的随便点开一个即可,我点人数多的那个
点开之后,这个就是我们需要的依赖,导入到pom.xml中:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
复制之后等待一会,导入完之后如下图:
3. 配置applicationContext.xml
1. 进入Spring官网(https://spring.io/)
2. 点击Projects->Spring Framework
3. 切换到LEARN
点击Reference DOC—>点击Core
—> 点击左侧的1.2.1,如下图
然后往下翻一点点,你就可以发现需要配置的内容了,如下图:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
- 在Resource下面新建一个File名为applicationContext.xml的文件,如下图:
再把上面文档的内容复制进去,完成之后如下图:
1.4 环境测试 SpringTest
-
在main —> Java 里面新建一个Java文件(com.xp.Hello)
package com.xp; public class Hello { String name="xp"; public void hi(){ System.out.println(name+"你好!"); } }
-
在前面建的applicationContext.xml文件中如下配置
<bean id="hello" class="com.xp.Hello"> </bean>
-
要使用@Test注解先导入依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>RELEASE</version> </dependency>
-
在test—>Java 里面新建一个文件(TestHello)
import com.xp.Hello; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestHello { @Test public void test1() throws Exception { /** * 1.加载Spring的配置文件 * 2.取出Bean容器中的实例 * 3.调用bean方法 */ // 1.加载Spring的配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 2.取出Bean容器中的实例 Hello hello = (Hello) context.getBean("hello"); // 3.调用bean方法 hello.hi(); } }
-
测试结果,目录结构:
2.1 控制反转(IOC)
什么是控制反转:就是本该我们程序员自己管理对象,我们却交给了程序本身,Spring去管理他
依赖注入:注入就是赋值
2.1.1 ApplicationContext 的主要实现类
(1) ClassPathXmlApplicationContext:对应类路径下的xml格式的配置文件,是相对路径
(2) FileSystemXMLApplicationCntext:对应文件系统中的XML格式的配置文件
(3)在初始时就创建的单例的bean ,也可以通过配置的方式指定创建的Bean是多实例的
2.1.2 ConfigurableApplicationContext
(1)是ApplicationContext的子接口,包含 一些扩展方法
(2)refresh() 和 close() 让ApplicationContext 具有启动、关闭和刷新上下文的能力
2.1.3 WebApplicationContext
(1)专门为 WEB 应用准备的,它允许从相对于 WEB 根目录的路径完成初始化工作
2.2 给 bean 的属性赋值
(1)从IOC容器中获取Bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在xml文件中配置了多个,则会抛出异常,所以同一个类型的bean在容器中必须是唯一的
HelloWord helloWord=cxt.getBean(HelloWord.class)
(2)也可以使用另外一个重载的方法,同时指定 bean 的 id 值和类型
HelloWord helloWord=cxt.getBean("helloWord",HelloWord.class)
2.3 给Bean的属性赋值
通过<property name="" value="">
属性给对象赋值的时候,name应该填的是对应Set方法后面的名字,比如,SetId() 方法,填的就应该是id。<property>
标签也只会通过set方法注入
2.3.1 依赖注入的方式
- 通过 bean 的 setXxx() 方法赋值
<bean id="student" class="com.xp.di"> <property name="id" value="123"></property> <property name="name" value="张三"></property> <property name="age" value="18"></property> </bean>
- 通过 bean 的构造器赋值
(1)Spring 自动匹配合适的构造器
Student 中的构造方法:
public Student(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
Spring会自动匹配上面的构造方法
<bean id="student" class="com.xp.di">
<constructor-arg value="123"/>
<constructor-arg value="张三"/>
<constructor-arg value="18"/>
</bean>
如果再多一个构造方法如下,和上面的构造方法数量一样,我们本意是想通过这个方法赋值的,可能就会找到上面的方法,这时候可以通过index + type 区分,如下:
public Student(Integer id, String name, Double score) {
this.id = id;
this.name = name;
this.score = score;
}
(2)通过索引值指定参数位置
<bean id="student" class="com.xp.di">
<constructor-arg value="123" index=0/>
<constructor-arg value="张三" index=1/>
<constructor-arg value="18" index=2/>
</bean>
(3)通过类型区分重载的构造器,如下面的score是Double类型,这样就能区分上面的那种构造方法了
<bean id="student" class="com.xp.di">
<constructor-arg value="123" index=0/>
<constructor-arg value="张三" index=1/>
<constructor-arg value="90" index=2 type="java.lang.Double"/>
</bean>
2.3.2 p命名空间
为了简化XMl文件的配置,越来越多的XML文件采用属性而非子元素配置信息,Spring 从 2.5 版本开始引入了一个新的p命名空间,可以通过 <bean>
元素属性的方式配置 Bean 的属性
先配置xml文件,在 beans标签后面添加:
xmlns:p="http://www.springframework.org/schema/p"
xml文件整体如下:
其实这种添加都是有小技巧的,就是xmlns:p="http://www.springframework.org/schema/p
复制这句话,把p替换成你想添加的那个就行了
使用 p 命名空间后,基于 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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="Student" p:id="100" p:age="18" p:name="张三">
</bean>
</beans>
2.3.3 可以使用的值
(1) 字面量
字面量就是你能够把值写成字符串形式的就叫字面量
可以使用字符串表示的值,可以通过 value 属性或 value 字节点的方式指定,如
<bean id="student" class="com.xp.di">
<!--通过 value 属性赋值-->
<property name="id" value="123"></property>
<property name="name" value="张三"></property>
<!--通过 value 字节点的方式赋值-->
<property name="age">
<value>18</value>
</property>
</bean>
```
(2)**null 值**
```xml
<bean id="student" class="Student">
<property name="id" value="100"></property>
<property name="age">
<null/>
</property>
</bean>
(3)外部已声明的bean ,引用其他的bean
比如在Student中多加一个 Student 属性,Student 是个引用类型,就不能通过 value 直接赋值了,使用应该如下
<bean id="teacher" class="Teacher">
<property name="tid" value="1001"></property>
<property name="age" value="30"></property>
</bean>
<bean id="student" class="Student">
<property name="teacher" ref="teacher"/>
</bean>
(4)给 bean 的级联属性赋值
<bean id="teacher" class="Teacher">
<property name="tid" value="1001"></property>
<property name="age" value="30"></property>
</bean>
<bean id="student" class="Student">
<property name="teacher" ref="teacher"></property>
<property name="teacher.age" value="32"></property>
</bean>
值得注意的是:为级联属性赋值,属性先要初始化之后才可以为级联属性赋值,否则会有异常。
(5)内部bean
当 bean 实例仅仅给一个特定的属性使用时,可以将其声明为内部 bean ,内部 bean 声明直接包含在 <property> 和 <constructor-arg>
元素里,不需要设置任何 id 和 name 属性
可以对比上面的写法,你会发现,这里就是把 bean 写到了 property 内部而已
<bean id="student" class="Student">
<property name="teacher">
<bean id="teacher" class="Teacher">
<property name="tid" value="1001"></property>
<property name="age" value="30"></property>
</bean>
</property>
</bean>
2.4 集合属性
注意:在要用到setXxx()的时候,变量的命名一定不能一个小写字母,然后跟大写,比如sList就是错误的命名,在xml配置的时候会报错,如果没有报错的话,说明是 Idea 自动给你改成了大写
2.4.1 数组和List
配置 java.util.List 类型的属性,需要指定 <list>
标签,在标签里包含一些元素,这些标签可以通过 <value>
指定简单的常量值,通过 <ref>
指定对其他 Bean 的引用,通过 <bean>
指定内置 bean 定义,通过 <null/>
指定空元素。甚至可以内嵌其他集合
数组的定义和 List 一样,都是用 <list>
元素
配置 java.util.Set 需要使用 <set>
标签,定义的方法与 List 一样
<bean id="teacher" class="Teacher">
<!-- List 简单的常量值注入-->
<property name="cls">
<list>
<value>A</value>
<value>B</value>
<value>C</value>
</list>
</property>
<!-- 对其他 Bean 的引用 -->
<property name="studentList">
<list>
<ref bean="s1"/>
<ref bean="s2"/>
</list>
</property>
<!-- 数组第一种写法,数组也可以直接使用list标签定义 -->
<property name="bookID">
<array>
<value>1</value>
<value>2</value>
</array>
</property>
</bean>
2.4.2 Map
可以将 Map 的键和值作为 <entry>
的属性定义:简单常量使用 key 和 value 来定义。bean 可以通过 key-ref 和 value-ref 属性定义
<property name="mp">
<map>
<entry>
<key>
<value>A</value>
</key>
<value>1001</value>
</entry>
</map>
</property>
2.4.3 集合类型的 bean
如果只能将集合对称配置在某个 bean 内部,则这个集合的配置将不能重用。我们需要将集合 bean 的配置拿到外面,供其他 bean 引用。
配置集合类型的 bean 需要引入 util 名称空间
<util:list id="studentList">
<ref bean="s1"/>
<ref bean="s2"/>
</util:list>
<util:list id="cls">
<value>A</value>
<value>B</value>
</util:list>
<bean id="teacher2" class="Teacher">
<property name="studentList" ref="studentList"></property>
<property name="cls" ref="cls"></property>
</bean>
2.5 FactoryBean
Spring 中有两种类型的 bean ,一种是普通 bean,另一种是工厂 bean ,即 FactoryBean。
普通 bean: 在配置文件中定义 bean 类型就是返回类型
工厂 bean: 在配置文件定义 bean 类型可以和返回类型不一样
工厂 bean 跟普通 bean 不同,其返回的对象不是指定类的一个实例。其返回的是该工厂 bean 的 getObject 方法所返回的对象。
工厂 bean 必须实现 org.springframework.beans.factory.FactoryBean 接口。
案例演示:
第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
Car 类:
public class Car {
private String Brand;
private Double price;
....
MyFactory 类:
import org.springframework.beans.factory.FactoryBean;
public class MyFactory implements FactoryBean<Car> {
//将创建好的bean返回给IOC容器
public Car getObject() throws Exception {
Car car=new Car();
car.setBrand("奥迪");
car.setPrice(100000.0);
return car;
}
//返回 bean 类型
public Class<?> getObjectType() {
return Car.class;
}
//是否是单例模式
public boolean isSingleton() {
return false;
}
}
xml 配置:
<bean id="factory" class="MyFactory"></bean>
测试类
@Test
public void testFactory()
{
// 1.加载Spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("Factory.xml");
// 2.取出Bean容器中的实例
Object object=context.getBean("factory");
// 3.调用bean方法
System.out.println(object);
}
虽然配置的类是 Myfactory,但实际输出的 object 类型是Car
在测试类这里要注意一下,在使用 contexsuirdjkoepry")的时候,不要指定具体的类型,比如你要指定 context.getBean(“factory”,MyFactory.class)就会报错。如果一定要指定具体的类型,应该这样写:Car car=context.getBean(“factory”,Car.class);
因为工厂 bean 跟普通 bean 不同,其返回的对象不是指定类的一个实例。其返回的是该工厂 bean 的 getObject 方法所返回的对象。
2.6 bean 的作用域
在Spring中,bean作用域用于确定哪种类型的bean实例应该从Spring容器中返回给调用者
在 Spring 中,可以在 <bean>
元素的 scope 属性里设置 bean 的作用域,以决定这个 bean 是单实例的还是多实例的
默认情况下,Spring 只为每个在 IOC 容器里声明的 bean 创建唯一一个实例,整个 IOC 容器范围内都能共享该实例:所有后续的 getBean() 调用和 bean 引用都将返回这个唯一的 bean 实例。该作用域被称为 singleton ,他是所有 bean 的默认作用域
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
application | 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。 |
当 bean 的作用域是单例的时候,Spring 会在容器创建的时候就创建 bean 的实例对象。而当 bean 的作用域是多例的时候, IOC 容器会在获取 bean 实例时创建 bean 的实例对象
2.7 bean 的生命周期
(1)Spring IOC 容器可以管理 bean 的生命周期,Spring 允许在 bean 生命周期内特定时间点执行特定任务
(2)Spring IOC 容器对 bean 的生命周期进行管理的过程:
① 通过构造器或工厂方法创建 bean 实例(无参数构造)
② 为 bean 属性设置值和其他 bean 的引用(调用 set 方法)
③ 调用 bean 的初始方法(需要进行配置初始化的方法)
④ 使用 bean(对象获取到了)
⑤ 当容器关闭时,调用 bean 的销毁方法(需要进行配置销毁的方法)
案例演示 bean 的生命周期:
public class Orders {
String name;
public Orders() {
System.out.println("第一步:构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("第二步:调用 set 方法");
this.name = name;
}
public void init(){
System.out.println("第三步:初始化方法");
}
public void destory(){
System.out.println("第五步:销毁方法");
}
}
配置文件:
<bean id="orders" class="com.xp.lifecycle.Orders" init-method="init" destroy-method="destory">
<property name="name" value="张三"></property>
</bean>
测试:
@Test
public void test3(){
//AbstractApplicationContext context=new ClassPathXmlApplicationContext("cycle.xml");
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("cycle.xml");
Orders orders=context.getBean("orders", Orders.class);
System.out.println("第四步:使用 bean "+orders.getName());
// 手动让 bean 实例销毁
context.close();
}
注意测试不能写 ApplicationContext ,因为 context.close()这个 close 方法是从 AbstractApplicationContext 才开始实现的
测试结果:
(3)在配置 bean 时,通过 init-method 和 destroy-method 属性为 bean 指定初始化和销毁方法
(4)bean 的后置处理器
① bean 的后置处理器允许在调用初始方法前后对 bean 进行额外的处理
② bean 后置处理器对 IOC 容器里的所有 bean 实例逐一处理,而非单一实例。意思就是:设置了后置处理器之后,就对Spring管理的每个对象都有效果,而非单个 Bean 或者某多个 Bean。其典型应用是:检查 bean 属性的正确性或根据特定的标准更改 bean 的属性
③ bean 后置处理器需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor 。在初始化方法被调用前后,Spring 将每个 bean 实例分别传递给上述接口的以下两个方法:
- public Object postProcessBeforeInitialization(Object bean, String beanName)
- public Object postProcessAfterInitialization(Object bean, String beanName)
注意在实现这两个方法的时候,可能在实现接口之后,Idea 不会报红,那就直接点进源码,复制这两个方法出来改改
代码示例如下,记得在xml中配置这个类,只有在xml中配置
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
public class AfterHandler implements BeanPostProcessor {
//初始化之前,这个返回对象就是经过处理的新的 bean
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Person person=(Person)bean;
person.setAge(18);
return bean;
}
//初始化之后
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
(5) 添加 bean 后置处理器后 bean 的生命周期
① 通过构造器或工厂方法创建 bean 实例
② 为 bean 的属性设置值和其他 bean 的引用
③ 将 bean 的实例传递给 bean 后置处理器的 postProcessBeforeInitialization 方法
④ 调用 bean 的初始化方法
⑤ 将 bean 实例传递给 bean 后置处理器的 postProcessAfterInitialization 方法
⑥ 使用 bean
⑦ 当容器关闭时调用 bean 的销毁方法
2.8 引用外部属性文件
当 bean 的配置信息逐渐增多时,查找和修改一些 bean 的配置信息就变得愈加困难。这时可以将一部分信息提取到 bean 配置文件的外部,以 properties 格式的属性文件保存起来,同时在 bean 配置文件中引用 properties 属性文件中的内容,从而实现一部分属性值在发生变化时仅修改 properties 属性文件即可。这种技术多用于连接数据库的基本信息的配置
2.8.1 直接配置
要用到一个 druid 这样的jar包,先在Idea导入依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
顺便记得导入mysql数据库连接的依赖,不然运行就会报错,如下
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
接下来就是在xml文件(我命名为datasource.xml)直接配置的代码,如下
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
测试的代码:
public class MyTest {
@Test
public void testDataSource() throws SQLException {
// 1.加载Spring的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("datasource.xml");
// 2.取出Bean容器中的实例
DruidDataSource bean = context.getBean("dataSource",DruidDataSource.class);
// 3.调用bean方法
System.out.println(bean.getConnection());
}
}
2.8.2 使用外部的属性文件
1.创建 properties 属性文件
prop.userName=root
prop.password=123456
prop.url=jdbc:mysql:///test
prop.driveClass=com.mysql.jdbc.Driver
2.指定 properties 属性文件的位置
有两种方式:
① 第一种(现在已经不推荐使用了):
<!-- 加载资源文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="db.properties"></property>
</bean>
② 第二种:
<context:property-placeholder location="db.properties"/>
3.从 properties 属性文件中引入属性值
Tip:${}为固定语法,从jdbc配置文件中加载值
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driveClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
测试的代码和上面是一样的
2.9 自动装配
2.9.1 自动装配的概念
(1)手动装配:以 value 或 ref 的方式明确指定属性值都是手动装配的
(2)自动装配:根据指定的装配规则,不需要明确指定,Spring 自动将匹配的属性值注入 bean 中。
自动装配又叫自动注入、自动赋值
自动装配只针对非字面量的属性,也就是需要用 ref 的那些属性
2.9.2 装配模式
(1)根据类型自动装配:将类型匹配的 bean 作为属性注入到另一个 bean 中,若 IOC 容器中有多个与目标 bean 类型一致的 bean ,Spring 将无法判断哪个 bean 最适合该属性,所以不能执行自动装配
(2)根据名称自动装配:必须将目标 bean 的名称与属性名设置的完全相同(反正我测试出来应该是 Set方法后面的名字相同才对,这是老师的笔记)
(3)通过构造器自动装配:当 bean 中存在多个构造器的时候 ,此种自动装配方式将会很复杂,不推荐使用
自动装配语法:在 bean 标签后 autowire=“byType” 或者 autowire=“byName”
byName:根据名称自动装配,只要需要赋值的属性名(其实是Set方法后面的名称)和你xml里面的bean 的 id一样,就可以自动找得到
也就是说,下面的 setCls 中的 Cls 和第二张图的 id 必须一致:
byType:通过类型自动装配,和 bean 后面的 class 有关,不仅可以给 type 类型的类赋值,还可以给他的父类或者他所实现的接口赋值(兼容性)
2.9.3 选用建议
相对于使用注解的方式实现的自动装配,在 XML 文档中进行的自动装配略显笨拙,项目中更多的使用注解的方式实现
通过xml自动注入有个很大的缺陷就是,当设置 autowire属性,会作用于该bean中所有的非字面量属性,因此 ByType 和 ByName 两个都不建议使用
Tip:Spring 里管理的对象都是针对类的,不能针对接口和抽象类
2.10 通过注解配置的 bean
2.10.1 概述
相对于 XML 而言,通过注解的方式配置 bean 更加简洁优雅,而且和 MVC 组件化开发的理念十分契合,是开发中常用的方式
2.10.2 使用注解标识组件
(1)普通组件:@Component
标识一个受 Spring IOC 容器管理的组件
(2)持久化组件:@Repository
标识一个受 Spring IOC 容器管理的持久化组件
(3)业务逻辑层组价::@Service
标识一个受 Spring IOC 容器管理的业务逻辑层组件
(4)表述层控制组件:@Controller
表述一个受 Spring IOC 容器管理的表述层控制器组件
(5)组件命名规则:
① 默认情况下:会自动在 Spring 的配置文件中生成相对应的 bean ,这些 bean 的 id 会以类的首字母小写为值
② 使用组件注解的 value 属性指定 bean 的 id
注意:事实上 Spring 并没有能力识别一个组件到底是不是他所标记的类型,也就是说上面的四种组件,只是为了业务逻辑上的区分,见名知意,功能上都是一样的,甚至可以互相交换着写(业务逻辑允许的话)
Tip:
Spring 里面的组件只有对象,所以看到组件可以直接理解为对象
我们平时见到的注解要么加在类上的,要么加在方法上的,要么加在属性上的
控制层接收请求,再调用业务层处理业务逻辑,再调用 Dao 来实现数据持久化,把最终持久化的结果返回给 Service ,再返回给控制层,最终响应到客户端
代码演示:
1.先在xml文件里加上这句话,扫描整个包,因为一个项目如果 bean 很多的话,一个个配置很麻烦
如果有多个包需要扫描的时候,可以逗号隔开
<context:component-scan base-package="com.xp.ioc.userMethod"></context:component-scan>
2.然后在类上面加上注解,例如,ServiceDao 就加 @Service 注解,如下
@Service
public class ServiceDao {
public ServiceDao() {
System.out.println("ServiceDao 层已经创建");
}
}
然后测试就行了
2.10.3 扫描组件之包含和排除
<context:component-scan>
:扫描组件,对设置的宝下面的类进行扫描,会将加上注解的类作为 Spring 的组件进行加载
-
base-package 属性指定一个需要扫描的基类包,Spring 容器会扫描这个基类包里及其子包中的所有类
-
当需要扫描多个包的时候,可以使用逗号隔开
-
如果仅希望扫描特定的类而非基包下的所有类可以使用 resource - pattern 属性过滤特定的类,示例:
还可以使用子标签:
<context:include-filter>
:子节点要包含的目标类
注意:在使用包含的时候,一定要设置use-default-filters="false"
<context:exclude-filter>
:子节点要排除在外的目标类
注意: 在使用排除时,一定要设置use-default-filters="false"
,或者不加这句话也可以(这句话的意思是,将默认的过滤也即扫描包下所有的类打开)<context:component-scan base-package="com.xp.ioc.userMethod" use-default-filters="false"> <!-- type=annotation:类型=注解,意思就是根据注解的类型去包含/排除 --> <!-- type=assignable:类型=全限定类名,意思就是类的类型去包含/排除 --> <!-- 这句话的意思就是根据注解名为 Controller 的去扫描 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- 大概意思和上面差不多,就是不要去扫描全限定名为 expression 那串字符串的类 --> <context:exclude-filter type="assignable" expression="com.xp.ioc.userMethod.ServiceDao"/> </context:component-scan>
注意:一个 <context:component-scan>
中可以出现多个 include ,也可以同时出现多个 exclude ,但是两个不能同时出现
注:我自己测出来,两个同时存在也不会报错,功能能否实现取决于use-default-filters=true 还是 false,如果是 true 的话,只有排除有作用,否则,是包含发挥作用
IOC:工厂模式
AOP:代理模式
context:include-filter 和 context:exclude-filter 子节点支持多种类型的过滤表达式:
2.10.4 基于完全注解的自动装配
context:component-scan 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例, 该实例可以自动装配具有 @Autowired 和 @Resource 、@Inject注解的属性.
@Autowired 注解自动装配具有兼容类型的单个 Bean属性
构造器, 普通字段(即使是非 public), 一切具有参数的方法都可以应用@Authwired 注解
默认情况下, 所有使用 @Autowired 注解的属性都需要被设置. 当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Autowired 注解的 required 属性为 false
默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作。此时可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称
@Autowired 注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配.
@Autowired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean.
@Autowired 注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值
Spring 还支持 @Resource 和 @Inject 注解,这两个注解和 @Autowired 注解的功用类似
@Resource 注解要求提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 Bean 的名称
@Inject 和 @Autowired 注解一样也是按类型匹配注入的 Bean, 但没有 reqired 属性
建议使用 @Autowired 注解
(1)@Autowired:根据属性类型进行自动装配 ,当有多种相同的类型的时候可以使用 @Qualifier
(2)@Qualifier:根据名称进行注入 ,和 @Autowired 配套使用
(3)@Resource:可以根据类型注入,可以根据名称注入 。但是这个注解是 javax 中的,不是 Spring 中的,所以 Spring 更建议使用 @Autowired
(4)@Value:注入普通类型属性 ,有了这个注解,不用写属性的 set 方法了
案例演示:
dao层:
public interface UserDao {
public void add();
}
@Repository
public class UserDaoImpl1 implements UserDao{
@Override
public void add() {
System.out.println("add1...");
}
}
@Repository
public class UserDaoImpl2 implements UserDao{
@Override
public void add() {
System.out.println("add2...");
}
}
service 层:
@Service
public class UserService{
@Qualifier("userDaoImpl1")
@Autowired
UserDao userDao;
public void add(){
System.out.println("service add...");
userDao.add();
}
}
配置类:
@Configuration
@ComponentScan(basePackages = {"com.xp.dao","com.xp.service","com.xp.anno"})
public class Config {
}
测试类:
@Test
public void test5(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean5.xml");
UserService userService=context.getBean("userService",UserService.class);
System.out.println(userService);
userService.add();
}
3. AOP
3.1 AOP 基本概念
什么是 AOP ?
(1)面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能
(3)使用登录例子说明 AOP
3.2 AOP 底层原理
1.AOP 底层使用动态代理
(1)有两种情况动态代理
第一种情况:有接口的情况,使用 JDK 动态代理
- 创建接口实现类代理对象,增强类方法。就是创建了一个代理类实现了接口,然后就可以在接口中新增你想写的代码
第二种情况:没有接口的情况,使用 GGLIB 动态代理 - 创建子类的代理对象,增强类的方法。也就是说创建了一个代理类,继承了原来的类,这样就能在父类上增强方法啦
上面展示的是 AOP 的底层实现原理,其实在 Spring 里面他是把代理对象给我们封装好了的,我们直接使用即可,就是Proxy类里面的方法,原理就是上面的原理
3.3 AOP (JDK 动态代理)
jdk8 在线API中文文档:https://www.matools.com/api/java8
1、使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
(1)调用 newProxyInstance 方法
该方法有三个参数:
第一参数:类加载器来加载代理类
第二参数:增强方法所在的类(代理类)所实现的接口列表
第三参数:实现这个 InvocationHandler 接口,创建代理对象,写增强的部分
我感觉代理就是帮我们创建任意对象,并执行其中任意方法
2.编写 JDK 动态代理代码
动态代理原理和代码参见我的另外一篇博客:
3.4 AOP 操作术语
1、连接点:
类里面哪些方法可以被增强,这些方法称为连接点。就像下面那个图里面,User 类里面的这四个方法都可以被增强,那么这四个方法都可以被称为连接点
2、切入点:
实际被真正增强的方法,称为切入点。比如上面的四个方法,你可能只增强了 add() 或者 update() ,那就只有这两个方法叫切入点
3、通知(增强):
(1)实际增强的逻辑部分称为通知(增强),比如你在上图中的 add 前后添加了两条输出信息,那么这两条输出信息就叫通知(增强)
(2)通知有多种类型:
a、前置通知:在增强方法前的通知
b、后置通知:在增强方法后的通知
c、环绕通知:在增强方法前后的通知
d、异常通知:有异常才会执行的通知
e、最终通知:类似于 try catch finally 中的 finally
4、切面:
切面是一个动作,把通知应用到切入点的过程就叫切面。比如登录的时候加一个权限判断,这个权限判断加到登录模块的过程就叫做切面
4. AspectJ
对 Spring 来说,有四种实现 AOP 的方式:
1、Java Proxy(动态构建字节码)
2、cglib(动态构建字节码)
3、AspectJ
4、instrumentation
其中 AspectJ 不是 Spring 的组成部分,是独立的 AOP 框架,一般把 AspectJ 和 Spring 一起使用,进行 AOP 操作
AspectJ 有两种使用方式:
(1)基于 xml 配置文件实现
(2)基于注解方式实现
4.1 AspectJ 环境配置
在 Idea 导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.14</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.14</version>
</dependency>
4.2 切入点表达式
配置 AspectJ 过程中需要用到一个东西 — 切入点表达式
切入点表达式的作用: 知道对哪个类里面的方法进行了增强
切入点表达式语法结构:execution([权限修饰符] [返回类型] [类全路径] [方法名称] )
其中权限修饰符我们一般写成通配符 *
,因为这样可以匹配任意类型的权限修饰符,返回类型可以省略,类全路径就是类的全限定类名(包名+类名),方法名称通常也写 *
,匹配任意方法,参数列表通常写 ..
,代表可变参数,传几个都可以
举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.atguigu.dao.BookDao.add(…))
举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (…))
举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.. (…))
其实 AOP 主要实现的就是那几种通知,接下来基于注解和 xml 实现AOP主要也是演示怎么实现那几种通知
4.3 基于注解实现 AspectJ
1、创建被增强类:
package com.xp.anno;
public class User {
public void add(){
System.out.println("add...");
}
}
2、创建增强类(编写增强逻辑):
package com.xp.anno;
public class UserProxy {
public void before(){
System.out.println("前置通知...");
}
}
3、进行通知的配置:
(1)bean.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解-->
<context:annotation-config></context:annotation-config>
<!--注解要扫描的包-->
<context:component-scan base-package="com.xp.anno" use-default-filters="false"></context:component-scan>
</beans>
(2)使用注解创建 User 和 UserProxy 对象
(3)在增强类上面添加 @Aspect
注解
(4)在 bean.xml 文件中开启生成代理对象,就是去扫描有 AspectJ 注解的类,只要有这个注解,就给他生成个代理对象
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.xp.anno" use-default-filters="false"></context:component-scan>
<!-- 开启 Aspect 生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4、配置不同类型的通知
在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置【切入点表达式指明是为哪个方法增强】
package com.xp.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Component
@Aspect
public class UserProxy {
// 前置通知
// @Before 注解表示作为前置通知
@Before(value = "execution(* com.xp.anno.User.add(..))")
public void before(){
System.out.println("前置通知...");
}
//后置通知
@After(value = "execution(* com.xp.anno.User.add(..))")
public void after(){
System.out.println("后置通知");
}
//环绕通知:就是方法前后都有一条通知
@Around(value = "execution(* com.xp.anno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
//环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的
proceedingJoinPoint.proceed();
System.out.println("环绕之后");
}
//异常通知
@AfterThrowing(value = "execution(* com.xp.anno.User.add(..))")
public void exception(){
System.out.println("异常通知");
}
//最终通知
@AfterReturning(value = "execution(* com.xp.anno.User.add(..))")
public void isfinally(){
}
}
测试:
import com.xp.anno.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test1(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
User user=context.getBean("user",User.class);
user.add();
}
}
测试结果:
5、相同的切入点抽取
上面的 UserProxy 方法,在配置切入点表达式的时候,也就是@Around(value =
中 value 的值都是一样的,我们重复写了很多遍,为了简洁,这部分内容可以抽取出来,这个过程就叫相同的切入点抽取
案例演示:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Component
@Aspect
public class UserProxy2 {
@Pointcut(value = "execution(* com.xp.anno.User.add(..))")
public void pointdemo(){
}
// 前置通知
// @Before 注解表示作为前置通知
@Before(value = "pointdemo()")
public void before(){
System.out.println("前置通知...");
}
//后置通知
@After(value = "pointdemo()")
public void after(){
System.out.println("后置通知");
}
//环绕通知:就是方法前后都有一条通知
@Around(value = "pointdemo()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
//环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的
proceedingJoinPoint.proceed();
System.out.println("环绕之后");
}
//异常通知
@AfterThrowing(value = "pointdemo()")
public void exception(){
System.out.println("异常通知");
}
//最终通知
@AfterReturning(value = "pointdemo()")
public void isfinally(){
System.out.println("最终通知");
}
}
6、有多个增强类多同一个方法进行增强,设置增强类优先级
(1)在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
案例演示:
第一个类:UserProxy
@Component
@Aspect
@Order(2)
public class UserProxy {
// 前置通知
// @Before 注解表示作为前置通知
@Before(value = "execution(* com.xp.anno.User.add(..))")
public void before(){
System.out.println("前置通知2...");
}
}
第二个类:UserProxy2
@Component
@Aspect
@Order(1)
public class UserProxy2 {
@Pointcut(value = "execution(* com.xp.anno.User.add(..))")
public void pointdemo(){
}
// 前置通知
// @Before 注解表示作为前置通知
@Before(value = "pointdemo()")
public void before(){
System.out.println("前置通知1...");
}
}
测试结果:
7、完全使用注解开发
(1)创建配置类,不需要创建 xml 配置文件
@Configuration //这个注解一般的来说,用在类上面,加上这个注解的类可以成为一个spring的xml配置文件,使用的是java代码的配置
@ComponentScan(basePackages = {"com.xp.anno"}) //规定扫描哪个包
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启 Aspect 生成代理对象
public class ConfigAop { }
4.4 基于 xml 实现 AspectJ
配置文件 bean2.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="com.xp.xmltest"></context:component-scan>
<!-- 开启 Aspect 生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="book" class="com.xp.xmltest.Book"></bean>
<bean id="bookProxy" class="com.xp.xmltest.BookProxy"></bean>
<aop:config>
<!-- 抽取公共切入点 -->
<aop:pointcut id="p" expression="execution(* com.xp.xmltest.Book.buyBook(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="bookProxy">
<aop:before method="before" pointcut-ref="p"></aop:before>
</aop:aspect>
</aop:config>
</beans>
Book 类和 BookProxy类:
import org.springframework.stereotype.Component;
@Component
public class Book {
public void buyBook(){
System.out.println("Buy....");
}
}
public class BookProxy {
public void before(){
System.out.println("Before...");
}
}
测试类:
public class MyTest {
@Test
public void test1(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
Book book=context.getBean("book", Book.class);
book.buyBook();
}
}
5. JdbcTemplate
5.1 概述
为了使 JDBC 更易于使用,Spring 在 JDBC API 上定义了一个抽象层,以此建立一个 JDBC 存取框架
作为 Spring JDBC 框架的核心,JDBC 模板的设计目标是为不同类型的 JDBC 操作提供模板方法,通过这种方式,可以再尽可能暴力流灵活性的情况下,将数据库存取的工作量降到最低
可以将 Spring 的 JdbcTemplate 看作是一个小型的轻量的持久化层框架,和我们之前使用过的 DBUtils 风格非常接近
5.2 环境准备
JdbcTemplate 的存在仅仅是为了减少 Spring 操作数据库的代码量
基于之前的配置,先在 pom.xml 中导入依赖,这样就可以直接使用类org.springframework.jdbc.core.JdbcTemplate:
<!-- https://mvnrepository.com/artifact/org.springframework/org.springframework.jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
再在application.xml里面配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/project"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
测试类:
@Test
public void test01(){
ApplicationContext ac=new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplate =ac.getBean("jdbcTemplate",JdbcTemplate.class);
String sql="insert into emp values(null,?,?)";
jdbcTemplate.update(sql,"张三",28);
}
如果测试有报错,大概意思是 bean 注入错误的话,建议使用自己的仓库就好了
5.3 JdbcTemplate 之 batchUpdate(批量增删改查)
@Test
public void test02(){
ApplicationContext ac=new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplate =ac.getBean("jdbcTemplate",JdbcTemplate.class);
String sql="insert into emp values(null,?,?)";
//jdbcTemplate.update(sql,"张三",28);
List<Object[]> list=new ArrayList<Object[]>();
for(int i=1;i<=3;i++)
{
Object []objects=new Object[]{i,i};
list.add(objects);
}
jdbcTemplate.batchUpdate(sql, list);
}
Tip:在使用 preStatement 的时候,通配符是通过 setString 的方式加到数据库的,而 setString 方法会自动给 属性加上单引号,所以用 in 什么去匹配的时候就不对,相当于比较了 123=‘123’,所以,in 和 like 都是不可以用通配符去赋值的
5.4 JdbcTemplate 之 query(查询单条数据)
查询多行:
查询单行:
6. 事务
6.1 事务的基本概念
什么事务 ?
(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
(2)典型场景:银行转账 * lucy 转账 100 元 给 mary * lucy 少 100,mary 多 100
6.2 搭建事务操作环境
1、创建数据库表,添加记录
2、创建 service,搭建 dao,完成对象创建和注入关系
(1)service 注入 dao,在 dao 注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource
6.3 声明式事务管理参数配置
在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数**
6.3.1 propagation:事务传播行为
多事务方法直接进行调用,这个过程中事务是如何进行管理的
Spring 框架事务传播行为有 7 种:
REQUIRED:如果 add 方法本身有事务,调用 update 方法之后,update 方法使用当前 add 方法里面的事务;如果 add 方法没有事务,调用 update 方法之后,创建新事务
REQUIRED_NEW: 使用 add 方法调用 update 方法的时候,不论 add 方法有没有事务,都创建新的事务
6.3.2 ioslation:事务隔离级别
(1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
(2)有三个读问题:脏读、不可重复读、虚(幻)读
(3)脏读:一个未提交事务读取到另一个未提交事务的数据
(4)不可重复读:一个未提交事务读取到另一提交事务修改数据
(5)虚读:一个未提交事务读取到另一提交事务添加数据
(6)解决:通过设置事务隔离级别,解决读问题
6.3.3 timeout:超时时间
(1)事务需要在一定时间内进行提交,如果不提交进行回滚
(2)默认值是 -1 ,设置时间以秒单位进行计算
6.3.4 readOnly:是否只读
(1)读:查询操作,写:添加修改删除操作 (2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作 (3)设置 readOnly 值是 true,设置成 true 之后,只能查询
6.3.5 rollbackFor:回滚
(1)设置出现哪些异常进行事务回滚
6.3.6 noRollbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
6.4 基于完全注解的声明式事务管理
7. Spring5 新功能
整个 Spring5 框架的代码基于 Java8 ,运行时兼容 JDK9 ,许多不建议使用的类和方法在代码库中删除
7.1 整合日志框架
(1)Spring5 已经移除 Log4ConfigListerner ,官方建议使用 Log4j2
(2)Spring5 框架整合 Log4j2
第一步:导入依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 用于slf4j与log4j2桥接 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.6.2</version>
</dependency>
<!-- commons-logging与log4j2桥接 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 使用log4j2的异步日志需要的依赖 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.5</version>
</dependency>
<!-- web工程需要的log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.6.2</version>
</dependency>
第二步:创建配置文件 log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别以及优先级排序 : OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置, 当设置成 trace 时,可以看到 log4j2 内部各种详细输出 -->
<configuration status="INFO">
<!-- 先定义所有的 appender -->
<appenders>
<!-- 输出日志信息到控制台 -->
<console name="Console" target="SYSTEM_OUT">
<!-- 控制日志输出的格式 -->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!-- 然后定义 logger ,只有定义 logger 并引入的 appender , appender 才会生效 -->
<!-- root :用于指定项目的根日志,如果没有单独指定 Logger ,则会使用 root 作为 默认的日志输出 -->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
第三步:测试和输出结果
package com.xp.Log4j2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestLog {
//参数为.class 可以为你展示出是哪个类打印出来的错误日志
private static final Logger log= LoggerFactory.getLogger(TestLog.class);
public static void main(String[] args) {
log.info("hello log4j2");
log.warn("hello log4j2");
}
}
7.2 Nullable 注解
(1)@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以 为空,参数值可以为空
(2)注解用在方法上面,方法返回值可以为空
(3)注解使用在方法参数里面,方法参数可以为空
(4)注解使用在属性上面,属性值可以为空
7.3 函数式风格 GenericApplicationContext
//函数式风格创建对象,交给 Spring 管理
@Test
public void test2(){
//1、创建 GenericApplicationContext 对象
GenericApplicationContext context=new GenericApplicationContext();
//调用 context 的方法对象进行注册
context.refresh();
/*
第一种写法:
context.registerBean("book",Book.class,()->new Book());
//3 获取在 spring 注册的对象
Book book=context.getBean("book",Book.class);
*/
//第二种写法:
context.registerBean(Book.class,()->new Book());
Book book=context.getBean("com.xp.xmltest.Book",Book.class);
System.out.println(book);
}
7.4 整合 Junit5 单元测试框架
(1)整合 JUnit4
第一步,导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
第二步,创建测试类,使用注解方式完成
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
/*
* 下面这个注解相当于之前的:
* ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
*/
@ContextConfiguration("classpath:bean2.xml")
public class TestJunit {
@Autowired
Book book;
@Test
public void testBook(){
book.buyBook();
}
}
@RunWith(SpringJUnit4ClassRunner.class): 可以指定你现在用的是哪个版本的 Junit 框架
(2)Spring5 整合 Junit5
第一步,先导入 Spring5 的依赖:
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>4.12.1</version>
<scope>test</scope>
</dependency>
第二步,创建测试类,使用注解完成:
import com.xp.xmltest.Book;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean2.xml")
public class TestJunit5 {
@Autowired
Book book;
@Test
public void test(){
book.buyBook();
}
}
(3)使用一个复合注解替代上面两个注解完成整合
import com.xp.xmltest.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:bean2.xml")
public class TestJunit5 {
@Autowired
Book book;
@Test
public void test(){
book.buyBook();
}
}
何时使用@RunWith以及何时使用@ExtendWith: