Spring5框架

Spring5框架

文章目录


下载地址

概述

Spring 是轻量级的开源的 JavaEE 框架

Spring 可以解决企业应用开发的复杂性

Spring 有两个核心部分:IOC 和Aop

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

Spring 特点

  • 方便解耦,简化开发
  • Aop 编程支持
  • 方便程序测试
  • 方便和其他框架进行整合
  • 方便进行事务操作
  • 降低 API 开发难度

小案例

src下new一个springconfig配置文件:

<?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.eker.spring5.User"></bean>
</beans>

java类:

package com.eker.spring5;

/**
 * ClassName: User
 * Description:
 * date: 2022/3/14 15:54
 *
 * @author Ekertree
 * @since JDK 1.8
 */
public class User {

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

test类:

package com.eker.spring5.testdemo;

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

/**
 * ClassName: TestSpring5
 * Description:
 * date: 2022/3/14 16:01
 *
 * @author Ekertree
 * @since JDK 1.8
 */
public class TestSpring5 {

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

IOC

什么是IOC

  • 控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
  • 使用 IOC 目的:为了耦合度降低

底层原理

xml 解析、工厂模式、反射

service层中需要调用dao层,一旦dao层进行了改变,service层也要进行改变,十分麻烦,使用工厂模式,耦合度还不是最低的。

在这里插入图片描述


在这里插入图片描述

IOC(beanfactory接口)

  • IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂

  • Spring 提供 IOC 容器实现两种方式:(两个接口)

    • BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用

      加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象

    • ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用

      加载配置文件时候就会把在配置文件对象进行创建

  • ApplicationContext 接口有实现类

在这里插入图片描述

IOC操作Bean管理

什么是Bean管理

Bean 管理指的是两个操作

  • Spring 创建对象
  • Spirng 注入属性
Bean管理操作两种方式
基于 xml 配置文件方式实现

基于 xml 方式创建对象:

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

基于 xml 方式注入属性:

DI:依赖注入,就是注入属性,是IOC的一种具体实现,需要在创建对象的基础上实现的。

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

​ 在 spring 配置文件配置对象创建,配置属性注入

<bean id="book" class="com.eker.spring5.Book"> 
    <!--使用 property 完成属性注入 
        name:类里面属性名称 
        value:向属性注入的值 
    --> 
    <property name="bname" value="java"></property> 
    <property name="bauthor" value="author"></property> 
</bean> 

使用有参数构造进行注入

​ 创建类,定义属性,创建属性对应有参数构造方法

public class Orders { 
    //属性 
    private String oname; 
    private String address; 
    //有参数构造 
    public Orders(String oname,String address) { 
        this.oname = oname; 
        this.address = address; 
    } 
}

​ 在 spring 配置文件中进行配置

<bean id="orders" class="com.eker.spring5.Orders"> 
    <constructor-arg name="oname" value="电脑"></constructor-arg> 
    <constructor-arg name="address" value="China"></constructor-arg> 
</bean> 

p名称空间注入(了解)

​ 使用 p 名称空间注入,可以简化基于 xml 配置方式

​ 第一步 添加 p 名称空间在配置文件中

在这里插入图片描述

​ 第二步 进行属性注入,在 bean 标签里面进行操作

<bean id="user" class="com.eker.spring5.User" p:name="eker" p:sex="boy"></bean>

注入其他类型属性

(1)字面量

​ null值:

<bean id="user" class="com.eker.spring5.User">
        <property name="name" value="eker"></property>
        <property name="sex" value="boy"></property>
        <property name="address">
            <null/>
        </property>
    </bean>

​ 属性值包含特殊符号:

  • 把<>进行转义

     &lt; &gt;
    
  • 把带特殊符号内容写到CDATA

<property name="address"> 
    <value><![CDATA[<<南京>>]]></value> 
</property> 

(2)注入属性-外部bean

  1. 创建两个类 service 类和 dao 类
public class UserDaoImpl implements UserDao{
    @Override
    public void update() {
        System.out.println("dao update!");
    }
}

package com.eker.spring5.service;

import com.eker.spring5.dao.UserDao;

/**
 * ClassName: UserService
 * Description:
 * date: 2022/3/15 14:18
 *
 * @author Ekertree
 * @since JDK 1.8
 */
public class UserService {

