Spring——入门介绍(以版本5.2为例)

目录

本文章的笔记整理来自视频https://www.bilibili.com/video/BV1Vf4y127N5

1.Spring框架概述

(1)Spring 是轻量级的开源的 JavaEE 框架,它可以解决企业应用开发的复杂性。
(2)Spring 有两个核心部分:IoC 和 AOP

IoC控制反转,把创建对象过程交给 Spring 进行管理
Aop面向切面,不修改源代码进行功能增强

(3)Spring 有以下特点:
① 方便解耦,简化开发
② Aop 编程支持
③ 方便程序测试
④ 方便和其他框架进行整合
⑤ 方便进行事务操作
⑥ 降低 API 开发难度

2.Spring入门案例

(1)下载Spring5,地址为https://repo.spring.io/ui/native/release/org/springframework/spring/,选择版本5.26
在这里插入图片描述
在这里插入图片描述
(2)在IDEA中创建一个普通的Java工程,并且导入需要的jar包,需要注意的是其中的commons-logging-1.1.1.jar并不是Spring本身自带的,需要另外下载
在这里插入图片描述
此外还需要在项目中添加这些依赖
在这里插入图片描述
(3)编写代码
定义类User,并且定义方法add()

package com.atguigu.spring5;

public class User {
    public void add(){
        System.out.println("add......");
    }    
}

编写配置文件bean1.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置User对象创建-->
    <bean id="user" class="com.atguigu.spring5.User"></bean>

</beans>

编写测试方法

package com.atguigu.spring5.TestDemo;

import com.atguigu.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.获取配置创建的对象,此处体现了IoC控制反转,即把创建对象过程交给 Spring 进行管理
        User user = context.getBean("user", User.class);
        System.out.println(user);
        user.add();
    }

}

(4)结果输出
在这里插入图片描述

3.IoC介绍

(1)IoC概述

IoC,其英文全称为Inversion of Control,即控制反转。它把对象创建和对象之间的调用过程,交给 Spring 进行管理,这样可以降低耦合度,上面的入门案例就是IoC的简单实现。

(2)IoC底层原理

(2.1)IoC底层原理涉及的主要技术有xml 解析、工厂模式和反射
(2.2)IoC过程:
① 在xml配置文件配置创建的对象

<bean id="user" class="com.atguigu.spring5.User"></bean>

② 用service类和dao类创建工厂类(IoC 思想基于 IoC 容器完成,IoC 容器底层就是对象工厂

class UserFact ory {
	public static UserDao getDao(){
		//xml解析
		String classValue = class属性值;
		//通过反射创建对象
		Class clazz = Class.forName(classValue);
		//返回对象
		return (UserDao)clazz.newInstance();
	}
}

(3)IoC接口

Spring 提供 IOC 容器实现两种方式(两个接口):
① BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,一般不提供开发人员进行使用。加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
② ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。加载配置文件时候就会把在配置文件对象进行创建。此外,ApplicationContext 接口有实现类:
在这里插入图片描述

4.IoC操作——Bean 管理

(1)Bean 管理概述

Bean 管理指的是两个操作,即Spring 创建对象和 Spirng 注入属性
Bean 管理操作有两种方式,即基于 xml 配置文件方式实现和基于注解方式实现

(2)Bean 管理——基于 xml 配置方式

① 基于 xml 方式创建对象

有关xml相关的具体知识可以查看Java基础——XML这篇文章。

<!--
	(1)在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
	(2)在 bean 标签有很多属性,介绍常用的属性:
	* id 属性:唯一标识
	* class 属性:类全路径(包类路径)
	(3)创建对象时候,默认也是执行无参数构造方法完成对象创建
-->
<bean id="user" class="com.atguigu.spring5.User"></bean>

② 基于 xml 方式注入属性

此处需要知道一个概念——DI(Dependency Injection),即依赖注入,也就是注入属性。
第一种注入方式:使用 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;
	} 
	//测试set方法注入
    public void testSet(){
        System.out.println("bname:"+bname);
        System.out.println("bauthor:"+bauthor);
    }
}
<!--在spring 配置文件配置对象创建,配置属性注入--> 
<bean id="book" class="com.atguigu.spring5.Book">
	 <!--
	 	使用 property 完成属性注入
	 	name:类里面属性名称
		value:向属性注入的值
	 -->
	<property name="bname" value="西游记"></property>
	<property name="bauthor" value="吴承恩"></property>
</bean>
package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//测试
public class TestSpring5 {
    @Test
    public void test(){
        //1.加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        //2.获取配置创建的对象
        Book book = context.getBean("book", Book.class);
        System.out.println(book);
        book.testSet();
    }
}

结果如下:
在这里插入图片描述
第二种注入方式:使用有参数构造进行注入

package com.atguigu.spring5;

public class Order{
    //属性
    private String oname;
    private String address;
    //有参数构造
    public Order(String oname,String address) {
        this.oname = oname;
        this.address = address;
    }
    
    public void testconstructorArg(){
        System.out.println("oname:"+oname);
        System.out.println("address:"+address);
    }
}
<!--有参数构造注入属性-->
<bean id="order" class="com.atguigu.spring5.Order">
    <constructor-arg name="oname" value="电脑"></constructor-arg>
    <constructor-arg name="address" value="China"></constructor-arg>
</bean>
package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.Order;
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.获取配置创建的对象
        Order order = context.getBean("order", Order.class);
        System.out.println(order);
        order.testconstructorArg();
    }
}

结果如下:
在这里插入图片描述
第三种注入方式:p 名称空间注入(第一种方式的简化)
在配置文件中添加p名称空间
在这里插入图片描述

<!--进行属性注入,在 bean 标签里面进行操作--> 
<bean id="book" class="com.atguigu.spring5.Book" p:bname="西游记" p:bauthor="吴承恩"></bean>

③ 基于 xml 方式注入null值和包含特殊符号的值

<!--(1)null值--> 
<property name="address">
	<null/>
</property>
<!--
	(2)属性值包含特殊符号,处理方法如下:
 	将特殊符号进行转义,例如使用 &lt; 或 &gt; 把<>进行转义 
 	把带特殊符号内容写到 CDATA
-->
<property name="address">
	<value><![CDATA[<<杭州>>]]></value>
