spring5框架
- 一、Spring 框架概述
- 二、IOC容器
- 1、IOC 底层原理
- 2、IOC 接口(BeanFactory)
- 3、IOC 操作 Bean 管理(基于 xml)
- 4、IOC 操作 Bean 管理(基于 注解)
- 5、IOC 操作 Bean 管理(p 名称空间注入)
- 6、IOC 操作 Bean 管理(xml 注入其他类型属性)
- 7、IOC 操作 Bean 管理(xml 注入集合属性)
- 8、IOC 操作 Bean 管理(FactoryBean)
- 9、IOC 操作 Bean 管理(bean作用域)
- 10、IOC 操作 Bean 管理(bean生命周期)
- 11、IOC 操作 Bean 管理(xml自动装配)
- 12、IOC 操作 Bean 管理(外部属性文件)
- 13、IOC 操作 Bean 管理(基于注解方式)
- 三、Aop
- 四、JdbTemplate
- 五、事务管理
- 六、Spring5 新特性
一、Spring 框架概述
1、Spring是一个开源的 轻量级 Java SE(Java 标准版本)/Java EE(Java
企业版本)开发应用框架
2、Spring 简化企业级应用程序开发,降低开发的复杂性
3、Spring 有两个核心部分:IOC 和 Aop
(1)IOC:控制反转,把创建对象的过程交给 spring 进行管理(对象实例化)
(2)Aop:面向切面,在不修改源代码的情况下,进行功能增强
4、Spring 特点:
(1)方便解耦,简化开发
(2)Aop 编程支持
(3)方便程序的测试(junit支持)
(4)方便和其他框架进行整合
(5)方便进行事务操作
(6)降低 API 开发难度
(7)Java 源码是经典学习范例
5、选举 Spring 版本 5.x 课程讲解
入门案例
1、下载 Spring5
(1)选择 Spring 稳定版本 5.2.6 :
(2)下载地址
进入github地址:找到 Access to Binaries 位置
进入 Spring Framework Artifacts,找到下载地址 :https://repo.spring.io
进入:
找到 Spring 的位置,复制地址:
在:https://repo.spring.io 地址拼接复制的内容:https://repo.spring.io/release/org/springframework/spring/
下载:
下载解压到本地:
jar包:
配置文件:
2、打开idea,创建普通 Java 工程
File --> New --> Project
创建一个普通的 Java 工程:
3、导入 Spring5 相关 jar 包
spring模块:
spring核心容器:
-
Beans
:这个 jar 文件是所有应用都要用到的,它包含访问配置文件、创建和管理 bean 以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI 支持,引入spring-core.jar 及spring-beans.jar 文件就可以了。外部依赖spring-core,(CGLIB)。 -
Core
:这个 jar 文件包含 Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。外部依赖Commons Logging, (Log4J)。 -
Context
:这个 jar 文件为 Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。
外部依赖spring-beans, (spring-aop)。 -
Expression
:spring表达式语言
在 spring-framework-5.3.6\libs 目录下找到四jar个包:
复制依赖包到项目里:
在idea里,导入刚才复制的依赖包:
File --> Project Structure
Modules --> spring5_demo1 -->Dependencies --> +
1.JARS or Directories
添加依赖包:
4、创建普通类,在这个类创建普通方法
package com.zzp.spring5;
public class User {
public void add(){
System.out.println("....User.add().....");
}
}
5、创建 Spring 配置文件,在配置文件配置创建对象
(1)Spring 配置文件使用 xml 格式:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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">
<!-- 配置User对象创建 -->
<bean id="user" class="com.zzp.spring5.User">
</bean>
</beans>
6、进行测试代码编写
package com.zzp.spring5.test;
import com.zzp.spring5.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSpring5 {
@Test
public void testAdd(){
// 1、加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
// 2、获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
}
执行结果:
com.zzp.spring5.User@4671e53b
....User.add().....
完成了 xml配置 创建对象
二、IOC容器
1、IOC 底层原理
IOC (概念和原理):
IOC: 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
什么是IOC:
(1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 管理
(2)使用 IOC 目的:为了耦合降低
(3) 上面做的入门案例就是 IOC 实现
IOC 底层原理:
(1)xml 解析、工厂模式、反射
图画讲解 IOC 底层原理:
原始模式:
工厂模式:
IOC 过程:
2、IOC 接口(BeanFactory)
1、IOC 思想基于 IOC容器完成,IOC 容器底层就是对象工厂
2、Spring 提供 IOC 容器实现两种方式:(两个接口)
-
BeanFactory
:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。(注:加载配置文件的时候不会创建对象,在获取 对象/使用 才会创建对象)// 1、加载spring配置文件 BeanFactory context = new ClassPathXmlApplicationContext("bean1.xml");
-
ApplicationContext
:BeanFactory 接口的子接口,提供个更多更强大的功能,一般提供开发人员进行使用。(注:加载配置文件的时候就会把在配置文件对象进行创建)// 1、加载spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
3、ApplicationContext
接口实现类:
FileSystemXmlApplicationContext
:全路径ClassPathXmlApplicationContext
:类路径
3、IOC 操作 Bean 管理(基于 xml)
1、什么是 bean 管理
Bean 管理指的是两个操作:
(1)Spring 创建对象
(2)Spring 注入属性
2、Bean 管理操作有两种方式:
(1)基于 xml配置文件方式实现
(2)基于注解方式实现
基于 xml 方式创建对象
xml配置文件内容
<!-- 配置User对象创建 -->
<bean id="user" class="com.zzp.spring5.User"></bean>
①在 Spring配置文件中,使用 bean 标签,在标签里面添加对应属性,就可以实现对象创建
②在 bean 标签有很多属性,介绍常用的属性:
id
属性:唯一标识class
属性:类全路径(包类路径)name
属性:类似id
属性 可以有特殊字符
③创建对象的时候,默认也是执行无参构造方法完成对象的创建
4、IOC 操作 Bean 管理(基于 注解)
(1)DI:依赖注入,就是注入属性
第一种注入方式:使用 set 方法进行注入
(1)创建类,定义属性和对应的 set 方法
/**
* 演示使用 set方法进行注入属性
*/
public class Book {
// 创建属性
private String bname;
private String bauthor;
// 创建属性对应的 set方法
public void setBname(String bname){
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
}
(2)在 Spring 配置文件配置对象创建,配置属性注入
<bean id="book" class="com.zzp.spring5.Book">
<!-- 使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
</bean>
(3)小测试:
在Book类添加一个输出方法:
/**
* 演示使用 set方法进行注入属性
*/
public class Book {
....
public void testDemo(){
System.out.println(bname + "::" +bauthor);
}
}
测试方法:
@Test
public void testBook1(){
// 1、加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
// 2、获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
book.testDemo();
}
执行:
com.zzp.spring5.Book@2eda0940
易筋经::达摩老祖
第二种注入方式:使用有参数构造进行注入
(1)创建类,定义属性,创建属性对应有参数构造方法
/**
* 使用有参数构造注入
*/
public class Orders {
// 创建属性
private String oname;
private String address;
// 有参数构造
public Orders(String oname, String address) {
this.oname = oname;
this.address = address;
}
}
(2)在 Spring 配置文件中进行配置
<!-- 有参数构造注入属性 -->
<bean id="orders" class="com.zzp.spring5.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>
(3)小测试
在Orders 类添加一个输出方法:
/**
* 使用有参数构造注入
*/
public class Orders {
...
public void ordersTest(){
System.out.println(oname + "::" +address);
}
}
测试方法:
@Test
public void testOrders(){
// 1、加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
// 2、获取配置创建的对象
Orders orders = context.getBean("orders", Orders.class);
System.out.println(orders);
orders.ordersTest();
}
执行:
com.zzp.spring5.Orders@4cf4d528
电脑::China
5、IOC 操作 Bean 管理(p 名称空间注入)
(1)使用 p 名称空间注入,可以简化基于 xml配置方法 (注:了解即可)
第一步:添加 p 名称空间在 xml配置文件中
xmlns:p=“http://www.springframework.org/schema/p”
第二步:进行属性注入,在 bean 标签里面进行操作
<!-- p 名称空间注入 -->
<bean id="book" class="com.zzp.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏"></bean>
底层也是set方法注入
(2)小测试
@Test
public void testBook1(){
// 1、加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
// 2、获取配置创建的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
book.testDemo();
}
执行:
com.zzp.spring5.Book@2a693f59
九阳神功::无名氏
6、IOC 操作 Bean 管理(xml 注入其他类型属性)
1、字面量
private String bname = “易筋经”; 或者
<property name=“bname” value=“易筋经”>
(1)null 值
在Book类添加一个属性 address
public class Book {
// 创建属性
private String bname = "易筋经";
private String bauthor;
private String address;
// 创建属性对应的 set方法
public void setBname(String bname){
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
public void setAddress(String address) {
this.address = address;
}
public void testDemo(){
System.out.println(bname + "::" +bauthor + "::" + address );
}
}
xml配置文件,添加属性
<bean id="book" class="com.zzp.spring5.Book">
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
<!-- null值-->
<property name="address">
<null/>
</property>
</bean>
执行:testBook1()
com.zzp.spring5.Book@eb21112
易筋经::达摩老祖::null
(2)属性值包含特殊符号
传统写法会报错:
里面不能直接用 “<< >>” 符号,可以用转义符号:"< >
" `相当大于小于符号;
使用CDATA
:
<bean id="book" class="com.zzp.spring5.Book">
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
<!-- 属性值包含特殊符号
1 把<>进行转义 < >
2 把特殊符号内容写到CDATA
-->
<property name="address">
<value><![CDATA[<<南京>>]]></value>
</property>
</bean>
执行:testBook1()
com.zzp.spring5.Book@eb21112
易筋经::达摩老祖::<<南京>>
2、注入属性 – 外部 bean
(1)创建两个类 service类 和 dao类
(2)在 service类调用 dao类里面的方法
dao接口类
package com.zzp.spring5.dao;
public interface UserDao {
public void update();
}
dao实现类
package com.zzp.spring5.dao;
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println(".......UserDao.update()........");
}
}
service类
package com.zzp.spring5.service;
public class UserService {
public void add(){
System.out.println("...UserService.add().....");
// 原始方式 创建UserDao对象
// UserDao userDao = new UserDaoImpl();
// userDao.update();
}
}
(3)在 Spring 配置文件中进行配置
UserService类:
public class UserService {
// 创建UserDao类型属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
public void add(){
System.out.println("...UserService.add().....");
userDao.update();
}
}
xml配置文件:
<!--1 service和dao对象创建 -->
<bean id="userService" class="com.zzp.spring5.service.UserService">
<!-- 注入userDao对象
name属性值:类里面属性名称
ref属性值:创建userDao对象bean标签id值
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<!-- 外部bean -->
<bean id="userDaoImpl" class="com.zzp.spring5.dao.UserDaoImpl"></bean>
(4)小测试:
@Test
public void testAdd(){
// 1、加载spring配置文件
BeanFactory context = new ClassPathXmlApplicationContext("bean2.xml");
// 2、获取配置创建的对象
UserService user = context.getBean("userService", UserService.class);
System.out.println(user);
user.add();
}
执行:
com.zzp.spring5.service.UserService@4524411f
...UserService.add().....
.......UserDao.update()........
3、注入属性 – 内部 bean
(1)一对多关系:比如,部门和员工
一个部门有多个员工,一个员工属于一个部门
部门是一,员工是多
(2)在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
// 员工类
public class Emp {
private String ename;
private String gender;
// 员工属于某一个部门,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void add(){
System.out.println(ename + "::" +gender + "::" + dept);
}
}
(3)在 spring 配置文件中进行配置
<!-- 内部bean -->
<bean id="emp" class="com.zzp.spring5.bean.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="Tom"></property>
<property name="gender" value="男"></property>
<!-- 设置对象类型属性 -->
<property name="dept">
<bean id="dept" class="com.zzp.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
(4)小测试:
@Test
public void testBean3(){
// 1、加载spring配置文件
BeanFactory context = new ClassPathXmlApplicationContext("bean3.xml");
// 2、获取配置创建的对象
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
emp.add();
}
执行:
com.zzp.spring5.bean.Emp@10a035a0
Tom::男::Dept{dname='安保部'}
4、注入属性 – 级联赋值
使用上面的 emp对象示例:
(1)第一种写法:
修改xml配置文件:
<!-- 级联赋值 -->
<bean id="emp" class="com.zzp.spring5.bean.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="Tom"></property>
<property name="gender" value="男"></property>
<!-- 级联赋值 -->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.zzp.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
执行:testBean3()
com.zzp.spring5.bean.Emp@6eceb130
Tom::男::Dept{dname='财务部'}
(2)第二种写法:
Emp类添加 getDept() 方法:
// 员工类
public class Emp {
...
// 员工属于某一个部门,使用对象形式表示
private Dept dept;
// 生成dept的get方法
public Dept getDept() {
return dept;
}
....
修改xml配置文件:
<!-- 级联赋值 -->
<bean id="emp" class="com.zzp.spring5.bean.Emp">
<!-- 设置两个普通属性 -->
<property name="ename" value="Tom"></property>
<property name="gender" value="男"></property>
<!-- 级联赋值 -->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.zzp.spring5.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>
执行:testBean3()
com.zzp.spring5.bean.Emp@67b467e9
Tom::男::Dept{dname='技术部'}
7、IOC 操作 Bean 管理(xml 注入集合属性)
1、注入数组类型属性
2、注入 List集合
3、注入 Map集合
4、注入 Set集合
(1)创建类。定义数字、list、map、set类型属性,并生成对应 set方法
Stu类:
public class Stu {
// 1 数组类型属性
private String[] courses;
// 2 list集合类型属性
List<String> list;
// 3 Map集合类型属性
private Map<String,String> maps;
// 4 set集合类型属性
private Set<String> set;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void test(){
System.out.println(Arrays.asList(courses));
System.out.println(list);
System.out.println(maps);
System.out.println(set);
}
}
(2)在 Spring配置文件进行配置
<!--1 集合类型属性注入 -->
<bean id="stu" class="com.zzp.spring5.collectiontype.Stu">
<!-- 数组类型属性注入 -->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!-- 集合类型属性注入 -->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!-- map类型属性注入 -->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!-- set类型属性注入 -->
<property name="set">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
(3)小测试:
@Test
public void testCollection(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
System.out.println(stu);
stu.test();
}
执行:
com.zzp.spring5.collectiontype.Stu@7a5ceedd
[java课程, 数据库课程]
[张三, 李四]
{JAVA=java, PHP=php}
[MySQL, Redis]
5、在集合里面设置对象类型值
添加Course类:
public class Course {
// 课程名称
private String cname;
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course{" +
"cname='" + cname + '\'' +
'}';
}
}
添加Stu集合对象:
public class Stu {
....
private List<Course> courseList;
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
....
public void test(){
....
System.out.println(courseList);
}
}
在 Spring配置文件进行配置
<!--1 集合类型属性注入 -->
<bean id="stu" class="com.zzp.spring5.collectiontype.Stu">
.....
<!-- 注入list集合类型,值是对象 -->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!-- 创建多个course对象 -->
<bean id="course1" class="com.zzp.spring5.collectiontype.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.zzp.spring5.collectiontype.Course">
<property name="cname" value="MyBatic框架"></property>
</bean>
测试执行结果:
com.zzp.spring5.collectiontype.Stu@eadd4fb
[java课程, 数据库课程]
[张三, 李四]
{JAVA=java, PHP=php}
[MySQL, Redis]
[Course{cname='Spring5框架'}, Course{cname='MyBatic框架'}]
6、把集合注入部分提取出来
(1)在 Spring配置文件中引入名称空间 util
xmlns:util=“http://www.springframework.org/schema/util”
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
(2)使用 util标签完成 list集合注入提取
创建Book类:
public class Book {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
public void test(){
System.out.println(list);
}
}
在 Spring配置文件进行配置
<!--1 提取list集合类型属性注入 -->
<util:list id="bookList">
<value>易筋经</value>
<value>九阳神功</value>
<value>一阳指</value>
</util:list>
<!--2 提取list集合类型属性注入使用 -->
<bean id="book" class="com.zzp.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
测试单元:
@Test
public void testCollection2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
System.out.println(book);
book.test();
}
执行:
com.zzp.spring5.collectiontype.Book@3feb2dda
[易筋经, 九阳神功, 一阳指]
8、IOC 操作 Bean 管理(FactoryBean)
1、Spring 有两种类型 bean,一种普通 bean,另外一种 工厂bean(FactoryBean)
2、普通 bean:在配置文件定义 bean类型就是返回类型(上面测试的案例 xml配置文件bean标签)
3、工厂 bean:在配置文件定义 bean类型可以和返回类型不一样
(1)第一步 创建类,让这个类作为 工厂bean,实现接口 FactoryBean
(2)第二步 实现接口里面的方法,在实现的方法中定义返回的 bean类型
创建MyBean类,实现FactoryBean:
public class MyBean implements FactoryBean<Course> {
// 定义返回bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
在 Spring配置文件进行配置
<bean id="myBean" class="com.zzp.spring5.factorybean.MyBean"></bean>
测试单元:
@Test
public void testCollection3(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
执行:
Course{cname='abc'}
9、IOC 操作 Bean 管理(bean作用域)
1、在 Spring里面,设置创建 bean实例是单实例还是多实例
2、在 Spring里面,默认情况下,bean是单实例对象
以Book类,作为演示:
测试单元:
@Test
public void testCollection2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
System.out.println(book1);
System.out.println(book2);
}
执行:
com.zzp.spring5.collectiontype.Book@3feb2dda
com.zzp.spring5.collectiontype.Book@3feb2dda
两个对象地址是一致的,说明默认是单实例
3、如何设置单实例还是多实例
(1)在 Spring配置文件 bean标签里面有属性(scope)用于设置单实例还是多实例
(2)scope属性值:
- 第一个 默认值,
singleton
,表示单实例对象 - 第二个
prototype
,表示是多实例对象
单元测试:结果
com.zzp.spring5.collectiontype.Book@6a8658ff
com.zzp.spring5.collectiontype.Book@1c742ed4
两个对象地址不一样,多实例
(3)singleton
和 prototype
区别
第一 singleton
单实例, prototype
多实例
第二 设置scope值是singleton
时候,加载 spring配置文件时候就会创建单实例对象;设置scope值是prototype
时候,不是加载 spring配置文件时候创建对象,在调用 getBean()
方法时候创建多实例对象
10、IOC 操作 Bean 管理(bean生命周期)
1、生命周期
(1)从对象创建到对象销毁的过程
2、bean生命周期
(1)通过构造器创建 bean实例(无参构造)
(2)为 bean的属性设置值和其他 bean引用(调用 set方法)
(3)调用 bean的初始化的方法(需要进行配置初始化的方法)
(4)bean可以使用了(对象获取到了)
(5)当容器关闭的时候,调用 bean的销毁方法(需要进行配置销毁的方法)
3、演示 bean生命周期
创建Orders类:
public class Orders {
// 无参数构造
public Orders(){
System.out.println("第一步 执行无参数构造创建bean实例");
}
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用set方法设置属性值");
}
// 创建执行的初始化的方法
public void initMethod(){
System.out.println("第三步 执行初始化的方法");
}
// 创建执行的销毁的方法
public void destroyMethod(){
System.out.println("第五步 执行销毁的方法");
}
}
在 Spring配置文件进行配置
<bean id="orders" class="com.zzp.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
单元测试:
@Test
public void testCollection4(){
// ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建bean实例对象");
System.out.println(orders);
// 手动让bean实例销毁
context.close();
}
}
执行:
第一步 执行无参数构造创建bean实例
第二步 调用set方法设置属性值
第三步 执行初始化的方法
第四步 获取创建bean实例对象
com.zzp.spring5.bean.Orders@7d9d0818
第五步 执行销毁的方法
4、bean 的后置处理器,bean 生命周期七步
(1)通过构造器创建 bean实例(无参构造)
(2)为 bean的属性设置值和其他 bean引用(调用 set方法)
(3)把 bean实例传递 bean 前置处理器的方法(postProcessBeforeInitialization()
方法)
(4)调用 bean的初始化的方法(需要进行配置初始化的方法)
(5)把 bean实例传递 bean 后置处理器的方法(postProcessAfterInitialization()
方法)
(6)bean可以使用了(对象获取到了)
(7)当容器关闭的时候,调用 bean的销毁方法(需要进行配置销毁的方法)
5、演示添加后置处理器效果:
(1)创建类,实例接口 BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
在 Spring配置文件进行添加配置
<bean id="orders" class="com.zzp.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
<!-- 配置后置处理器 -->
<bean id="myBeanPost" class="com.zzp.spring5.bean.MyBeanPost"></bean>
继续上一个单元测试:
第一步 执行无参数构造创建bean实例
第二步 调用set方法设置属性值
在初始化之前执行的方法
第三步 执行初始化的方法
在初始化之后执行的方法
第四步 获取创建bean实例对象
com.zzp.spring5.bean.Orders@3e08ff24
第五步 执行销毁的方法
11、IOC 操作 Bean 管理(xml自动装配)
1、什么是自动装配
(1)根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
2、演示自动装配过程
创建两个类:
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
public void test(){
System.out.println(dept);
}
}
(1)根据属性名称自动注入
在 Spring配置文件进行添加配置
<!-- 实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常两个值;
byName根据属性名称注入,注入值bean的id值和类属性名称一样
byType根据属性类型注入
-->
<bean id="emp" class="com.zzp.spring5.autowire.Emp" autowire="byName">
<!-- <property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.zzp.spring5.autowire.Dept"></bean>
单元测试:
@Test
public void test4(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
执行:
Emp{dept=Dept{}}
(2)根据属性类型自动注入
<!-- 实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常两个值;
byName根据属性名称注入,注入值bean的id值和类属性名称一样
byType根据属性类型注入
-->
<bean id="emp" class="com.zzp.spring5.autowire.Emp" autowire="byType">
<!-- <property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.zzp.spring5.autowire.Dept"></bean>
根据属性类型自动注入 byType
,只能只有一个 bean 类型的类 不能创建两个;如果有两个,只能使用 byName
。
12、IOC 操作 Bean 管理(外部属性文件)
1、直接配置数据库信息
(1)配置德鲁伊连接池
(2)引入德鲁伊连接池 依赖 jar包
引入依赖 jar包:
在 Spring配置文件进行添加配置
<!--直接配置连接池 -->
<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/userDb"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
2、引入外部属性文件配置数据库连接池
(1)创建外部属性文件,properties
格式文件,写数据库信息
jdbc.properties
文件内容如下:
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.username=root
prop.password=123456
(2)把外部 properties
属性文件引入在 spring配置文件中
①引入 context
名称空间
xmlns:context="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
②在 spring配置文件使用标签引入外部属性文件
<!--引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--直接配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
13、IOC 操作 Bean 管理(基于注解方式)
1、什么是注解
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值...)
(2)使用注解,注解作用在类上面,方法上面,属性上面
(3)使用注解目的:简化 xml配置
2、Spring 针对Bean 管理中创建对象提供注解
(1)@Component
(2)@Service
(3)@Controller
(4)@Repository
*上面四个注解功能是一样的,都可以用来创建 bean实例
3、基于注解方式实现对象创建
第一步 引入依赖 spring-aop-5.3.6.jar
第二步 开启组件扫描(引入 context
名称空间 上面有描述)
在 Spring配置文件进行添加配置
<!--开启组件扫描
1 如果扫描多个包,多个包使用逗号隔开
2 扫描包上层目录
-->
<context:component-scan base-package="com.zzp.spring5"/>
第三步 创建类,在类上面添加创建对象注解
import org.springframework.stereotype.Component;
// 在注解里面value属性可以省略不写,
// 默认值是类名,首字母小写 ( UserService ==> userService )
@Component(value = "userService") // <bean id="userService" class="..." />效果一样
public class UserService {
public void add(){
System.out.println("...UserService.add()....");
}
}
第四步 小测试
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
执行:
com.zzp.spring5.service.UserService@27c86f2d
...UserService.add()....
这里使用其他 @Service
、@Controller
、@Repository
效果是一样的
4、开启组件扫描细节配置
<!-- 实例1
use-default-filters="false" 表示现在不使用默认filter,自己配置filter
context:include-filter , 设置扫描哪些内容
-->
<context:component-scan base-package="com.zzp.spring5" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 实例2
下面配置扫描包所有内容
context:exclude-filter , 设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.zzp.spring5">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
5、基于注解方式实现属性注入
(1)@Autowired
:根据属性类型进行自动装配
第一步 把 service和 dao对象创建,在 service和dao类添加创建对象注解
@Service
public class UserService {
....
}
public interface UserDao {
void add();
}
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("...UserDao.add()...");
}
}
第二步 在 service注入 dao对象,在 service类添加 dao类型属性,在属性上面使用注解@Autowired
@Service
public class UserService {
//定义dao类型属性
//不需要添加set方法
//添加注入属性注解
@Autowired // 根据类型进行注入
private UserDao userDao;
public void add(){
System.out.println("...UserService.add()....");
userDao.add();
}
}
第三步 小测试
单元测试test01()
执行结果:
com.zzp.spring5.service.UserService@d706f19
...UserService.add()....
...UserDao.add()...
(2)@Qualifier
:根据名称进行注入
这个@Qualifier
注解的使用,要上面的@Autowired
一起使用
@Service
public class UserService {
@Autowired // 根据类型进行注入
@Qualifier(value = "userDaoImpl1") // 根据名称进行注入
private UserDao userDao;
...
}
@Repository(value = "userDaoImpl1") // 默认类名称 首字母小写
public class UserDaoImpl implements UserDao{
....
}
单元测试,执行结果:
com.zzp.spring5.service.UserService@2f177a4b
...UserService.add()....
...UserDao.add()...
(3)@Resource
:可以根据类型注入,也可以根据名称注入
@Service
public class UserService {
//@Resource // 根据类型进行注入
@Resource(name = "userDaoImpl1") // 根据名称进行注入
private UserDao userDao;
....
}
单元测试:
com.zzp.spring5.service.UserService@233fe9b6
...UserService.add()....
...UserDao.add()...
注:@Resource
注解使用javax.annotation.Resource
;java扩展包
(4)@Value
:注入普通类型属性
@Service
public class UserService {
@Value(value = "abc")
private String name;
...
public void add(){
System.out.println("...UserService.add()....name:" + name);
userDao.add();
}
}
单元测试:
com.zzp.spring5.service.UserService@2928854b
...UserService.add()....name:abc
...UserDao.add()...
6、完全注解开发
(1)创建配置类,替代 xml配置
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration // 作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.zzp.spring5"}) // 扫描的包
public class SpringConfig {
}
(2)编写测试类
@Test
public void test02(){
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
执行:
com.zzp.spring5.service.UserService@9353778
...UserService.add()....name:abc
...UserDao.add()...
三、Aop
1、AOP(概念)
1、什么是AOP
(1)AOP为Aspect Oriented Programming
的缩写,意为:面向切面编程(方向)
。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
(3)使用登录例子说明 AOP
2、AOP(底层原理)
1、AOP 底层使用 动态代理
(1)有两种情况动态代理
- 第一种 有接口情况,使用 JDK 动态代理
创建接口实现类代理对象,增强类的方法
- 第二种 没有接口情况,使用 CGLIB 动态代理
创建子类的代理对象,增强类的方法
3、AOP(JDK 动态代理)
1、使用 JDK动态代理,使用 Proxy类里面方法创建代理对象
(1)调用 newProxyInstance()
方法
方法有三个参数:
第一个参数(ClassLoader loader
),类加载器
第二个参数(类<?>[] interfaces
),增强方法所在的类,这个类实现的接口,支持多个接口
第三个参数(InvocationHandler h
),实现这个接口InvocationHandler
,创建代理对象,写增强的部分
2、编写 JDK 动态代理代码
(1)创建接口,定义方法
public interface UserDao {
int add(int a,int b);
String update(String id);
}
(2)创建接口实现类,实现方法
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
System.out.println("add方法执行了...");
return a + b;
}
@Override
public String update(String id) {
System.out.println("update方法执行了...");
return id;
}
}
(3)使用 Proxy类创建接口代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class JKDProxy {
public static void main(String[] args) {
// 创建接口实现类代理对象
Class[] interfaces = {UserDao.class};
// // 使用匿名内部类
// Proxy.newProxyInstance(JKDProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// });
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JKDProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println("result="+result);
}
}
//创建代理对象代码
class UserDaoProxy implements InvocationHandler{
// 1 把创建的是谁的代理对象,把谁传递过来
// 有参数构造传递
private Object obj;
public UserDaoProxy(Object obj){
this.obj = obj;
}
// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System.out.println("方法之前执行..." + method.getName() + " :传递的参数..." + Arrays.asList(args));
// 被增强的方法执行
Object res = method.invoke(obj,args);
// 方法之后
System.out.println("方法之后执行..." + obj);
return res;
}
}
执行:
方法之前执行...add :传递的参数...[1, 2]
add方法执行了...
方法之后执行...com.zzp.spring5.UserDaoImpl@6f94fa3e
result=3
4、AOP(术语)
1、连接点
类里面哪些方法可以被增强,这些方法称为连接点
2、切入点
实际被真正增强的方法,称为切入点
3、通知(增强)
(1)实际增强的逻辑部分称为通知 (增强)
(2)通知有多种类型
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
4、切面
是动作
(1)把通知应用到切入点过程
5、AOP(准备)
1、Spring 框架一般基于 AspectJ 实现 AOP 操作
(1)什么是 AspectJ
AspectJ
不是 Spring 组成部分,独立 AOP 框架,一般 AspectJ 和 Spring 框架一起使用,进行 AOP操作
2、基于 AspectJ 实现 AOP 操作
(1)基于 xml配置文件实现
(2)基于注解方式实现(使用)
3、在项目工程里面引入 AOP 相关依赖
spring-aspects-5.3.6.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
查找依赖包方法:
MVN仓库
复制在项目lib目录下:
4、切入点表达式
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
(2)语法结构:
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
举例 1:对com.zzp.spring5.dao.BookDao
类里面的 add()
方法进行增强
execution(* com.zzp.spring5.dao.BookDao.add(..))
(* 代表所有)
举例 2:对com.zzp.spring5.dao.BookDao
类里面的所有方法进行增强
execution(* com.zzp.spring5.dao.BookDao.*(..))
举例 3:对com.zzp.spring5.dao
包里面所有类,类里面的所有方法进行增强
execution(* com.zzp.spring5.dao.*.*(..))
6、AOP(AspectJ 注解)
1、创建类,在类里面定义方法
// 被增强的类
public class User {
public void add(){
System.out.println("add.....");
}
}
2、创建类增强类(编写增强逻辑)
(1)在增强类里面,创建方法,让不同方法代表不同通知类型
// 增强的类
public class UserProxy {
// 前置通知
public void before(){
System.out.println("before....");
}
}
3、进行通知配置
(1)在 spring配置文件中,开启注解扫描
引入 `context`名称空间
引入 `aop`名称空间
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.zzp.spring5.aopanno"></context:component-scan>
(2)使用注解创建 User 和 UserProxy 对象
添加@Component
注解
// 被增强的类
@Component
public class User {...}
// 增强的类
@Component
public class UserProxy {...}
(3)在增强类上面添加注解 @Aspect
import org.aspectj.lang.annotation.Aspect;
// 增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {...}
(4)在 spring配置文件中开启生成代理对象
<!-- 开启Aspect生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4、配置不同类型的通知
(1)在增强类里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
// 增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {
// 前置通知
// @Before注解表示作为前置通知
@Before(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before....");
}
}
5、单元测试
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
执行:
before....
add.....
6、测试其他通知类型
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {
// 前置通知
// @Before注解表示作为前置通知
@Before(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("before....");
}
// 最终通知
@After(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after....");
}
// 后置通知(返回通知)
@AfterReturning(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning....");
}
// 异常通知
@AfterThrowing(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing....");
}
// 环绕通知
@Around(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕之前....");
//被增强的方法执行
joinPoint.proceed();
System.out.println("环绕之后....");
}
}
单元测试:
环绕之前....
before....
add.....
afterReturning....
after....
环绕之后....
模拟异常情况:
// 被增强的类
@Component
public class User {
public void add(){
int i = 10/0;
System.out.println("add.....");
}
}
执行:
环绕之前....
before....
afterThrowing....
after....
java.lang.ArithmeticException: / by zero
7、相同的公共切入点抽取
添加@Pointcut
注解:
// 增强的类
@Component
@Aspect // 生成代理对象
public class UserProxy {
// 相同切入点抽取
@Pointcut(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void pointdemo(){
}
// 前置通知
// @Before注解表示作为前置通知
@Before(value = "pointdemo()")
public void before(){
System.out.println("before....");
}
....
测试,执行结果
环绕之前....
before....
afterThrowing....
after....
8、有多个增强类多个同一个方法进行增强,设置增强类优先级
(1)在增强类上面添加注解 @Order(数字类型),数字类型值越小优先级越高
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)
public class PersonProxy {
@Before(value = "execution(* com.zzp.spring5.aopanno.User.add(..))")
public void before(){
System.out.println("Person Before....");
}
}
// 增强的类
@Component
@Aspect // 生成代理对象
@Order(3)
public class UserProxy {...}
执行结果:
Person Before....
环绕之前....
before....
afterThrowing....
after....
9、完全使用注解开发
(1)创建配置类,不需要创建 xml配置文件
@Configuration
@ComponentScan(basePackages = {"com.zzp.spring5"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}
(2)测试方法:
@Test
public void test03(){
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigAop.class);
User user = context.getBean("user", User.class);
user.add();
}
执行结果:
Person Before....
环绕之前....
before....
afterThrowing....
after....
7、AOP(AspectJ 配置文件)
1、创建两个类,增强类和被增强类,创建方法
public class Book {
public void buy(){
System.out.println("buy.....");
}
}
public class BookProxy {
public void before(){
System.out.println("before.....");
}
}
2、在 spring配置文件创建两个类对象
<!-- 创建对象 -->
<bean id="book" class="com.zzp.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.zzp.spring5.aopxml.BookProxy"></bean>
3、在 spring配置文件中配置切入点
<!-- 配置aop增强 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="p" expression="execution(* com.zzp.spring5.aopxml.Book.buy(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="bookProxy">
<!-- 增强作用在具体的方法上 -->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
测试:
@Test
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.buy();
}
执行结果
before.....
buy.....
四、JdbTemplate
1、JdbcTemplate(概念和准备)
1、什么是JdbcTemplate
(1)Spring框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
2、准备工作
(1)引入相关 jar 包
druid-1.1.16.jar
mysql-connector-java-8.0.13.jar
spring-jdbc-5.3.6.jar
spring-tx-5.3.6.jar
spring-orm-5.3.6.jar
(2)在 spring配置文件配置数据库连接池
创建外部属性文件,properties
格式文件,写数据库信息
jdbc.properties
文件内容如下:
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT%2B8
prop.username=root
prop.password=123456
<!--引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--直接配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
(3)配置 JdbcTemplate 对象,注入 DataSource
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"/>
</bean>
(4)创建 service类,创建dao类,在 dao 注入 jdbcTemplate 对象
开启组件扫描配置:
<!-- 组件扫描 -->
<context:component-scan base-package="com.zzp.spring5"></context:component-scan>
service
@Service
public class BookService {
// 注入dao
@Autowired
private BookDao bookDao;
}
dao
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
}
2、JdbcTemplate 操作数据库(添加)
1、创建数据库的表和对应的实体类
CREATE TABLE `t_book` (
`userid` varchar(32) NOT NULL DEFAULT '' COMMENT '用户id',
`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
`userstatus` varchar(8) NOT NULL DEFAULT '0' COMMENT '用户状态'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
public class Book{
private String userid;
private String username;
private String userstatus;
get.. set.. 方法
}
2、编写 service 和 dao
(1)在 dao 进行数据库添加操作
@Service
public class BookService {
// 注入dao
@Autowired
private BookDao bookDao;
// 添加的方法
public void addBook(Book book){
bookDao.add(book);
}
}
public interface BookDao {
// 添加的方法
void add(Book book);
}
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 添加的方法
@Override
public void add(Book book) {
}
}
(2)调用 JdbcTemplate 对象里面 update方法实现添加操作
有两个参数:
第一个参数(sql
):sql 语句
第二个参数(args
):可变参数,设置 sql 语句的值
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 添加的方法
@Override
public void add(Book book) {
// 1 创建sql语句
String sql = "insert into t_book values(?,?,?)";
// 2 调用方法实现
Object[] args = {book.getUserid(),book.getUsername(),book.getUserstatus()};
int update = jdbcTemplate.update(sql,args);
System.out.println("影响的行数:"+update);
}
}
3、单元测试:
@Test
public void testJdbcTemplate(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setUserid("1");
book.setUsername("java");
book.setUserstatus("a");
bookService.addBook(book);
}
结果:
信息: {dataSource-1} inited
影响的行数:1
查看数据库
3、JdbcTemplate 操作数据库(修改和删除)
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 添加的方法
@Override
public void add(Book book) {
...
}
// 1.修改
@Override
public void updateBook(Book book) {
String sql = "update t_book set username=?,userstatus=? where userid=?";
Object[] args = {book.getUsername(),book.getUserstatus(),book.getUserid()};
int update = jdbcTemplate.update(sql,args);
System.out.println("修改的行数:"+update);
}
// 2.删除
@Override
public void deleteBook(String id) {
String sql = "delete from t_book where userid=?";
int update = jdbcTemplate.update(sql,id);
System.out.println("删除的行数:"+update);
}
}
测试:修改方法
@Test
public void testJdbcTemplate(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setUserid("1");
book.setUsername("java_update");
book.setUserstatus("a_1");
bookService.updateBook(book);
}
执行结果:
信息: {dataSource-1} inited
修改的行数:1
数据库:
测试:删除方法
@Test
public void testJdbcTemplate(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
bookService.deleteBook("1");
}
执行:
信息: {dataSource-1} inited
删除的行数:1
数据库:
4、JdbcTemplate 操作数据库(查询返回某个值)
1、查询表里面有多少条记录,返回是某个值
2、使用 JdbcTemplate 实现查询返回某个值代码
有两个参数:
第一个参数(sql
);sql 语句
第二个参数(requiredType
):返回类型 Class
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
...
// 查询表记录数
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
return count;
}
}
测试:
@Test
public void testJdbcTemplate(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
int count = bookService.findCount();
System.out.println("查询数量:" + count);
}
在数据库添加2条记录
执行结果:
信息: {dataSource-1} inited
查询数量:2
5、JdbcTemplate 操作数据库(查询返回对象)
1、场景:查询图书详情(一个对象内容)
2、使用 JdbcTemplate 实现查询返回对象
有三个参数
第一个参数(sql
);sql 语句
第二个参数(rowMapper
):RowMapper,是接口,针对返回不同的类型数据,使用这个接口里面实现类完成数据封装
第三个参数(args
);sql 语句值
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
...
// 查询返回对象
@Override
public Book findBookInfoById(String id) {
String sql = "select * from t_book where userid=? ";
Book book = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Book>(Book.class),id);
return book;
}
}
测试:
Book book = bookService.findOne(“1”);
System.out.println(“查询数据:” + book);
执行:
信息: {dataSource-1} inited
查询数据:Book{userid='1', username='aa', userstatus='a'}
6、JdbcTemplate 操作数据库(查询返回集合)
1、场景:查询图书列表分页
2、调用 JdbcTemplate 方法实现查询返回集合
有三个参数
第一个参数(sql
);sql 语句
第二个参数(rowMapper
):RowMapper,是接口,针对返回不同的类型数据,使用这个接口里面实现类完成数据封装
第三个参数(args
);sql 语句值
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
...
// 查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book ";
// 调用方法
List<Book> bookList = jdbcTemplate.query(sql,new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
}
测试:
List<Book> all = bookService.findAll();
System.out.println(“查询集合:” + all);
执行:
信息: {dataSource-1} inited
查询集合:[Book{userid='1', username='aa', userstatus='a'}, Book{userid='', username='', userstatus='0b'}]
7、JdbcTemplate 操作数据库(批量操作)
1、批量操作:操作表里面多条记录
2.、JdbcTemplate 实现批量添加操作
有两个参数:
第一个参数(sql
);sql 语句
第二个参数(batchArgs
):List集合,添加多条记录数据
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
...
// 批量添加
@Override
public void batchAdd(List<Object[]> batchArgs) {
String sql = "insert into t_book values (?,?,?) ";
int[] ints = jdbcTemplate.batchUpdate(sql,batchArgs);
System.out.println("批量添加:"+ Arrays.toString(ints));
}
}
测试:
List<Object[]> batchArgs = new ArrayList<>();
Object[] arg1 = {“3”,“java”,“a”};
Object[] arg2 = {“4”,“c++”,“b”};
Object[] arg3 = {“5”,“MySQL”,“c”};
batchArgs.add(arg1);
batchArgs.add(arg2);
batchArgs.add(arg3);
bookService.batchAdd(batchArgs);
执行:
信息: {dataSource-1} inited
批量添加:[1, 1, 1]
数据库:
3.、JdbcTemplate 实现批量修改操作
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
...
// 批量修改
@Override
public void batchUpate(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,userstatus=? where userid=?";
int[] ints = jdbcTemplate.batchUpdate(sql,batchArgs);
System.out.println("批量修改:"+ Arrays.toString(ints));
}
}
测试:
List<Object[]> batchArgs = new ArrayList<>();
Object[] arg1 = {“java_update”,“a_u”,“3”};
Object[] arg2 = {“c++_update”,“b_u”,“4”};
Object[] arg3 = {“MySQL_update”,“c_u”,“5”};
batchArgs.add(arg1);
batchArgs.add(arg2);
batchArgs.add(arg3);
bookService.batchUpdate(batchArgs);
执行:
信息: {dataSource-1} inited
批量修改:[1, 1, 1]
数据库:
4.、JdbcTemplate 实现批量删除操作
@Repository
public class BookDaoImpl implements BookDao{
//注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
...
// 批量删除
@Override
public void batchDelete(List<Object[]> batchArgs) {
String sql = "delete from t_book where userid=? ";
int[] ints = jdbcTemplate.batchUpdate(sql,batchArgs);
System.out.println("批量删除:"+ Arrays.toString(ints));
}
}
测试:
List<Object[]> batchArgs = new ArrayList<>();
Object[] arg1 = {“3”};
Object[] arg2 = {“4”};
batchArgs.add(arg1);
batchArgs.add(arg2);
bookService.batchDelete(batchArgs);
执行:
信息: {dataSource-1} inited
批量删除:[1, 1]
数据库:
五、事务管理
1、事务操作(事务概念)
1、什么是事务
(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
(2)典型场景:银行转账
小明 转账 100 给 小王
小明 少 100 ,小王 多 100
2、事务四个特性(ACID)
(1)原子性
(2)一致性
(3)隔离性
(4)持久性
2、事务操作(搭建事务操作环境)
简单的理解图:
1、创建数据库,添加记录
CREATE TABLE `t_account` (
`id` varchar(32) NOT NULL DEFAULT '' COMMENT '用户id',
`username` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
`money` int(8) NOT NULL DEFAULT 0 COMMENT '金额'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号表';
INSERT INTO t_account (id,username,money) VALUES("1","小明",1000);
INSERT INTO t_account (id,username,money) VALUES("2","小王",1000);
2、创建 service,搭建 dao,完成对象创建和注入关系
(1)service 注入dao,在dao注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource
JdbcTemplate 注入 DataSource 复用上面的实例:
public interface UserDao {
}
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Service
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
}
3、在dao创建两个方法:多钱和少钱的方法;在 service创建方法(转账的方法)
public interface UserDao {
//多钱
void addMoney();
//少钱
void reduceMoeny();
}
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
// 小明 转账 100 给 小王
//少钱
@Override
public void reduceMoeny() {
String sql = "update t_account set money= money-? where username=?";
jdbcTemplate.update(sql,100,"小明");
}
//多钱
@Override
public void addMoney() {
String sql = "update t_account set money= money+? where username=?";
jdbcTemplate.update(sql,100,"小王");
}
}
@Service
public class UserService {
//注入dao
@Autowired
private UserDao userDao;
//转账的方法
public void accountMoney(){
//小明少100
userDao.reduceMoeny();
//小王多100
userDao.addMoney();
}
}
测试:
@Test
public void testAccount(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
执行:
4、上面代码,如果正常执行没有问题的,但是如果代码执行过程中出现异常,就有问题
//转账的方法
public void accountMoney(){
//小明少100
userDao.reduceMoeny();
//模拟异常
int i= 10/0;
//小王多100
userDao.addMoney();
}
在把金额还原:
执行:
信息: {dataSource-1} inited
java.lang.ArithmeticException: / by zero
数据库:
(1)上面问题如何解决呢?
使用事务进行解决
(2)事务操作过程
//转账的方法
public void accountMoney(){
try {
// 第一步 开启事务
// 第二步 进行业务操作
//小明少100
userDao.reduceMoeny();
//模拟异常
int i= 10/0;
//小王多100
userDao.addMoney();
// 第三步 没有发送异常,提交事务
}catch (Exception e){
// 第四步 出现异常,事务回滚
}
}
3、事务操作(Spring 事务管理介绍)
1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作
(1)有两种方式:编程式事务管理 和 声明式事务管理(使用)
3、声明式事务管理
(1)基于注解方式(使用)
(2)基于 xml 配置文件方式
4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
5、Spring 事务管理 API
(1)通过一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
PlatformTransactionManager
接口(事务管理器)
4、事务操作(注解声明式事务管理)
1、在 spring 配置文件配置事务管理器
<!-- 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
2、在 spring 配置文件,开启事务注解
(1)在 spring 配置文件引入名称空间 tx
xmlns:tx="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
(2)开启事务注解
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在 service层类上面(获取service类里面方法上面)添加事务注解
(1) @Transactional
,这个注解添加到类上面,也可以添加方法上面
(2)如果把这个注解添加类上面,这个类里面所有的方法都添加了事务
(3)如果把这个注解添加方法上面,为这个方法添加事务
@Service
@Transactional
public class UserService {...}
测试,先把数据还原各为1000
执行:
信息: {dataSource-1} inited
java.lang.ArithmeticException: / by zero
数据库:
5、事务操作(声明式事务管理参数配置)
1、在 service类上添加注解@Transactional
,在这个注解里面可以配置事务相关参数属性
2、propagation
:事务传播行为
(1)多事务方法直接进行调用,这个过程中事务是如何进行管理的
事务的传播行为可以有传播属性指定。Spring 定义了 7 种类型传播行为:
传播属性 | 描述 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行 |
REQUIRED_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内,否则它可以不运行在事务中 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须运行在事务内,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。否则,就启动一个新的事务,并在它自己的事务内运行。 |
配置方式:propagation = Propagation.REQUIRED
默认是 REQUIRED
:
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService {...}
3、isolation
:事务隔离级别
(1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题
(2)有三个读问题:脏读、不可重复读、虚(幻)读
(3)脏读:一个未提交事务读取到另一个未提交事务的数据
(4)不可重复读:一个未提交事务读取到另一个提交事务修改的数据
(5)虚(幻)读:一个未提交事务读取到另一个事务添加的数据
(6)解决:通过设置事务隔离级别,解决读问题
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ UNCOMMITTED (读未提交) | 有 | 有 | 有 |
READ COMMITTED (读已提交) | 无 | 有 | 有 |
REPEATABLE READ (可重复读) | 无 | 无 | 有 |
SERIALIZABLE (串行化) | 无 | 无 | 无 |
配置方式:isolation = Isolation.REPEATABLE_READ
@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {...}
4、timeout
:超时时间
(1)事务需要在一定时间内进行提交,如果不提交则进行回滚
(2)默认值是 -1(没有时间限制超时),设置时间以秒单位进行计算
配置方式:timeout = 1
@Service
@Transactional(timeout = 1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {...}
5、readOnly
:是否只读
(1)读:查找操作;写:添加修改删除操作
(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true,设置成 true 之后,只能查询
配置方式:readOnly = false
@Service
@Service
@Transactional(readOnly = false,timeout = 1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {...}
6、rollbackFor
:回滚
(1)设置出现哪些异常进行事务回滚
7、noRollbackFor
:不回滚
(1)设置出现哪些异常不进行事务回滚
6、事务操作(XML 声明式事务管理)
1、在 spring 配置文件中进行配置
第一步 配置事务管理器
第二步 配置通知
第三步 配置切入点和切面
<!-- 组件扫描 -->
<context:component-scan base-package="com.zzp.spring5"></context:component-scan>
<!--引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--直接配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 1 创建事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2 配置通知 -->
<tx:advice id="txadvice">
<!-- 配置事务参数 -->
<tx:attributes>
<!-- 指定哪种规则的方法上面添加事务 -->
<tx:method name="accountMoney" propagation="REQUIRED" isolation="REPEATABLE_READ"/>
<!-- <tx:method name="account*"/> *:通配符-->
</tx:attributes>
</tx:advice>
<!-- 3 配置切入点和切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pt" expression="execution(* com.zzp.spring5.service.UserService.*(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
单元测试:
@Test
public void testAccount2(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
执行:
信息: {dataSource-1} inited
java.lang.ArithmeticException: / by zero
at com.zzp.spring5.service.UserService.accountMoney(UserService.java:32)
数据库无变化:
7、事务操作(完全注解开发)
1、创建配置类,使用配置类替代 xml配置文件
@Configuration //配置类
@ComponentScan(basePackages = {"com.zzp.spring5"})
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库的连接池
@Bean
public DruidDataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT%2B8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
//创建JdbcTemlplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
// 到ioc容器中根据类型找到dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
测试:
@Test
public void testAccount3(){
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
执行:
信息: {dataSource-1} inited
java.lang.ArithmeticException: / by zero
at com.zzp.spring5.service.UserService.accountMoney(UserService.java:32)
六、Spring5 新特性
1、Spring5 框架新功能
1、通过使用泛型等特性提高可读性
2、整个 Spring 框架打代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除
3、Spring 5.0框架自带了通用的日志封装
(1)Spring5 已移除了 Log4jConfigListener,官方建议使用 Log4j2
(2)Spring5 框架整合 Log4j2
第一步 引入jar包
引入项目中
第二步 创建 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>
测试;运行上面使用的testAccount3()
方法
运行结果:
先前是 INFO 级别日志输出 现在改成 DEBUG 日记级别输出
测试输出:更多的日记输出
手动在控制台日志输出:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserLog {
private static final Logger log = LoggerFactory.getLogger(UserLog.class);
public static void main(String[] args) {
log.info("hello log4j2");
log.warn("hello log4j2");
}
}
执行结果,控制台输出:
4、Spring5 框架核心容器支持@Nullable注解
(1)@Nullable
注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
(2)注解用在方法上面,方法返回值可以为空
(3)注解使用在方法参数里面,方法参数可以为空
(4)注解使用在属性上面,属性值可以为空
5、Spring5 核心容器函数式风格GenericApplicationContext
创建User类:
public class User {
};
单元测试:
// 函数式风格创建对象,交给spring进行管理
@Test
public void testGenericApplicationContext(){
// 1 创建 GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
// 2 调用context的方法对象注册
context.refresh();
context.registerBean("user1",User.class,() -> new User() );
// 3 获取在spring注册的对象
// User user = (User)context.getBean("com.zzp.spring5.test.User"); // context.registerBean(User.class,() -> new User() ); 不指定 beanName
User user = (User)context.getBean("user1");
System.out.println(user);
}
执行:
com.zzp.spring5.test.User@59e5ddf
6、Spring5 支持整合 JUnit5
(1)整合 JUnit4
第一步 引入 Spring 相关针对测试依赖
spring-test-5.3.6.jar
第二步 创建测试类 ,使用注解方式完成
使用 org.junit.Test
包
import com.zzp.spring5.service.UserService;
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) // 指定单元测试框架junit4
@ContextConfiguration("classpath:bean1.xml") // 加载配置文件
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test1(){
userService.accountMoney();
}
}
执行:
(2)Spring5 整合 JUnit5
第一步 引入 JUnit5 的 jar 包
第二步 创建测试类,使用注解完成
使用 org.junit.jupiter.api.Test
包
import com.zzp.spring5.service.UserService;
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:bean1.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1(){
userService.accountMoney();
}
}
执行:
(3)使用一个复合注解替代上面两个注解完成整合: @SpringJunitConfig
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:bean1.xml")
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5 {...}
执行效果是和上面一样的
2、Spring5 框架新功能(Webflux)
1、SpringWebflux介绍
(1)是 Spring5 添加新的模块,用于 web 开发的,功能 SpringMVC 类似的,Webflux 使用当前一种比较流行的响应式编程出现的框架
(2)使用传统 web 框架,比如 SpringMVC ,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持的,核心是基于 Reactor(响应式编程) 的相关 API 实现的。
(3)解释什么是异步非阻塞
异步同步
非阻塞和阻塞
--上面都是针对对象不一样
异步同步针对调用者,调用者发送请求,
如果等着对方回应之后才去做其他事情就是同步
如果发送请求之后不等着对方回应就去做其他事情就是异步
--
阻塞和非阻塞针对被调用者,被调用者收到请求之后,
做完请求任务之后才给出反馈就是阻塞,
收到请求之后马上给出反馈后再去做事情就是非阻塞
(4)Webflux 特点:
第一个 非阻塞式:在有限的资源下,提高系统吞吐量和伸缩性,处理更多的请求,以 Reactor 为基于响应式编程
第二个 函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
(5)比较 SpringMVC
第一 两个框架都可以使用注解方式,都运行在 Tomcat 等容器上
第二 SpringMVC 采用命令式编程,Webflux 采用异步响应式编程
2、响应式编程
(1)什么是响应式编程
简称RP(Reactive Programming)
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
例如,对于 a=b+c 这个表达式的处理,在命令式编程中,会先计算 b+c 的结果,再把此结果赋值给 变量a,因此 b,c 两值的变化不会对 变量a 产生影响。但在响应式编程中,变量a 的值会随时跟随 b,c 的变化而变化。
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
响应式编程最初是为了简化交互式用户界面的创建和实时系统动画的绘制而提出来的一种方法,但它本质上是一种通用的编程范式。
例如,在MVC软件架构中,响应式编程允许将相关模型的变化自动反映到视图上,反之亦然。
(8)Java8 及其之前版本
- 通过的观察者模式两个类
Observer
和Observable
代码演示:
import java.util.Observable;
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observer = new ObserverDemo();
// 添加观察者
observer.addObserver((o,rag)->{
System.out.println("发送变化");
});
observer.addObserver((o,rag)->{
System.out.println("手动被观察者通知,准备改变");
});
observer.setChanged(); // 监控数据变化
observer.notifyObservers(); // 通知
}
}
运行:
手动被观察者通知,准备改变
发送变化
3、响应式编程( Reactor 实现)
(1)响应式编程操作中,Reactor 是满足 Reactive 规范
(2)Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作符。Flux 对象实现发布者,返回 N 个元素;Mono 也实现发布者,返回 0 或者 1 个元素
(3)Mono 和 Flux 都是数据流的发布者,使用 Mono 和 Flux 都可以发出三种数据信号:元素值、错误信号、完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者
(4)代码演示 Mono 和 Flux
第一步 引入依赖:
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.2.5.RELEASE</version>
</dependency>
第二步 编写代码
public static void main(String[] args) {
// 发布元素 just方法直接声明
Flux.just(1,2,3,4);
Mono.just(1);
// 数组
Integer[] array = {1,2,3,4};
Flux.fromArray(array);
// 集合
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
// 流
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
}
(5)三种信号特点
错误信号和完成信号都是终止信号,不能共存
如果没有发生任何元素值,而是直接发送错误或者完成信号,表示是空数据流
如果没有错误信号,没有完成信号,表示是无限数据流
错误信号
Flux.error(Throwable error);
(6)调用 just 或者其他方法只是声明数据流,数据流没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的
发布-订阅
public static void main(String[] args) {
// 发布元素 just方法直接声明
Flux.just(1,2,3,4).subscribe(System.out::println);
Mono.just(1).subscribe(System.out::println);
}
控制台输出:
(7)操作符
对数据流进行一道道操作,成为操作符,比如工厂流水线
第一 map
元素映射为新元素
第二 flatMap
元素映射为流
- 把每个元素转换流,把转换之后多个流合并大的流
4、Webflux 执行流程和核心 API
SpringWebFlux 是基于 Reactor 实现,默认容器是 Netty 是高性能,NIO 框架,异步非阻塞框架
(1)Netty
-
BIO 阻塞
-
NIO 非阻塞
(2)SpringWebflux 执行过程和 SringMVC 相似
- SpringWebflux 核心控制器
DispatcherHandler
,实现接口WebHandler
- 接口
WebHandler
有一个方法handle
修改依赖artifactId
:spring-boot-starter
为‘spring-boot-starter-webflux’
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
WebHandler
源码:
package org.springframework.web.server;
import reactor.core.publisher.Mono;
public interface WebHandler {
Mono<Void> handle(ServerWebExchange var1);
}
源码实现:
package org.springframework.web.reactive;
...
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
....
public Mono<Void> handle(ServerWebExchange exchange) {// 放入http请求响应信息
return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
return mapping.getHandler(exchange);//根据请求地址获取对应的 mapping
}).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
return this.invokeHandler(exchange, handler);// 调用具体的业务方法
}).flatMap((result) -> {
return this.handleResult(exchange, result);// 处理结果返回
});
}
...
}
(3)SpringWebflux 里面核心控制器 DispatcherHnadler
,负责请求的处理
HandlerMapping
:请求查询到处理的方法HandlerAdapter
:真正负责处理业务请求处理HandlerResultHandler
:响应结果处理
(4)SpringWebflux 实现函数式编程,两个接口:RouterFunction
(路由处理) 和 HandlerFunction
(处理函数)
5、SpringWebflux (基于注解编程模型)
SpringWebflux 实现方式有两种:注解编程模型和函数式编程模型
使用注解编程模型方式,和之前 SpringMVC 使用相似,只需要把相关依赖配置到项目中,
SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器
第一步 创建 SpringBoot 工程(webfluxdemo1),引入 Webflux 依赖
修改pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<groupId>com.zzp</groupId>
<artifactId>webfluxdemo1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>webfluxdemo1</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 引入webflux依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
第二步 配置启动的端口号
server.port=8081
# 应用名称
spring.application.name=webfluxdemo1
第三步 创建包和相关类
- 实体类
package com.zzp.spring.entity;
//实体类
public class User {
private String name;
private String gender;
private Integer age;
public User(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
- 创建接口定义操作方法
package com.zzp.spring.service;
import com.zzp.spring.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
//用户操作接口
public interface UserService {
//根据id查询用户
Mono<User> getUserById(int id);
//查询所有用户
Flux<User> getAllUser();
//添加用户
Mono<Void> saveUserInfo(Mono<User> user);
}
- 接口实现类
package com.zzp.spring.service.impl;
import com.zzp.spring.entity.User;
import com.zzp.spring.service.UserService;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Repository
public class UserServiceImpl implements UserService {
//创建map集合存储数据
private final Map<Integer,User> userMap = new HashMap<>();
public UserServiceImpl(){
this.userMap.put(1,new User("小明","男",20));
this.userMap.put(2,new User("小王","男",30));
this.userMap.put(3,new User("小芳","女",25));
}
//根据id查询
@Override
public Mono<User> getUserById(int id) {
return Mono.justOrEmpty(this.userMap.get(id));
}
//查询所有用户
@Override
public Flux<User> getAllUser() {
return Flux.fromIterable(this.userMap.values());
}
//添加用户
@Override
public Mono<Void> saveUserInfo(Mono<User> userMono) {
return userMono.doOnNext(person->{
//向map集合里面放值
int size = userMap.size() + 1;
userMap.put(size,person);
}).thenEmpty(Mono.empty());
}
}
- 创建 controller
package com.zzp.spring.controller;
import com.zzp.spring.entity.User;
import com.zzp.spring.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
public class UserController {
//注入service
@Autowired
private UserService userService;
//id查询
@GetMapping("/user/{id}")
public Mono<User> getUserId(@PathVariable int id){
return userService.getUserById(id);
}
//查询所有
@GetMapping("/user")
public Flux<User> getUsers(){
return userService.getAllUser();
}
//添加
@GetMapping("/saveuser")
public Mono<Void> saveUser(@RequestBody User user){
Mono<User> userMono = Mono.just(user);
return userService.saveUserInfo(userMono);
}
}
启动,测试
id查询:http://localhost:8081/user/1
查所有:http://localhost:8081/user
- 说明
SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC + Servlet + Tomcat
SpringWebflux 方式实现,异步非阻塞,基于SpringWebflux + Reactor + Netty
6、SpringWebflux (基于函数式编程模型)
(1)在使用函数式编程模型操作的时候,需要自己初始化服务器(Netty
)
(2)基于函数式编程模型时候,有两个核心接口:
-
RouterFunction
:实现路由功能,请求转发给对应的handler
-
HandlerFunction
:处理请求生成响应的函数核心任务定义这两个函数式接口的实现并且启动需要的服务器。
(3)SpringWebflux 请求和响应不再是 ServletRequest
和 ServletResponse
,而是ServerRequest
和 ServerResponse
第一步 把注解编程模型工程复制一份
第二步 创建 Handler
(具体实现方法)
package com.zzp.spring.handler;
import com.zzp.spring.entity.User;
import com.zzp.spring.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class UserHandler {
private UserService userService;
public UserHandler(UserService userService){
this.userService = userService;
}
//根据id查询
public Mono<ServerResponse> getUserById(ServerRequest request){
// 获取id值
int userId = Integer.valueOf(request.pathVariable("id"));
//空置处理
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
// 调用service方法得到数据
Mono<User> userMono = this.userService.getUserById(userId);
// 把userMono进行转换返回
// 使用 Reactor操作符 flatMap
return userMono.flatMap(person ->
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(Mono.just(person), User.class))
.switchIfEmpty(notFound);
}
//查询所有
public Mono<ServerResponse> getAllUsers(ServerRequest request){
//调用service得到结果
Flux<User> users = this.userService.getAllUser();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
}
//添加
public Mono<ServerResponse> saveUser(ServerRequest request){
//得到user对象
Mono<User> userMono = request.bodyToMono(User.class);
return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}
}
第三步 初始化服务器,编写 Router
- 创建路由的方法
package com.zzp.spring;
import com.zzp.spring.handler.UserHandler;
import com.zzp.spring.service.UserService;
import com.zzp.spring.service.impl.UserServiceImpl;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
public class Server {
//1、创建Router路由
public RouterFunction<ServerResponse> routerFunction(){
//创建handler对象
UserService userService = new UserServiceImpl();
UserHandler handler = new UserHandler(userService);
//设置路由
return RouterFunctions.route(
GET("/user/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
.andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
}
- 创建服务器完成适配
package com.zzp.spring;
import com.zzp.spring.handler.UserHandler;
import com.zzp.spring.service.UserService;
import com.zzp.spring.service.impl.UserServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
public class Server {
//1、创建Router路由
public RouterFunction<ServerResponse> routerFunction(){...}
//2、创建服务器完成适配
public void createReactorServer(){
//路由和handler适配
RouterFunction<ServerResponse> route = routerFunction();
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
//创建服务器
HttpServer httpServer = HttpServer.create();
httpServer.handle(adapter).bindNow();
}
}
- 最终调用
public static void main(String[] args) throws IOException {
Server server = new Server();
server.createReactorServer();
System.out.println("enter to exit");
System.in.read();
}
执行测试:
(4) 使用 WebClinet 调用
先执行函数式编程模型Server类 mian
方法,获取端口号
package com.zzp.spring;
import com.zzp.spring.entity.User;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
public class Clinet {
public static void main(String[] args) {
//调用服务器地址
WebClient webClient = WebClient.create("http://127.0.0.1:52565");
//根据id查询
String id = "1";
User user = webClient.get().uri("/user/{id}",id)
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
System.out.println(user.getName());
//查询所有
Flux<User> userFlux = webClient.get().uri("/users")
.accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
userFlux.map(per -> per.getName())
.buffer().doOnNext(System.out::println).blockFirst();
}
}
执行,报错了
User
类创建无参构造
public class User {
...
public User() {
}
...
再执行,查看控制台输出