    private UserDao userDao;

    public void add() {
        System.out.println("service add!");
        userDao.update();
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

​ 2.在 service 调用 dao 里面的方法

​ 3.在 spring 配置文件中进行配置

    <bean id="userDaoImpl" class="com.eker.spring5.dao.UserDaoImpl"></bean>

    <bean id="userService" class="com.eker.spring5.service.UserService">
        <property name="userDao" ref="userDaoImpl"></property>
    </bean>

(3)注入属性**-**内部 bean

​ 一对多关系:部门和员工,一个部门有多个员工,一个员工属于一个部门,部门是一,员工是多,在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示。

//部门类 
public class Dept { 
    private String dname; 
    public void setDname(String dname) { 
        this.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; 


    } 
} 

在 spring 配置文件中进行配置

<bean id="emp" class="com.eker.spring5.bean.Emp">
        <property name="ename" value="eker"></property>
        <property name="gender" value="boy"></property>
        <property name="dept">
            <bean id="dept" class="com.eker.spring5.bean.Dept">
                <property name="dname" value="security"></property>
            </bean>
        </property>
    </bean>

(4)注入属性-级联赋值

第一种写法

<bean id="emp" class="com.eker.spring5.bean.Emp">
        <property name="ename" value="eker"></property>
        <property name="gender" value="boy"></property>
        <property name="dept" ref="dept"></property>
    </bean>
    <bean id="dept" class="com.eker.spring5.bean.Dept">
        <property name="dname" value="security"></property>
    </bean>

第二种写法

该写法要设置其对应属性的get方法,通过反射设置值

    <bean id="emp" class="com.eker.spring5.bean.Emp">
        <property name="ename" value="eker"></property>
        <property name="gender" value="boy"></property>
        <property name="dept" ref="dept"></property>
        <property name="dept.dname" value="财务部"></property>
    </bean>
    <bean id="dept" class="com.eker.spring5.bean.Dept">
        <property name="dname" value="security"></property>
    </bean>

注入集合属性

1、注入数组类型属性

2、注入 List 集合类型属性

3、注入 Map 集合类型属性

首先创建类,定义数组、list、map、set 类型属性,生成对应 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; 
 
    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; 
    } 
} 

然后在 spring 配置文件进行配置

<!--1 集合类型属性注入--> 
<bean id="stu" class="com.eker.spring5.bean.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> 
</bean> 

在集合里面设置对象类型值

<!--创建多个 course 对象--> 
<bean id="course1" class="com.eker.spring5.bean.Course">
	<property name="cname" value="spring"></property>
</bean>
<bean id="course2" class="com.eker.spring5.bean.Course">
	<property name="cname" value="mybatis"></property>
</bean>
<!--注入 list 集合类型,值是对象-->
<property name="courseList">
	<list>
		<ref bean="course1"></ref>
	</list>
</property>

把集合注入部分提取出来

在 spring 配置文件中引入名称空间 util

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

使用 util 标签完成 list 集合注入提取

<util:list>
        <value>java</value>
        <value>c++</value>
        <value>python</value>
</util:list>
<!--2 提取 list 集合类型属性注入使用--> 
<bean id="book" class="com.eker.spring5.bean.Book"> 
    <property name="list" ref="bookList"></property> 
</bean> 

FactoryBean

Spring 有两种类型 bean,一种普通 bean,另外一种工厂 beanFactoryBean

普通 bean:在配置文件中定义 bean 类型就是返回类型

工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样

第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean

第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型

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作用域(单实例多实例)

Spring 里面,设置创建 bean 实例是单实例(获取到的都是同一个)还是多实例(每次获取到的book不一样)

public void test2(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
    Book book1 = context.getBean("book", Book.class);
    Book book2 = context.getBean("book", Book.class);
    System.out.println(book1);
    System.out.println(book2);
}

单实例:

在这里插入图片描述

多实例:

在这里插入图片描述

如何设置单实例还是多实例?

  • 在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例

  • scope :

    ​ 第一个值 默认值,singleton,表示是单实例对象

    ​ 第二个值 prototype,表示是多实例对象