</property>

④ 基于 xml 方式注入外部 bean

package com.atguigu.spring5.dao;

public interface UserDao {
    void update();
}
package com.atguigu.spring5.dao;

public class UserDaoImpl implements UserDao {
    @Override
    public void update() {
        System.out.println("dao update...");
    }
}
package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;

public class UserService {
    //创建 UserDao 类型属性,生成 set 方法
    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void add() {
        System.out.println("service add...............");
        userDao.update();
    }
}
<!--创建 service 和 dao 对象-->
<bean id="userService" class="com.atguigu.spring5.service.UserService">
	<!--
		注入 userDao 对象
		name 属性:类里面属性名称
		ref 属性:创建 userDao 对象 bean 标签 id 值
	-->
    <property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>

⑤ 基于 xml 方式注入内部 bean

package com.atguigu.spring5.bean;

public class Dept {
    private String dname;
    public void setDname(String dname) {
        this.dname = dname;
    }
}
package com.atguigu.spring5.bean;

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;
    }
    
    @Override
    public String toString() {
        return "Emp{" +
                "ename='" + ename + '\'' +
                ", gender='" + gender + '\'' +
                ", dept=" + dept +
                '}';
    }
}
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
	<!--设置两个普通属性-->
	<property name="ename" value="lucy"></property>
	<property name="gender" value=""></property>
	<!--设置对象类型属性-->
	<property name="dept">
	    <bean id="dept" class="com.atguigu.spring5.bean.Dept">
	        <property name="dname" value="销售部"></property>
	    </bean>
	</property>
</bean>

⑥ 基于 xml 方式进行级联赋值

第一种写法:

<!--级联赋值--> 
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
	<!--设置两个普通属性-->
	<property name="ename" value="lucy"></property>
	<property name="gender" value=""></property>
	<!--级联赋值-->
	<property name="dept" ref="dept"></property>
</bean> 
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
	<property name="dname" value="销售部"></property>
</bean>

第二种写法:

//在Emp.java中添加getDept()方法
public Dept getDept() {
	return dept;
}
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
	<!--设置两个普通属性-->
	<property name="ename" value="lucy"></property>
	<property name="gender" value=""></property>
	<!--级联赋值-->
	<property name="dept" ref="dept"></property>
	<property name="dept.dname" value="技术部"></property>
</bean> 
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
	<property name="dname" value="财务部"></property>
</bean>

⑦ 基于 xml 方式注入集合属性

package com.atguigu.spring5.collectiontype;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Stu {
    
    //1 数组类型属性
    private String[] courses;
    //2 list 集合类型属性
    private List<String> list;
    //3 map 集合类型属性
    private Map<String,String> maps;
    //4 set 集合类型属性
    private Set<String> sets;
    //5.学生所学的多门课程
    private List<Course> courseList;
    
    public void setCourseList(List<Course> courseList) {
        this.courseList = courseList;
    }
    public void setSets(Set<String> sets) {
        this.sets = sets;
    }
    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 testCollection(){
        System.out.println(Arrays.toString(courses));
        System.out.println(list);
        System.out.println(maps);
        System.out.println(sets);
        System.out.println(courseList);
    }
}
<!--1 集合类型属性注入-->
<bean id="stu" class="com.atguigu.spring5.collectiontype.Stu">
    <!--数组类型属性注入-->
    <property name="courses">
        <array>
            <value>java 课程</value>
            <value>数据库课程</value>
        </array>
    </property>
    <!--list 类型属性注入-->
    <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="sets">
        <set>
            <value>MySQL</value>
            <value>Redis</value>
        </set>
    </property>
    	<!--注入 list 集合类型,值是对象--> 
    	<property name="courseList">
		<list>
			<ref bean="course1"></ref>
			<ref bean="course2"></ref>
		</list>
	</property>
</bean>
<!--创建多个 course 对象--> 
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
	<property name="cname" value="Spring5 框架"></property>
</bean> 
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
	<property name="cname" value="MyBatis 框架"></property>
</bean>

⑦ 基于 xml 方式把集合注入部分提取出来

前期准备:在 spring 配置文件中引入名称空间 util

<?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:util="http://www.springframework.org/schema/util"
 	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/util 
	http://www.springframework.org/schema/util/spring-util.xsd">
<!--提取 list 集合类型属性注入-->

	<util:list id="bookList">
		<value>西游记</value>
		<value>水浒传</value>
	</util:list>
	<!--提取 list 集合类型属性注入使用--> 
	<bean id="book" class="com.atguigu.spring5.collectiontype.Book">
		<property name="list" ref="bookList"></property>
	</bean>

</beans>

(3)Bean 管理——FactoryBean

Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)

普通 bean在配置文件中定义 bean 类型就是返回类型
工厂 bean在配置文件定义 bean 类型可以和返回类型不一样
package com.atguigu.spring5.factorybean;

import com.atguigu.spring5.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;

//继承接口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 false;
    }
}
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.collectiontype.Course;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring5 {
    @Test
    public void test(){
        //1.加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
        //2.获取配置创建的对象
        //定义 bean 类型可以和返回类型不一样
        Course course = context.getBean("myBean", Course.class);
        System.out.println(course);
    }
}

(4)Bean 管理——Bean的作用域

① 在 Spring 中,默认情况下的 bean 是单实例对象

package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring5 {
    @Test
    public void test(){
        //1.加载spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        //2.获取配置创建的对象
        Book book1 = context.getBean("book", Book.class);
        Book book2 = context.getBean("book", Book.class);
        //若输出结果一样,则说明获取到的bean是单实例的
        System.out.println("book1:"+book1);
        System.out.println("book2:"+book2);
    }
}

输出结果如下:
在这里插入图片描述
② 在Spring 配置文件中, bean 标签里面的属性 scope 用于设置单实例或者多实例

scope含义
singleton(默认)单实例,加载 spring 配置文件时就会创建单实例对象
prototype多实例,在调用getBean()方法时候创建多实例对象
request将创建完成的bean放入request中
session将创建完成的bean放入session 中

(5)Bean 管理——Bean的生命周期

① Bean的生命周期:从对象创建到对象销毁的过程。

② Bean的生命周期一般分为以下几个阶段:
1)通过构造器创建 bean 实例(无参数构造)
2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
4)bean 可以使用了(对象获取到了)
5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

package com.atguigu.spring5.bean;

public class LifeCycle {
    
    //无参数构造
    public LifeCycle() {
        System.out.println("第一步 执行无参数构造创建 bean 实例");
    }
    private String id;
    public void setId(String id) {
        this.id = id;
        System.out.println("第二步 调用 set 方法设置属性值");
    }
    //创建执行的初始化的方法
    public void initMethod() {
        System.out.println("第三步 执行初始化的方法");
    }
    //创建执行的销毁的方法
    public void destroyMethod() {
        System.out.println("第五步 执行销毁的方法");
    }
}
<!--
	init-method:初始化bean时执行的方法
	destroy-method:销毁bean时执行的方法
-->
<bean id="lifecycle" class="com.atguigu.spring5.bean.LifeCycle" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1"></property>
</bean>
package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.bean.LifeCycle;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring5 {

    @Test
    public void test(){
        //1.加载spring配置文件
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("bean5.xml");
        //2.获取配置创建的对象
        LifeCycle lifeCycle = context.getBean("lifecycle", LifeCycle.class);
        System.out.println("第四步 获取创建 bean 实例对象");
        System.out.println(lifeCycle);
        //手动销毁 bean 实例
        context.close();
    }
}

结果如下:
在这里插入图片描述
③ 如果考虑 bean 的后置处理器,完整的 bean 生命周期一共有七个阶段:
1)通过构造器创建 bean 实例(无参数构造)
2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
6)bean 可以使用了(对象获取到了)
7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

如果想要实现后置处理器效果,那么只需要创建一个类(例如MyBeanPost)继去承接口BeanPostProcessor,并且实现其中的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法,并且配置后置处理器即可,此外可知这两个方法的执行分别对应上面7个阶段中的第3和第4个阶段。

<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>

(6)Bean 管理——xml自动装配

package com.atguigu.spring5.autowire;

public class Dept {
    @Override
    public String toString() {
        return "Dept{}";
    }
}
package com.atguigu.spring5.autowire;

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);
    }
}
<!--实现自动装配
     bean 标签属性 autowire,配置自动装配
     autowire 属性常用两个值:
     byName:根据属性名称注入,注入值 bean 的 id 值和类属性名称一样
     byType:根据属性类型注入,但是相同类型的bean不能定义多个,否则会报错
-->
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName/byType">
    <!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>

(7)Bean 管理——外部属性文件

此处以配置数据库信息为例,连接池选用的是druid,此外需要将druid连接池依赖 jar 包引入到项目中,这里选用的版本为1.1.9(选择其它合适的版本均可)
① 直接配置数据库信息

<!--直接配置连接池-->
<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>

② 引入外部属性文件配置数据库连接池
创建外部属性文件(即properties 格式文件)并用其来保存数据库信息,此处命名为jdbc.properties

prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql:///user_db
prop.username=root
prop.password=123456

然后再把文件jdbc.properties引入到 spring 配置文件中,值得注意的是在此之前需要引入 context 名称空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入外部属性文件-->
    <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>
    
</beans>

(8)Bean 管理——基于注解方式

① 注解概述

注解的具体相关知识可查看这篇文章Java基础——注解
1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)
2)使用注解,注解作用在类上面,方法上面,属性上面
3)在Spring中使用注解目的:简化 xml 配置

② Spring 针对 Bean 管理的对象创建提供注解

1)@Component(普通注解)
2)@Service(一般用在业务逻辑层或Service层)
3)@Controller(一般用于Web层)
4)@Repository(一般用于持久层或Dao层)
注:上面四个注解都可以用来创建 bean 实例

③ 基于注解方式实现对象创建

1)引入相关依赖spring-aop-5.2.6.RELEASE.jar
2)开启组件扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--
        开启组件扫描(注意需要添加context命名空间)
        1 如果扫描多个包,多个包使用逗号隔开
        2 base-package="com.atguigu":扫描com.atguigu包下所有类的注解
    -->
    <context:component-scan base-package="com.atguigu"></context:component-scan>
</beans>

3)创建类,在类上面添加创建对象注解

package com.atguigu.spring5.service;

import org.springframework.stereotype.Component;
/*
    1.在注解里面 value 属性值可以省略不写
    2.如果不写,那么默认值就是类名称的首字母小写,例如UserService --> userService
    3.@Component(value="userService")相当于<bean id="userService" class="com.atguigu.spring5.service.UserService"/>
    4.注解@Component也可以写成@Service、@Controller、@Repository中的任何一个
*/
@Component(value="userService")
public class UserService {
    public void add(){
        System.out.println("add...");
    }
}

4)测试

package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring5 {

    @Test
    public void test(){
        //1.加载spring配置文件
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("bean1.xml");
        //2.获取配置创建的对象
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

结果如下:
在这里插入图片描述

④ 开启组件扫描中的细节配置

<!--
	示例1:
	use-default-filters="false":表示现在不使用默认 filter,自己配置 filter
	context:include-filter:设置扫描哪些内容
-->
<context:component-scan base-package="com.atguigu" use-default-filters="false">
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!--
	示例2:
	base-package="com.atguigu":扫描com.atguigu包下所有类的注解
 	context:exclude-filter:设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.atguigu">
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

⑤ 基于注解方式实现属性注入

1)@Autowired:根据属性类型进行自动装配
先把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
UserDao.java

package com.atguigu.spring5.dao;

public interface UserDao {
    public void add();
}

UserDaoImpl.java

package com.atguigu.spring5.dao;

import org.springframework.stereotype.Repository;

@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {
        System.out.println("dao add...");
    }
}

UserService.java

package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service(value="userService")
public class UserService {
  
    //在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解(不需要添加 set 方法)
    @Autowired
    private UserDao userDao;
    
    public void add(){
        System.out.println("service add...");
        userDao.add();
    }
}

TestSpring5.java