    <bean id="book" class="com.eker.spring5.bean.Book" scope="prototype">
        <property name="list" ref="booklist"></property>
    </bean>

singleton 和 prototype 区别

第一, singleton 单实例,prototype 多实例

第二 ,设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象;设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,在调用getBean 方法时候创建多实例对象。

bean生命周期

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

  1. 通过构造器创建 bean 实例(无参数构造)
  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
  3. 调用 bean 的初始化的方法(需要进行配置初始化的方法)
  4. bean 可以使用了(对象获取到了)
  5. 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

演示:

public class Order {

    private String oname;

    public Order(){
        System.out.println("第一步 执行无参构造创建bean实例");
    }

    public void setOname(String oname) {
        System.out.println("第二部 set方法设置值");
        this.oname = oname;
    }
    //初始化方法
    public void initMethod() {
        System.out.println("第三步 执行初始化方法");
    }

    //销毁方法
    public void destroyMethod(){
        System.out.println("第五步 执行销毁方法");
    }

}
<bean id="order" class="com.eker.spring5.bean.Order" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="xiaomiphone"></property>
    </bean>
    @Test
    public void test3(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
        Order order = context.getBean("order", Order.class);
        System.out.println("第四步 获取创建bean实例对象");
        System.out.println(order);
        context.close();
    }

在这里插入图片描述


bean的后置处理:

加上后置处理的化,生命周期应该是有七步。

  1. 通过构造器创建 bean 实例(无参数构造)
  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
  3. 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
  4. 调用 bean 的初始化的方法(需要进行配置初始化的方法)
  5. 把 bean实例传递 bean后置处理器的方法 postProcessAfterInitialization
  6. bean 可以使用了(对象获取到了)
  7. 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

演示添加后置处理器效果:

1.创建类,实现接口 BeanPostProcessor,创建后置处理器

package com.eker.spring5.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

/**
 * ClassName: MybeanPost
 * Description:
 * date: 2022/3/15 20:02
 *
 * @author Ekertree
 * @since JDK 1.8
 */
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;
    }
}

2.配置后置处理器

    <!--配置后置处理器-->
    <bean id="myBeanPost" class="com.eker.spring5.bean.MyBeanPost"></bean>

在这里插入图片描述


xml自动装配

什么是自动装配?

根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入。

演示自动装配

<!--实现自动装配 
    bean 标签属性autowire,配置自动装配 
    autowire 属性常用两个值: 
        byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样 
        byType 根据属性类型注入 
--> 
    <bean id="emp" class="com.eker.spring5.bean.Emp" autowire="byName"></bean>
    <bean id="dept" class="com.eker.spring5.bean.Dept" autowire="byName"></bean>

外部属性文件

1.直接配置数据库信息

<!--直接配置连接池--> 
<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="root"></property> 
</bean> 

2.引入外部属性文件配置数据库连接池

创建外部属性文件,properties 格式文件,写数据库信息

prop.driverClassName=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/book
prop.username=root
prop.password=root

把外部 properties 属性文件引入到 spring 配置文件中:

引入 context 名称空间

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="                    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.driverClassName}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
基于注解方式
什么是注解
  1. 注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)
  2. 使用注解,注解作用在类上面,方法上面,属性上面
  3. 使用注解目的:简化 xml 配置
针对Bean管理中创建对象提供注解
  • @Component
  • @Service
  • @Controller
  • @Repository

上面四个注解功能是一样的,都可以用来创建bean 实例

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

第一步 引入依赖

spring-aop-5.2.6.RELEASE.jar

第二步 开启组件扫描

<!--开启组件扫描 
    1 如果扫描多个包,多个包使用逗号隔开 
    2 扫描包上层目录 
--> 
<context:component-scan base-package="com.eker.spring5"></context:component-scan> 

第三步 创建类,在类上面添加创建对象注解

//在注解里面 value 属性值可以省略不写, 
//默认值是类名称,首字母小写 
//UserService -- userService 
@Component()
public class UserService {

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

开启组件扫描细节配置:

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

属性注入

@Autowired:根据属性类型进行自动装配

第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解

第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解

@Service 
public class UserService { 
    //定义 dao 类型属性 
    //不需要添加 set 方法 
    //添加注入属性注解 
    @Autowired 
    private UserDao userDao; 
 
    public void add() { 
        System.out.println("service add.	"); 
        userDao.add(); 
    } 
} 

@Qualifier:根据名称进行注入

@Qualifier 注解的使用,和上面@Autowired 一起使用

@Service
public class UserService {

    @Autowired
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;

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

@Resource:可以根据类型注入,可以根据名称注入

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

注意:@Resource是javax.annotation.Resource的,不是spring的。

@Value:注入普通类型属性

@Value(value = "abc") 
private String name; 
完全注解开发

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

@Configuration
@ComponentScan(basePackages = "com.eker.spring5")
public class SpringConfig {

}

测试类

    @Test
    public void test2(){
        //加载配置类
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }

AOP

什么是AOP

  1. 面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  2. 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
  3. 使用登录例子说明 AOP

在这里插入图片描述

底层原理

在这里插入图片描述

JDK动态代理

1.使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象

在这里插入图片描述

调用 newProxyInstance 方法

public static Object newProxyInstance(ClassLoader loader,<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException

方法有三个参数:

loader - 类加载器来定义代理类

interfaces - 代理类实现的接口列表

h - 调度方法调用的调用处理函数

结果:具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例


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

public class UserDaoImpl implements UserDao{

    @Override
    public int add(int a, int b) {
        return a+b;
    }

    @Override
    public String update(String id) {
        return id;
    }
}

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

public class JDKProxy {

    public static void main(String[] args) {
        //创建接口实现类的代理对象
        Class[] interfaces = {UserDao.class};
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces,new MyHandler(userDao));
        int rs = dao.add(1, 2);
        System.out.println(rs);
    }
}
//重写处理类
class MyHandler implements InvocationHandler {
    //被代理的类
    private Object obj;

    //创建该处理类的时候会把被代理类赋给obj
    public MyHandler(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 result = method.invoke(obj, args);
        //调用完被代理类的方法后再进行一些其他的操作
        System.out.println("方法之后执行	"+obj);
        return result;
    }
}

在这里插入图片描述

我的理解:创建了一个增强类(处理类handler),代理类会调用增强类(处理类handler)让处理类绑定被代理类,重写增强类(处理类)中的invoke方法,invoke方法可以通过method.invoke(obj,args)方法调用被代理类的方法,并且能再调用前后进行其他的操作,这样也就实现了在不改变源代码的情况下增加其他的功能。

术语

在这里插入图片描述

使用AspectJ与spring结合实现AOP

1.AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作

2.基于 AspectJ 实现 AOP 操作:

  • ​ 基于 xml 配置文件实现
  • ​ 基于注解方式实现(使用)

3.在项目工程里面引入 AOP 相关依赖

在这里插入图片描述

4.切入点表达式

  • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
  • 语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]) )

public默认不写,*为所有返回类型

举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强

execution(* com.atguigu.dao.BookDao.add(…))

举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强

execution(* com.atguigu.dao.BookDao.* (…))

举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强

execution(* com.atguigu.dao.. (…))


AOP操作(AspectJ注解)

1.创建类,在类里面定义方法

public class User {

    public void say(){
        System.out.println("say!");
    }
}

2.创建增强类