package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring5 {

    @Test
    public void test(){
        //1.加载spring配置文件
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("bean1.xml");
        //2.获取配置创建的对象
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

结果如下:
在这里插入图片描述
2)@Qualifier:根据名称进行注入,需要和@Autowired 一起使用
UserService.java

package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service(value="userService")
public class UserService {
    
    //有多个接口类的实现对象时,@Autowired并不能准确寻找需要注入哪个对象,所以此时需要@Qualifier按照具体名称进行注入
    @Autowired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;
    
    public void add(){
        System.out.println("service add...");
        userDao.add();
    }
}

3)@Resource:根据类型或名称注入

//1.根据类型进行注入
//@Resource 
//2.根据名称进行注入
@Resource(name = "userDaoImpl") 
private UserDao userDao;

注:@Resource是javax.annotation.Resource中的
4)@Value:注入普通类型属性

@Value(value = "abc")
private String name;

⑥ 完全注解开发

1)创建配置类,替代 xml 配置文件

package com.atguigu.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//@Configuration:作为配置类,替代 xml 配置文件
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
public class SpringConfig {
    
}

2)重新编写测试类

package com.atguigu.spring5.TestDemo;

import com.atguigu.spring5.config.SpringConfig;
import com.atguigu.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestSpring5 {

    @Test
    public void test(){
        //1.加载spring配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        //2.获取配置创建的对象
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

5.AOP介绍

5.1.AOP概述

AOP为 Aspect Oriented Programming 的缩写,意为面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗地描述为:不通过修改源代码方式,而在主干功能里面添加新功能

5.2.AOP底层原理

AOP 底层使用动态代理,一般来说有有以下两种情况的动态代理:
① 有接口,使用 JDK 动态代理,即创建接口实现类代理对象,增强类的方法
② 没有接口,使用 CGLIB 动态代理,即创建子类的代理对象,增强类的方法

有关代理模式的具体知识可以参考 Java 设计模式——代理模式这篇文章。

5.3.JDK 动态代理实现

① JDK动态代理使用到的类为java.lang.Proxy,以及相关的方法为

//返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序
static object newProxyInstance(ClassLoader loader,类<?>[] interfaces,InvocationHandler h)

该方法的三个参数的具体说明如下:

ClassLoader loader类加载器
类<?>[] interfaces增强方法所在的类实现的接口(支持多个接口)
nvocationHandler h实现这个接口 InvocationHandler,创建代理对象,编写增强的部分

② 创建接口,定义方法

package com.atguigu.spring5;

public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

③ 创建接口实现类,实现方法

package com.atguigu.spring5;

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) {
        return id;
    }
}

④ 使用 Proxy 类创建接口代理对象

package com.atguigu.spring5;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class JDKProxy {
    
    public static void main(String[] args) {
        //创建接口实现类代理对象
        Class[] interfaces = {UserDao.class};
        //可以选择使用匿名内部类
//        Proxy.newProxyInstance(JDKProxy.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(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int result = dao.add(1, 2);
        System.out.println("result:"+result);
    }
}

//创建代理对象代码
class UserDaoProxy implements InvocationHandler {
    
    //创建的是谁的代理对象,那就把谁传递过来
    //有参数构造传递
    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.toString(args));
        
        //被增强的方法执行
        Object res = method.invoke(obj,args);
        
        //方法之后
        System.out.println("方法之后执行...."+obj);
        return res;
    }
}

⑤ 测试
在这里插入图片描述

5.4.AOP操作术语

操作术语含义
连接点类里面可以被增强的方法
切入点实际被真正增强的方法
通知(增强)实际增强的逻辑部分
切面是一个动作,把通知应用到切入点过程

其中,通知有多种类型,例如前置通知、后置通知、环绕通知、异常通知、最终通知等。

5.5.具体操作的基础

① AspectJ 不是 Spring 组成部分,它独立于 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,来进行 AOP 操作。
② 基于 AspectJ 实现 AOP 操作的方式有两种:基于 xml 配置文件实现和基于注解方式实现(推荐使用)。
③ 在项目工程里面引入 AOP 相关依赖
在这里插入图片描述
④ 切入点表达式
1)作用:确定对哪个类里面的哪个方法进行增强。
2)语法结构:
execution( 方法修饰符 方法返回值 方法所属类 匹配方法名 ( 方法中的形参表 ) 方法申明抛出的异常 )

1.其中黄色字体的部分不能省略,各个部分都支持通配符 “*” 来匹配全部。
2.比较特殊的为形参表部分,它支持两种通配符:
	" * ":代表一个任意类型的参数;
	“. .”:代表零个或多个任意类型的参数;

3)应用举例:

对 com.atguigu.dao.BookDao 类里面的 add 进行增强execution( * com.atguigu.dao.BookDao.add(…))
对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强execution( * com.atguigu.dao.BookDao.* (…))
对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强execution( * com.atguigu.dao.. (…))

5.6.AOP具体操作——AspectJ 注解

① 创建类,在类里面定义方法

package com.atguigu.spring5.aopanno;

import org.springframework.stereotype.Component;

@Component
public class User {
    
    public void add(){
        //测试异常通知
        //int a=1/0;
        System.out.println("add...");
    }
}

② 创建增强类、编写增强逻辑

package com.atguigu.spring5.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//增强的类
@Component
@Aspect   //生成代理对象
@Order(1) //有多个增强类多同一个方法进行增强时,设置增强类优先级,即在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
public class UserProxy {
	
	//抽取相同的切入点
	@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
		public void pointdemo() {
	}    

    //前置通知,@Before 注解表示作为前置通知
    //@Before(value = "pointdemo()")
    @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void before() {
        System.out.println("before.........");
    }
    //后置通知(返回通知)
    @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning.........");
    }
    //最终通知
    @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void after() {
        System.out.println("after.........");
    }
    //异常通知
    @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing.........");
    }
    
    //环绕通知
    @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws
            Throwable {
        System.out.println("环绕之前.........");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后.........");
    }
}

③ 进行通知的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       		http://www.springframework.org/schema/beans/spring-beans.xsd
       		http://www.springframework.org/schema/context
       		http://www.springframework.org/schema/context/spring-context.xsd
       		http://www.springframework.org/schema/aop
       		http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启注解扫描-->
    <context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan>

    <!-- 开启 Aspect 生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

④ 测试

package com.atguigu.spring5.testaop;

import com.atguigu.spring5.aopanno.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAop {
    
    @Test
    public void testaopanno(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);
        user.add();
    }
}

结果如下:
在这里插入图片描述

5.7.AOP具体操作——AspectJ 配置文件

① 创建两个类,增强类和被增强类,并创建方法

package com.atguigu.spring5.aopxml;

public class Book {
    
    public void buy(){
        System.out.println("buy...");
    }
}
package com.atguigu.spring5.aopxml;

public class BookProxy {
    
    public void before(){
        System.out.println("before...");
    }
}

② 在 spring 配置文件中创建两个类对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--创建对象-->
    <bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
    <bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>

    <aop:config>
        <!--切入点-->
        <aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <!--增强作用在具体的方法上-->
            <aop:before method="before" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>

③ 测试

package com.atguigu.spring5.testaop;

import com.atguigu.spring5.aopxml.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAop {
    
    @Test
    public void testaopanno(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        Book book = context.getBean("book", Book.class);
        book.buy();
    }
}

结果如下:
在这里插入图片描述
注:如果使用完全注解,则不需要创建 xml 配置文件,只需创建配置类进行配置即可

package com.atguigu.spring5.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

6.JdbcTemplate

(1)JdbcTemplate概述

JdbcTemplate即Spring 框架对 JDBC 进行的封装,使用 JdbcTemplate 方便实现对数据库操作

(2)使用JdbcTemplate的前期准备工作

① 引入相关的jar包:
在这里插入图片描述
② 在 spring 配置文件进行相关配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/user_db" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!--配置JdbcTemplate对象,注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--使用set方法注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象 -->
    <!-- 组件扫描 -->
    <context:component-scan base-package="com.atguigu"></context:component-scan>
</beans>

③ 在数据库中创建需要的表,表名为t_user,表结构如下:
在这里插入图片描述
④ 创建实体类User
User.java

package com.atguigu.spring5.entity;

public class User {
    
    private String userId;
    private String username;
    private String ustatus;
    
    public String getUserId() {
        return userId;
    }
    
    public void setUserId(String userId) {
        this.userId = userId;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getUstatus() {
        return ustatus;
    }
    
    public void setUstatus(String ustatus) {
        this.ustatus = ustatus;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", username='" + username + '\'' +
                ", ustatus='" + ustatus + '\'' +
                '}';
    }
}

(3)JdbcTemplate操作数据库——添加功能

① 编写代码
UserDao.java

package com.atguigu.spring5.dao;

import com.atguigu.spring5.entity.User;

public interface UserDao {
    void add(User user);
}

UserDaoImpl.java

package com.atguigu.spring5.dao;

import com.atguigu.spring5.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao{

    //注入JdbcTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public void add(User user) {
        String sql = "insert into t_user values(?,?,?)";
        Object[] args = {user.getUserId(),user.getUsername(),user.getUstatus()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println("update:"+update);
    }
}

UserService.java

package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;
import com.atguigu.spring5.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    //注入dao
    @Autowired
    private UserDao userDao;
    
    //添加方法
    public void addUser(User user){
        userDao.add(user);
    }
}

② 测试

package com.atguigu.spring5.test;

import com.atguigu.spring5.entity.User;
import com.atguigu.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestJdbcTemplate {
    
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        UserService userService = context.getBean("userService",UserService.class);
        User user = new User();
        user.setUserId("1");
        user.setUsername("Tom");
        user.setUstatus("happy");
        userService.addUser(user);
    }
}

结果如下:
在这里插入图片描述

(4)JdbcTemplate操作数据库——修改和删除功能

① 修改功能

@Override
public void update(User user) {
    String sql = "update t_user set username=?,ustatus=? where userid=?";
    Object[] args = {user.getUsername(),user.getUstatus(),user.getUserId()};
    int update = jdbcTemplate.update(sql, args);
    System.out.println("update:"+update);
}

② 删除功能

@Override
public void delete(String id) {
    String sql = "delete from t_user where userid=?";
    int update = jdbcTemplate.update(sql, id);
    System.out.println("update:"+update);
}

(5)JdbcTemplate操作数据库——查询功能

① 查询返回某个值
使用到的方法为:queryForObject (String sql,Class requiredType)

sql查询的sql语句
requiredType返回类型Class
@Override
public int selectCount() {
    String sql = "select count(*) from t_user";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    return count;
}

② 查询返回对象
使用到的方法为:queryForObject(String sql,RowMapper rowMapper,Object… args)

sql查询的sql语句
rowMapperRowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
argssql 语句值
@Override
public User findUserInfo(String id) {
    String sql = "select * from t_user where userid=?";
    //调用方法
    User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
    return user;
}

③ 查询返回集合
使用到的方法为:query (String sql,RowMapper rowMapper,Object… args)

sql查询的sql语句
rowMapperRowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
argssql 语句值
@Override
public List<User> findAllUser() {
    String sql = "select * from t_user";
    List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
    return userList;
}

(6)JdbcTemplate操作数据库——批量添加操作

使用到的方法为:batchUpdate(String sql,List<0bject[> batchArgs)

sqlsql语句
batchArgsList 集合,添加多条记录数据
//批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
	String sql = "insert into t_user values(?,?,?)";
	int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
	System.out.println(Arrays.toString(ints));
}
//批量添加测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","Tom","happy"};
Object[] o2 = {"4","Mike","sad"};
batchArgs.add(o1);
batchArgs.add(o2);
//调用批量添加
bookService.batchAdd(batchArgs);

(7)JdbcTemplate操作数据库——批量修改操作

使用到的方法仍为:batchUpdate(String sql,List<0bject[> batchArgs)

//批量修改
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
	String sql = "update t_user set username=?,ustatus=? where userid=?";
	int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
	System.out.println(Arrays.toString(ints));
}
//批量修改测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"Marry","sad","3"};
Object[] o2 = {"Tom","happy","4"};
batchArgs.add(o1);
batchArgs.add(o2);
//调用方法实现批量修改
bookService.batchUpdate(batchArgs);

(8)JdbcTemplate操作数据库——批量删除操作