public class MyHandler {
    //前置通知
    @Before(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void before(){
        System.out.println("before");
    }
    //后置(返回)通知
    @AfterReturning(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    //最终通知
    @After(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void after(){
        System.out.println("after");
    }
    //异常通知
    @AfterThrowing(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
    //环绕通知
    @Around(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("around before");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("around after");
    }

}

3.在 spring 配置文件中,开启注解扫描

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

4.使用注解创建 User 和 增强类

5.在处理类上面添加注解 @Aspect

@Component
public class User {

    public void say(){
        System.out.println("say!");
    }
@Component
@Aspect
public class MyHandler {
    //前置通知
    @Before(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void before(){
        System.out.println("before");
    }
    //后置(返回)通知
    @AfterReturning(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    //最终通知
    @After(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void after(){
        System.out.println("after");
    }
    //异常通知
    @AfterThrowing(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
    //环绕通知
    @Around(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("around before");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("around after");
    }

}

6.在 spring 配置文件中开启生成代理对象

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

7.相同的切入点抽取

@Component
@Aspect
public class UserProxy {

    //相同切入点抽取
    @Pointcut(value = "execution(* com.eker.spring5.aopanno.User.say(..))")
    public void point(){
    }
    //前置通知
    @Before(value = "point()")
    public void before(){
        System.out.println("before");
    }
    //后置(返回)通知
    @AfterReturning(value = "point()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }
    //最终通知
    @After(value = "point()")
    public void after(){
        System.out.println("after");
    }
    //异常通知
    @AfterThrowing(value = "point()")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }
    //环绕通知
    @Around(value = "point()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("around before");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("around after");
    }

}

8.有多个增强类多同一个方法进行增强,设置增强类优先级

在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高

完全使用注解开发

创建配置类,不需要创建 xml 配置文件

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

@EnableAspectJAutoProxy注解等同于在xml中配置aspectj-autoproxy,表示开启spring对注解AOP的支持,proxyTargClass=true表示强制使用cglib代理方式。

使用配置文件

1.创建两个类,增强类和被增强类,创建方法

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

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

3.在 spring 配置文件中配置切入点

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

JdbcTemplate

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

在这里插入图片描述

1.在 spring 配置文件配置数据库连接池:

<!-- 数据库连接池 --> 
<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="root" /> 
    <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
</bean> 

2.配置 JdbcTemplate 对象,注入 DataSource

<!-- JdbcTemplate 对象 --> 
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 
    <!--注入 dataSource--> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 

记得在xml开启组件扫描

3.创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象

@Repository
public class BookDaoImpl implements BookDao{
    //注入JdbcTempla
    @Autowired
    private JdbcTemplate jdbcTemplate;
}
@Service
public class BookService {
    //注入bookdao
    @Autowired
    private BookDao bookDao;
}

修改

    @Override
    public void updateBook(Book book) {
        String sql = "update t_book set username=?,ustatus=? where user_id=?";
        Object[] args = {book.getBookId(), book.getBookName(),book.getBookStatus()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);
    }

删除

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

查询返回某个值

//查询表记录数 
@Override 
public int selectCount() { 
    String sql = "select count(*) from t_book"; 
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class); 
    return count; 
}

查询返回对象

//查询返回对象 
@Override 
public Book findBookInfo(String id) { 
    String sql = "select * from t_book where user_id=?"; 
    //调用方法 
//第一个参数:sql 语句 
//第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装 
//第三个参数:sql 语句值
    Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); 
    return book; 
} 

查询返回集合

//查询返回集合 
@Override 
public List<Book> findAllBook() { 
    String sql = "select * from t_book"; 
    //调用方法 
    //第一个参数:sql 语句 
	//第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装 
	//第三个参数:sql 语句值
    List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); 
    return bookList; 
} 

批量操作

批量添加

batchUpdate(String sql,List<Object[> batchArgs)

l 有两个参数

l 第一个参数:sql 语句

l 第二个参数:List 集合,添加多条记录数据

@Override
public void batchAddBook(List<Object[]> batchArgs) {
    String sql = "insert into t_book values(?,?,?)";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}
批量修改
    @Override
    public void batchUpdateBook(List<Object[]> batchArgs) {
        String sql = "update t_book set bookName=?,bookStatus=? where bookId=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        System.out.println(Arrays.toString(ints));
    }
批量删除
    @Override
    public void batchDeleteBook(List<Object[]> batchArgs) {
        String sql = "delete from t_book where bookId=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        System.out.println(Arrays.toString(ints));
    }

事务操作

事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操 作都失败

事务四个特性

  1. 原子性:事务操作不可再分割
  2. 一致性:事务提交前后的数据完整性和状态保持一致
  3. 隔离性:多事务操作,之间不产生影响
  4. 持久性:提交后,表的数据真正发生变化

事务操作过程

  1. 开启事务
  2. 进行事务操作
  3. 无异常,提交事务
  4. 出现异常,事务回滚

事务管理介绍

1.事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)

2.在 Spring 进行事务管理操作

有两种方式:编程式事务管理(写代码)和声明式事务管理(使用)

3,声明式事务管理

  • 基于注解方式(使用)
  • 基于 xml 配置文件方式

4.在 Spring 进行声明式事务管理,底层使用 AOP 原理

5.Spring 事务管理 API

提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

在这里插入图片描述

注解声明式事务管理

1.在 spring 配置文件配置事务管理器

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

2.在 spring 配置文件,开启事务注解

在 spring 配置文件引入名称空间 tx

<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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

开启事务注解

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

3.在 service 类上面(或者 service 类里面方法上面)添加事务注解

  • @Transactional,这个注解添加到类上面,也可以添加方法上面
  • 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
  • 如果把这个注解添加方法上面,为这个方法添加事务
参数配置

service 类上面添加注解**@Transactional**,在这个注解里面可以配置事务相关参数

在这里插入图片描述

propagation:事务传播行为

多事务方法之间进行调用,这个过程中事务 是如何进行管理的

在这里插入图片描述

在这里插入图片描述

@Transactional(propagation = Propagation.REQUIRED)
ioslation:事务隔离级别
  • 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
  • 有三个读问题:脏读、不可重复读、虚(幻)读
  • 脏读:一个未提交事务读取到另一个未提交事务的数据

在这里插入图片描述

  • 虚读:一个未提交事务读取到另一提交事务添加数据

    解决:通过设置事务隔离级别,解决读问题

在这里插入图片描述

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)

其他参数:

timeout:超时时间

  1. 事务需要在一定时间内进行提交,如果不提交进行回滚
  2. 默认值是 -1 ,设置时间以秒单位进行计算

readOnly:是否只读

  1. 读:查询操作,写:添加修改删除操作
  2. readOnly 默认值 false,表示可以查询,可以添加修改删除操作
  3. 设置 readOnly 值是 true,设置成 true 之后,只能查询

rollbackFor:回滚

设置出现哪些异常进行事务回滚

noRollbackFor:不回滚

设置出现哪些异常不进行事务回滚

XML声明式事务管理
<!--  配置通知  -->
    <tx:advice id="txadvice">
        <tx:attributes>
            <!--指定哪种规则的方法上面添加事务-->
            <tx:method name="addBook" propagation="REQUIRED"/>
            <tx:method name="add*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.eker.spring5.service.BookService.*(..))"/>
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>
完全注解声明式事务管理

创建配置类,使用配置类替代 xml 配置文件

@Configuration //配置类 
@ComponentScan(basePackages = "com.eker.spring5") //组件扫描@EnableTransactionManagement //开启事务 
public class TxConfig { 
    //创建数据库连接池 
    @Bean 
    public DruidDataSource getDruidDataSource() { 
        DruidDataSource dataSource = new DruidDataSource(); 
        dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 
        dataSource.setUrl("jdbc:mysql:///user_db"); 
        dataSource.setUsername("root"); 
        dataSource.setPassword("root"); 
        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; 
    } 
}

Spring5框架新功能

整合日志框架

整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除

Spring 5.0 框架自带了通用的日志封装

  • Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2
  • Spring5 框架整合 Log4j2

Log4j存在RCE漏洞,建议使用最新版

1.引入jar 包

在这里插入图片描述

2.创建 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>

支持**@Nullable** 注解

@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空

1.注解使用在方法参数里面,方法参数可以为空

@Nullable
String getId();

2.注解使用在方法参数里面,方法参数可以为空

public <T> void xxx (@Nullable String xxx)

3.注解使用在属性上面,属性值可以为空

@Nullable
private String name;

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.eker.spring5.test.User"); 
    User user = (User)context.getBean("user1"); 
    System.out.println(user); 
} 

Spring5支持整合JUnit

1.引入jar包

spring-test-5.2.6.RELEASE.jar
JUnit4
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架@ContextConfiguration("classpath:bean1.xml") //加载配置文件 
public class JTest4 { 
    @Autowired 
    private UserService userService; 
    @Test 
    public void test1() { 
        userService.accountMoney(); 
    } 
} 
JUnit5
@ExtendWith(SpringExtension.class) 
@ContextConfiguration("classpath:bean.xml")
public class JTest5 { 
    @Autowired 
    private UserService userService; 
    @Test 
    public void test1() { 
        userService.accountMoney(); 
    } 
} 

使用一个复合注解替代上面两个注解完成整合

3@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5 { 
    @Autowired 
    private UserService userService; 
    @Test 
    public void test1() { 
        userService.accountMoney(); 
    } 
} 

Webflux

是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流行的响应式编程出现的框架。

使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。

什么是异步非阻塞?

  • 异步和同步
  • 非阻塞和阻塞

上面都是针对对象不一样

异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步

阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞

Webflux 特点

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

与比较 SpringMVC

在这里插入图片描述

第一 两个框架都可以使用注解方式,都运行在 Tomcat 等容器中

第二 SpringMVC 采用命令式编程,Webflux 采用异步响应式编程

响应式编程

什么是响应式编程

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

Java8 及其之前版本提供的观察者模式两个类 Observer 和 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(); //通知 
    } 
} 

响应式编程(Reactor 实现)

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

1、引入依赖

        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>

2、编写代码

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

三种信号特点

  1. 错误信号和完成信号都是终止信号,不能共存的
  2. 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流
  3. 如果没有错误信号,没有完成信号,表示是无限数据流

调用 just 或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会触发数据流,不订阅什么都不会发生的

操作符

  1. map 元素映射为新元素

对数据流进行一道道操作,成为操作符,比如工厂流水线。

1 map操作符

2.flatMap 元素映射为流

把每个元素转换流,把转换之后多个流合并大的流

2 flatMap操作符

SpringWebflux 执行流程和核心 API

SpringWebflux 基于 Reactor,默认使用容器是 Netty,Netty 是高性能的 NIO 框架,异步非阻塞的框架

Netty

BIO

5 BIO

NIO

6 NIO

SpringWebflux 执行过程和 SpringMVC 相似的

SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler

接口 WebHandler 有一个方法

image-20220515104911748

SpringWebflux 里面 DispatcherHandler,负责请求的处理

  • HandlerMapping:请求查询到处理的方法
  • HandlerAdapter:真正负责请求处理
  • HandlerResultHandler:响应结果处理

SpringWebflux 实现函数式编程,两个接口:

  1. RouterFunction(路由处理)
  2. HandlerFunction(处理函数)

SpringWebflux(基于注解编程模型)

SpringWebflux 实现方式有两种:注解编程模型和函数式编程模型
使用注解编程模型方式,和之前 SpringMVC 使用相似的,只需要把相关依赖配置到项目中,
SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器。

第一步 创建 SpringBoot 工程,引入 Webflux 依赖

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

第二步,创建包和相关类

实体类:

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

创建接口定义操作的方法 :

public interface UserService {

    //根据 id 查询用户
    Mono<User> getUserById(int id);
    //查询所有用户
    Flux<User> getAllUser();
    //添加用户
    Mono<Void> saveUserInfo(Mono<User> user);
}

接口实现类:

@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());
    }
}