使用到的方法仍为:batchUpdate(String sql,List<0bject[> batchArgs)

//批量删除
@Override
public void batchDeleteBook(List<Object[]> batchArgs) {
	String sql = "delete from t_user where userid=?";
	int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
	System.out.println(Arrays.toString(ints));
}
//批量删除
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
batchArgs.add(o1);
batchArgs.add(o2);
//调用方法实现批量删除
bookService.batchDelete(batchArgs);

7.事务

(1)事务概述

事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位

(2)事务的ACID特性

原子性(Atomicity)事务是数据库的逻辑工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(Consistency)事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。即指系统从一个正确的状态到另一个正确的状态。举个例子,银行有A向B转账,A有50元,向B转100元,这时事务会提示余额不足,回滚,这就保证了银行系统的一致性,因为余额不可为负数。
隔离性(Isolation)一个事务的执行不能被其他事务干扰。即一个事务的内部操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
持续性(Durability)持续性也称永久性,指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。

(3)搭建事务操作环境

① 搭建环境
创建需要的表,表名为t_account,表结构为:
在这里插入图片描述
表中数据为:
在这里插入图片描述
UserDao.java

package com.atguigu.spring5.dao;

public interface UserDao {
    
    public void addMoney();
    public void reduceMoney();
}

UserDaoImpl.java

package com.atguigu.spring5.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao{

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    //多钱
    @Override
    public void addMoney() {
        String sql = "update t_account set money=money+? where username=?";
        jdbcTemplate.update(sql,100,"Marry");
    }
    
    //Lucy转100给Marry
    //少钱
    @Override
    public void reduceMoney() {
        String sql = "update t_account set money=money-? where username=?";
        jdbcTemplate.update(sql,100,"Lucy");
    }
}

UserService.java

package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    @Autowired
    private UserDao userDao;
    
    //转账
    public void accountMoney(){
        //Lucy少100
        userDao.reduceMoney();
        
        //人为模拟异常
        //int n=1/0;
        
        //Marry多100
        userDao.addMoney();
    }
    
}

② 测试

package com.atguigu.spring5.test;

import com.atguigu.spring5.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestJdbcTemplate {
    
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        UserService userService = context.getBean("userService",UserService.class);
        userService.accountMoney();
    }
}

结果如下:
在这里插入图片描述

(4)事务场景引入

如果在上述UserService类的accountMoney()方法中出现异常时(可以添加除零运算),那么这次转账就有可能不成功,即可能会出现Lucy账户减少了100,但是Marry账户却没有增加100的情况,这种情况很显然是必须要避免的,所以为了解决类似的问题,需要引入Spring中的事务管理。

(5)Spring事务管理介绍

① Spring 事务一般添加到 JavaEE 三层结构中的 Service 层(业务逻辑层)。
② Spring 进行事务管理操作一般有两种方式:编程式事务管理声明式事务管理(推荐使用),其中声明式事务管理包括两种:基于注解方式(推荐使用)和基于 xml 配置文件方式。
③ 在 Spring 进行声明式事务管理时,底层使用原理的是 AOP 。
④ Spring 事务管理 API:提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
在这里插入图片描述

(6)Spring事务管理——注解声明式事务管理

① 在 spring 配置文件中配置事务管理器(注意还需要引入事务 tx名称空间)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/user_db" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!--配置JdbcTemplate对象,注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--使用set方法注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象 -->
    <!-- 组件扫描 -->
    <context:component-scan base-package="com.atguigu"></context:component-scan>


    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

</beans>

② 在 service 类上面(或者 service 类里面相关方法上面)添加事务注解@Transactional,并开启模拟异常

package com.atguigu.spring5.service;

import com.atguigu.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional   //可以加到类或者方法上
public class UserService {
    
    @Autowired
    private UserDao userDao;
    
    //转账
    public void accountMoney(){
        //Lucy少100
        userDao.reduceMoney();
        
        //模拟异常
        int n=10/0;
        
        //Marry多100
        userDao.addMoney();
    }
}

③ 当再次测试时,控制台有除零异常提示,但数据库中的数据依然正确有效!

(7)Spring事务管理——事务参数(传播行为)

此处主要掌握以下用红色矩形圈出的参数:
在这里插入图片描述
① propagation:事务传播行为
事务传播行为是指多事务方法直接进行调用,这个过程中事务是如何进行管理的,其中事务方法指的是对数据库表数据进行变化的操作(例如添加数据、修改数据、删除数据)
举一个简单的例子,在 add() 方法(在该方法上已添加注解@Transactional)中调用 update() 方法(在该方法上未添加注解@Transactional)时,事务应如何进行管理。
事务的传播行为可以由传播属性指定,Spring框架的事务传播行为有以下7种:
在这里插入图片描述
使用举例:

@Service
@Transactional(propagation = Propagation.REQUIRED) 
public class UserService {
	......
}

② ioslation:事务隔离级别
当事务的隔离性被破环后,会出现以下三个问题:
1)读“脏”数据(也称为脏读):事务T1修改某一数据并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时被T1修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则此时T2读到的数据就为“脏”数据。
2)不可重复读:事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次得到读取结果。
3)丢失修改(也称为幻读、虚读):两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。
通过设置事务的隔离级别,可以解决上述问题:

脏读不可重复读幻读
READ UNCOMMITTED(读未提交)
READ COMMITTED(读已提交)
REPEATABLE READ(可重复读)
SERIALIZABLE(串行化)
使用举例:
//如果使用的是MySQL数据库,那么默认的隔离级别为REPEATABLE READ(可重复读)
@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.SERIALIZABLE) 
public class UserService {
	......
}

③ timeout:超时时间
1)事务需要在一定时间内进行提交,如果不提交进行回滚;
2)默认值是 -1(即不设置超时) ,设置时间以秒单位进行计算;
3)使用举例:

@Service
@Transactional(timeout = 500)
public class UserService {
	......
}

④ readOnly:是否只读
1)读:查询操作,写:添加修改删除操作;
2)readOnly 的默认值 false,表示可以查询,可以进行添加修改删除等操作;
3)当 readOnly 的值设置为 true时,那么只能进行查询操作;
4)使用举例:

@Service
@Transactional(readOnly = true)
public class UserService {
	......
}

⑤ rollbackFor:回滚
1)设置出现哪些异常进行事务回滚,其类型为Class对象数组,且必须继承自Throwable;
2)使用举例:

//设置运行时异常和类找不到时异常回滚
@Service
@Transactional(rollbackFor = {RuntimeException.class,ClassNotFoundException.class})
public class UserService {
	......
}

⑥ noRollbackFor:不回滚
1)设置出现哪些异常不进行事务回滚,其类型为Class对象数组,且必须继承自Throwable;
2)使用举例:

//设置所有异常都不回滚
@Service
@Transactional(noRollbackFor = Exception.class) 
public class UserService {
	......
}

(8)Spring事务管理——XML 声明式事务管理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/user_db" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!--配置JdbcTemplate对象,注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--使用set方法注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象 -->
    <!-- 组件扫描 -->
    <context:component-scan base-package="com.atguigu"></context:component-scan>

    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置通知--> <tx:advice id="txadvice">
    <!--配置事务参数-->
    <tx:attributes>
        <!--指定哪种规则的方法上面添加事务-->
        <tx:method name="accountMoney" propagation="REQUIRED"/>
            <!--<tx:method name="account*"/>-->
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
            <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>
</beans>

(9)Spring事务管理——完全注解声明式事务管理

① 创建配置类

package com.atguigu.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
    
    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/user_db");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    
    //创建 JdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        //到 ioc 容器中根据类型找到 dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入 dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    
    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

② 测试

@Test
public void test3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = context.getBean("userService",UserService.class);
    userService.accountMoney();
}

8.Spring5 框架新功能——整合日志框架

(1)整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中已删除。
(2)Spring 5.0 框架自带了通用的日志封装,Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2。
(3)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="DEBUG">
    <!--先定义所有的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>

③ 测试

@Test
public void test3(){
    ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = context.getBean("userService",UserService.class);
    userService.accountMoney();
}

在这里插入图片描述

9.Spring5 框架新功能——Nullable注解

(1)Spring5 框架核心容器支持@Nullable 注解,@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。
(2)@Nullable注解用在方法上面,方法返回值可以为空
在这里插入图片描述
(3)@Nullable注解使用在方法参数里面,方法参数可以为空
在这里插入图片描述
(4)@Nullable注解使用在属性上面,属性值可以为空
在这里插入图片描述

10.Spring5 框架新功能——函数时注册对象

Spring5 核心容器支持函数式风格 GenericApplicationContext

	//函数式风格创建对象,交给 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.atguigu.spring5.test.User");
	User user = (User)context.getBean("user1");
	System.out.println(user);
}

11.Spring5 框架新功能——支持整合 JUnit5

(1)整合 JUnit4
① 引入相关的测试依赖
在这里插入图片描述
在这里插入图片描述
② 创建测试类,使用注解方式完成

package com.atguigu.spring5.test;

import com.atguigu.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) //单元测试框架
@ContextConfiguration("classpath:bean1.xml") //加载配置文件
public class JTest4 {
    @Autowired
    private UserService userService;
    
    @Test
    public void test1() {
        userService.accountMoney();
    }
}

(2)整合 JUnit5
① 引入 JUnit5 的 jar 包
在这里插入图片描述
② 创建测试类,使用注解完成

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")
public class JTest5 {
	@Autowired
	private UserService userService;
		
	@Test
	public void test1() {
		userService.accountMoney();
	} 
}
//使用一个复合注解替代上面两个注解完成整合
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5 {
	@Autowired
	private UserService userService;
	
	@Test
	public void test1() {
		userService.accountMoney();
	} 
}

12.Spring5 框架新功能——Webflux

(1)Webflux概述

Webflux 是 Spring5 添加新的模块,用于 Web 开发,其功能与 SpringMVC 类似,Webflux 是一种使用当前比较流行的响应式编程的框架。传统 Web 框架(例如 SpringMVC)是基于 Servlet 容器的,而Webflux 是一种异步非阻塞的框架(异步非阻塞的框架在 Servlet3.1 以后才支持),其的核心是基于 Reactor 的相关 API 实现的。
在这里插入图片描述
异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步。
阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞。

(2)Webflux的特点

① 非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以 Reactor 为基础实现响应式编程。
② 函数式编程:Spring5 框架基于 Java8,Webflux 使用 Java8 中函数式编程方式实现路由请求。

(3)Webflux与SpringMVC的比较

在这里插入图片描述
① 两个框架都可以使用注解方式,都可以运行在 Tomcat 等容器中。
② SpringMVC 采用命令式编程(即一行一行地执行代码),Webflux 采用异步响应式编程。

(4)响应式编程(Java 实现)

① 响应式编程概述
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
② 响应式编程概述举例——Java8 及其之前版本

package com.atguigu.demoreactor8.reactor8;

import java.util.Observable;

public class ObserverDemo extends Observable {
    public static void main(String[] args) {
        ObserverDemo observer = new ObserverDemo();
        //添加观察者
        observer.addObserver((o,arg)->{
            System.out.println("发生变化");
        });
        observer.addObserver((o,arg)->{
            System.out.println("手动被观察者通知,准备改变");
        });
        observer.setChanged(); //数据变化
        observer.notifyObservers(); //通知
    }
}

(5)响应式编程(Reactor 实现)

① 在响应式编程操作中,Reactor 是满足 Reactive 的规范框架。
Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富的相关的操作符。Flux 对象实现发布者,返回 N 个元素;Mono 实现发布者,返回 0 或者 1 个元素。
③ Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。
在这里插入图片描述
④ 代码演示 Flux 和 Mono

package com.atguigu.demoreactor8.reactor8;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class TestReactor {
    
    public static void main(String[] args) {
        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);
    }
}

⑤ 三种信号的特点
1)错误信号和完成信号都是终止信号,不能共存。
2)如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流。
3) 如果没有错误信号,没有完成信号,表示是无限数据流。
⑥ 调用 just 或者其他方法时只是声明了数据流,但是数据流并没有发出,只有在进行订阅之后才会触发数据流,如果不订阅,那么什么都不会发生。

//just方法直接声明
Flux.just(1,2,3,4).subscribe(System.out::print);
Mono.just(1).subscribe(System.out::print);

⑦ 操作符
对数据流进行一道道操作,成为操作符,例如工厂流水线。
1)map:将元素映射为新元素
在这里插入图片描述
2)flatMap:将元素映射为流
把每个元素转换流,把转换之后的多个流合并成一个大的流。
在这里插入图片描述