说明:

SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat

SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty

SpringWebflux(基于函数式编程模型)

  1. 在使用函数式编程模型操作时候,需要自己初始化服务器
  2. 基于函数式编程模型时候,有两个核心接口:RouterFunction(实现路由功能,请求转发 给对应的 handler)和 HandlerFunction(处理请求生成响应的函数)。核心任务定义两个函数式接口的实现并且启动需要的服务器。

创建 Handler(具体实现方法):

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);
        return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(person))).switchIfEmpty(notFound);
    }

    //查询所有
    public Mono<ServerResponse> getAllUsers() {
        //调用 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

public class Server {

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

}

创建服务器完成适配

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

最终调用

public static void main(String[] args) throws Exception{
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }

运行main方法,控制台会给出随机的端口号

image-20220515135013325

使用 WebClient 调用
public class Client {

    public static void main(String[] args) {
        //调用服务器地址
        WebClient webClient = WebClient.create("http://127.0.0.1:13308");
        //根据 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();
    }
}
    return ServerResponse.ok().build(this.userService.saveUserInfo(userMono));
}

}


初始化服务器,编写 Router

```java
public class Server {

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

}

创建服务器完成适配

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

最终调用

public static void main(String[] args) throws Exception{
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }

运行main方法,控制台会给出随机的端口号

[外链图片转存中…(img-FaGiHbQv-1652594439490)]

使用 WebClient 调用
public class Client {

    public static void main(String[] args) {
        //调用服务器地址
        WebClient webClient = WebClient.create("http://127.0.0.1:13308");
        //根据 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();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值