(6)Spring Webflux 的执行流程和核心 API

① Spring Webflux 是基于 Reactor 的,其默认使用容器是 Netty,Netty 是高性能的 NIO 框架(异步非阻塞的框架)。
BIO
在这里插入图片描述
NIO
在这里插入图片描述
② Webflux 的执行过程和 SpringMVC 十分相似
Webflux 中的核心控制器是 DispatchHandler,它实现的接口为 WebHandler,该接口中的有一个名为handle的方法。

public interface WebHandler {
    Mono<Void> handle(ServerWebExchange var1);
}
public Mono<Void> handle(ServerWebExchange exchange) {  //存放http的请求信息
        if (this.handlerMappings == null) {
            return this.createNotFoundError();
        } else {
            return CorsUtils.isPreFlightRequest(exchange.getRequest()) ? this.handlePreFlight(exchange) : 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);  //返回处理结果
            });
        }
    }

③ Webflux 里面 DispatcherHandler,负责请求的处理:
1)HandlerMapping:请求查询到处理的方法;
2)HandlerAdapter:真正负责请求处理;
3)HandlerResultHandler:响应结果处理;

④ Webflux 实现函数式编程的两个接口:RouterFunction(路由处理)和 HandlerFunction(处理函数)

(7)Spring Webflux——基于注解编程模型

Spring Webflux 的实现方式有两种:注解编程模型和函数式编程模型,注解编程模型的使用方式和之前 SpringMVC 的使用方式相似,只需要把相关依赖配置到项目中,SpringBoot自动配置相关运行容器,默认情况下使用Netty服务器。
① 创建 SpringBoot 工程,并引入 Webflux 依赖
在这里插入图片描述

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

配置启动端口号
在这里插入图片描述
创建相关的包和相关的类
User.java

package com.atguigu.webfluxdemo1.entity;

public class User {
    
    private String name;
    private String gender;
    private Integer age;

	public User() {
    }    

    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;
    }
}

UserService.java

package com.atguigu.webfluxdemo1.service;

import com.atguigu.webfluxdemo1.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);
}

UserServiceImpl.java

package com.atguigu.webfluxdemo1.service;

import com.atguigu.webfluxdemo1.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

@Service
public class UserServiceImpl implements UserService{
    //创建 map 集合存储数据
    private final Map<Integer,User> users = new HashMap<>();
    public UserServiceImpl() {
        this.users.put(1,new User("lucy","nan",20));
        this.users.put(2,new User("mary","nv",30));
        this.users.put(3,new User("jack","nv",50));
    }
    
    //根据 id 查询
    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }
    
    //查询多个用户
    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }
    
    //添加用户
    @Override
    public Mono<Void> saveUserInfo(Mono<User> userMono) {
        return userMono.doOnNext(person -> {
            //向 map 集合里面放值
            int id = users.size()+1;
            users.put(id,person);
        }).thenEmpty(Mono.empty());
    }
}

UserController.java

package com.atguigu.webfluxdemo1.controller;

import com.atguigu.webfluxdemo1.entity.User;
import com.atguigu.webfluxdemo1.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
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> geetUserId(@PathVariable int id) {
        return userService.getUserById(id);
    }
    
    //查询所有
    @GetMapping("/user")
    public Flux<User> getUsers() {
        return userService.getAllUser();
    }
    
    //添加
    @PostMapping("/saveuser")
    public Mono<Void> saveUser(@RequestBody User user) {
        Mono<User> userMono = Mono.just(user);
        return userService.saveUserInfo(userMono);
    }
}

测试
启动项目:
在这里插入图片描述
进行测试
在这里插入图片描述
说明
SpringMVC 方式实现:同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
Spring Webflux 方式实现:异步非阻塞方式,基于 Spring Webflux+Reactor+Netty

(8)Spring Webflux——基于函数式编程模型

① 在使用函数式编程模型操作的时候,需要自己初始化服务器。
② 基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。
③ Spring Webflux 请求和响应不再是 ServletRequest 和 ServletResponse ,而是ServerRequest 和ServerResponse。
1)把注解编程模型工程复制一份 ,保留 entity 和 service 内容
2) 创建 Handler(具体实现方法)

package com.atguigu.webfluxdemo1.handler;

import com.atguigu.webfluxdemo1.entity.User;
import com.atguigu.webfluxdemo1.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;

import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class UserHandler {
    private final 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(fromObject(person))).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));
    }
}

3)初始化服务器,编写 Router,并创建服务器完成适配

package com.atguigu.webfluxdemo1;

import com.atguigu.webfluxdemo1.handler.UserHandler;
import com.atguigu.webfluxdemo1.service.UserService;
import com.atguigu.webfluxdemo1.service.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.RouterFunctionDsl;
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 {
    
    public static void main(String[] args) throws Exception{
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }
    
    //1.创建Router路由
    public RouterFunction<ServerResponse> routingFunction() {
        //创建hanler对象
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);
        //设置路由
        return RouterFunctions.route(
                GET("/users/{id}").and(accept(APPLICATION_JSON)),handler::getUserById)
                .andRoute(GET("/users").and(accept(APPLICATION_JSON)),handler::getAllUsers);
    }
    
    //2.创建服务器完成适配
    public void createReactorServer() {
        //路由和handler适配
        RouterFunction<ServerResponse> route = routingFunction();
        HttpHandler httpHandler = toHttpHandler(route);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        //创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
}

4)测试
执行main方法
在这里插入图片描述
查找端口号
在这里插入图片描述
在浏览器中输入访问路径
在这里插入图片描述
④ 使用 WebClient 调用

package com.atguigu.webfluxdemo1;

import com.atguigu.webfluxdemo1.entity.User;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

public class Client {
    public static void main(String[] args) {
        //调用服务器地址(需要运行Server中的main方法获取端口号)
        WebClient webClient = WebClient.create("http://127.0.0.1:56517");
        //根据 id 查询
        String id = "1";
        User userresult = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
        System.out.println(userresult.getName());
        //查询所有
        Flux<User> results = webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
        results.map(stu -> stu.getName()).buffer().doOnNext(System.out::println).blockFirst();
    } 
}

运行结果如下:
在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码星辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值