Spring 学习记录

Spring

初始化项目

  • 创建空项目

  • 创建对应文件夹

  • 导入jar包到libs

  • 项目结构-模块-选中模块-依赖-添加libs下的jar包

  • 项目结构-项目-设置SDK、Java版本以及输出目录

  • 配置文件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">
    
        <bean id="user" class="com.learn.bean.User">
            <!--DI 属性注入 需要有set方法-->
        	<property name="name" value="zhangsan"/>
        </bean>
    </beans>
    
  • User

    public class User {
        private String name;
        public void add(){
            System.out.println("add...");
        }
        public User() {
        }
        public User(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
  • 测试

public class SpringTests {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean(User.class);
        System.out.println(user.getName());// zhangsan
        user.add();// add...
    }
}

Bean管理

XML方式

属性注入

<bean id="user" class="com.learn.bean.User">
    <!--DI 属性注入 需要有set方法-->
	<property name="name" value="zhangsan"/>
</bean>

有参构造创建对象

<bean id="user" class="com.learn.bean.User" >
    <constructor-arg name="name" value="zhangsan"/>
    <constructor-arg name="age" value="16"/>
</bean>

命名空间

配置文件给beans添加属性

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

需要set方法

<bean id="user" class="com.learn.bean.User" p:name="zhangsan" p:age="18"/>

设置空值

<bean id="user" class="com.learn.bean.User">
    <property name="name">
        <null />
    </property>
</bean>

value含特殊字符

一:使用&lt代替

<bean id="user" class="com.learn.bean.User">
    <property name="name" value="&lt;&lt;zhangsan"/> <!--<<zhangsan-->
</bean>

二:使用CDATA

<bean id="user" class="com.learn.bean.User">
    <property name="name">
        <value><![CDATA[<<南京>>]]></value>
    </property>
</bean>

外部bean

同样需要有set方法(setUserService()),在UserDao中

<bean id="userService" class="com.learn.service.impl.UserServiceImpl"/>

<bean id="userDao" class="com.learn.dao.UserDao">
    <property name="userService" ref="userService"/>
</bean>

内部bean

<bean id="user" class="com.learn.bean.User">
    <property name="name" value="John"/>
    <property name="age" value="18"/>
    <property name="role">
        <bean class="com.learn.bean.Role">
            <property name="roleId" value="1"/>
            <property name="roleName" value="普通用户"/>
        </bean>
    </property>
</bean>

级联赋值

roleName需要有get方法

<bean id="user" class="com.learn.bean.User">
    <property name="name" value="John"/>
    <property name="age" value="18"/>
    <property name="role" ref="role"/>
    <property name="role.roleName" value="游客"/>
</bean>

<bean id="role" class="com.learn.bean.Role">
    <property name="roleId" value="1"/>
    <property name="roleName" value="普通用户"/>
</bean>

注入集合1

    <bean id="user" class="com.learn.bean.User">
        <property name="hobbies">
<!--            list或者array都可以支持数组对象注入-->
<!--        String[]-->
            <array>
                <value>跑步</value>
                <value>游泳</value>
            </array>
        </property>
<!--        List<E>-->
        <property name="pets">
            <list>
                <value>小猫</value>
                <value>小狗</value>
            </list>
        </property>
<!--        Map<K,V>-->
        <property name="cars">
            <map>
                <entry key="car1" value="car1"/>
                <entry key="car2" value="car2"/>
            </map>
        </property>
<!--        Set<E>-->
        <property name="titles">
            <set>
                <value>title1</value>
                <value>title2</value>
            </set>
        </property>
    </bean>
User{name='null', age='null', role=null, hobbies=[跑步, 游泳, 吉他], pets=[小猫, 小狗], cars={car1=car1, car2=car2}, titles=[title1, title2]}

注入集合2

<property name="pets">
    <list>
        <ref bean="pet1"/>
        <ref bean="pet2"/>
    </list>
</property>

提取

需要引入命名空间

<?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">
    <util:list id="pets">
        <value>123</value>
        <value>456</value>
        <value>789</value>
    </util:list>
    <bean id="user" class="com.learn.bean.User">
        <property name="pets" ref="pets"/>
    </bean>
</beans>

FactoryBean

返回值类型不是类本身

bean.xml

<bean id="myBean" class="com.learn.bean.MyBean"/>

MyBean

public class MyBean implements FactoryBean<User> {
    // 定义返回对象
    @Override
    public User getObject() {
        User user = new User();
        user.setName("MyBean");
        return user;
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

测试

User bean = context.getBean("myBean", User.class);// MyBean

bean单/多实例

单实例 scope="singleton"(默认)

<bean id="myBean" class="com.learn.bean.MyBean" scope="singleton"/>

多实例

<bean id="myBean" class="com.learn.bean.MyBean" scope="prototype"/>

区别:单实例加载配置文件时就会创建一个公共实例,每次用是它。多实例用的时候(getBean)创建,每个实例都是独立的,地址不同。

bean生命周期

  • 构造器构造bean实例

    public User() {
        System.out.println("第一步  -->>  执行无参构造方法创建bean实例...");
    }
    
  • 为bean的属性赋值和对其它bean引用(set方法)

    public void setName(String name) {
        this.name = name;
        System.out.println("第二步  -->>  调用set方法设置属性值...");
    }
    
  • 调用bean的初始化方法(需要配置)

    public void initMethod(){
        System.out.println("第三步  -->>  执行初始化方法...");
    }
    
  • 把bean实例传递给后置处理器的方法(需要实现BeanPostProcessor接口 postProcessBeforeInitialization方法 并在xml文件并配置)(初始化前)

    ⚠️ 所有bean实例都会执行

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步  -->>  bean初始化前");
        return null;
    }
    
  • bean可以使用了(对象获取到)

    User bean = context.getBean(User.class);
    System.out.println(bean.getName());
    
  • 把bean实例传递给后置处理器的方法(需要实现BeanPostProcessor接口 postProcessAfterInitialization 方法 并在xml文件配置)(初始化后)

    ⚠️ 所有bean实例都会执行

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步  -->>  bean初始化后");
        return null;
    }
    
  • 当容器关闭时,调用bean的销毁方法(需要配置)

    context.close();
    

自动装配

autowire = default | no | byName | byType | constructor

byName :根据属性的名字

byType : 根据属性的类型

<bean id="user" class="com.learn.bean.User" autowire="byName"/>
<bean id="role" class="com.learn.bean.Role">
    <property name="roleId" value="1"/>
    <property name="roleName" value="Amy"/>
</bean>

外部属性文件

项目添加 druid-1.2.14.jar 和 mysql-connector-java-8.0.27.jar 并在项目模块设置依赖

需要声明命名空间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: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名称空间-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
<!--    配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
     </bean>
</beans>

注解方式

Spring针对bean管理中创建对象提供注解

  • @Component(普通组件,@Component(value = "user"),只有value可以省略,不写默认value = 类名首字母小写 )
  • @Service(Spring建议放在service层上)
  • @Controller(Spring建议放在controller层上)
  • Repository (Spring建议放在dao层上)

上面注解功能一样,都是创建bean实例

前置

  • 使用注解操作,需要额外的AOP包

  • 添加spring-aop-5.2.9.RELEASE.jar到libs,并在项目结构模块依赖添加该jar包

  • 开启组件扫描:

    <?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:component-scan base-package="com.learn.service" />
        <context:component-scan base-package="com.learn.dao" />
        <context:component-scan base-package="com.learn.controller" />
        <context:component-scan base-package="com.learn.bean" />
    </beans>
    
  • 给类添加注解

    @Component
    public class Student {
        private String name;
        private String sex;
    	// setter getter constructor toString...
    }
    
    @Service
    public class StudentServiceImpl implements StudentService {
        private StudentDao studentDao = new StudentDao();
        public void print(){
            System.out.println("StudentServiceImpl print...");
            studentDao.print();
        }
    }
    
    @Controller
    public class StudentController {
        private StudentService studentService = new StudentServiceImpl();
        public void print(){
            studentService.print();
            System.out.println("StudentController print...");
        }
    }
    
    @Repository
    public class StudentDao {
        public void print(){
            System.out.println("StudentDao print...");
        }
    }
    
  • 注意:开启扫描和使用注解少了任意一个都不行

public class SpringTests {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean(Student.class);
        student.setName("Jack");
        System.out.println(student);
        StudentController controller = context.getBean(StudentController.class);
        controller.print();
    }
}
Student{name='Jack', sex='null'}
StudentController print...
StudentServiceImpl print...
StudentDao print...

自定义扫描

  • use-default-filters:不使用默认的filter
  • 第一个context:component-scan解释:仅在该包扫描带Service注解的类
  • 第二个context:component-scan解释:忽略该包带Component注解的类
<?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:component-scan base-package="com.learn.service" use-default-filters="false">
       <!--						type = annotation | assignable | aspectj | regex | custom 	--> 
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
    <context:component-scan base-package="com.learn.dao" use-default-filters="false">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    <context:component-scan base-package="com.learn.controller" />
    <context:component-scan base-package="com.learn.bean" />
</beans>

属性注入

  • @AutoWired:根据属性类型自动注入
  • Qualifier:根据属性名称注入
  • @Resource:根据属性名称或类型自动注入
  • @Value:诸如普通类型属性
@Controller
public class StudentController {
    @Resource
    private StudentService studentService;
    @Value("666")
    private String name;
    public void print(){
        System.out.println("StudentController print..." + name);// StudentController print...666
        studentService.print();
    }
}

注解配置

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

@Configuration
@ComponentScan(basePackages = {"com.learn"})
public class SpringConfig {
}
public class SpringTests {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        StudentController controller = context.getBean(StudentController.class);
        controller.print();
    }
}

AOP

  • 面向切面编程,降低耦合度,提高重用性

  • 不修改源代码修改而增强功能

  • 底层使用动态代理

  • 代理的两种情况

    • JDK动态代理:创建UserDao接口实现类代理对象
    • CGLIB动态代理:创建当前类子类的代理对象

基本术语

  • 连接点:类里面可以被增强的方法

  • 切入点:被增强的方法

  • 通知(增强):增强的部分

    • 前置通知:@Befaore 切入点 前执行
    • 后置通知:@AfterReturning 切入点 返回结果后执行
    • 环绕通知:@Around 切入点 前、后分别执行
    • 异常通知:@AfterThrowing 切入点 出现异常时
    • 最终通知:@After 切入点 后执行,不管有无异常,最终都会被执行
  • 切面:把通知应用到切点的过程

JDK动态代理

下面的代码所有的方法都会被代理

  • 匿名内部类
public class JDKProxy {
    public static void main(String[] args) {
        // 创建接口实现类代理对象
        Class<?>[] interfaces = {AdminDao.class};

        /*
        * arg1 类加载器 : JDKProxy的类加载器
        * arg2 接口数组 : 要增强的接口
        * arg3 增强逻辑 : 对目标进行增强的逻辑
        * */
        AdminDao adminDao =(AdminDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
            /**
             * 增强部分,参数都是被代理对象原来的内容
             * @param proxy 被代理的对象,空的,需要自己手动添加
             * @param method 执行的方法
             * @param args 传递给方法的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 代理的谁?
                proxy = new AdminDaoImpl();

                // 在 增强方法执行 之前执行的代码
                System.out.println("-------------- >>>> " + method.getName() + "之前执行 ,权限校验。传递的参数: " + Arrays.toString(args));

                // method:当前方法, obj:被代理的对象,  args:参数
                Object res = method.invoke(proxy, args);

                // 在 增强方法执行 之后执行的代码
                System.out.println("-------------- >>>> login之后执行,记录到日志系统");

                return res;
            }
        });


        // 测试
        boolean login = adminDao.login("admin", "123456");
        System.out.println("login result:" + login);

    }
}
  • 实现 InvocationHandler 接口
public class JDKProxy {
    public static void main(String[] args) {
        // 创建接口实现类代理对象
        Class<?>[] interfaces = {AdminDao.class};
        /*
        * arg1 类加载器 : JDKProxy的类加载器
        * arg2 接口数组 : 要增强的接口
        * arg3 增强逻辑 : 对目标进行增强的逻辑
        * */
        AdminDao adminDao =(AdminDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces,new AdminDaoProxy(new AdminDaoImpl()));
        // 测试
        boolean login = adminDao.login("admin", "123456");
        System.out.println("login result:" + login);
    }
}

class AdminDaoProxy implements InvocationHandler{
    // 代理谁把谁传过来
    private Object obj;
    public AdminDaoProxy(Object obj) {this.obj = obj;}
    public AdminDaoProxy() {}
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在 增强方法执行 之前执行的代码
        System.out.println("-------------- >>>> " + method.getName() + "之前执行 ,权限校验。传递的参数: " + Arrays.toString(args));
        // method:当前方法, obj:被代理的对象,  args:参数
        Object res = method.invoke(obj, args);
        // 在 增强方法执行 之后执行的代码
        System.out.println("-------------- >>>> login之后执行,记录到日志系统");
        return res;
    }
}
  • 结果
-------------- >>>> login之前执行 ,权限校验。传递的参数: [admin, 123456]
AdminDaoImpl login ... 验证账号密码 ...
-------------- >>>> login之后执行,记录到日志系统
login result:true
  • 指定方法做处理
method.getName().equals("login")

AOP操作

准备工作

  • 引入依赖

需要依赖spring-aspects以及spring-aspects所依赖的包,之后,项目结构-模块-添加依赖 这样就应用到项目中了

当前包情况

aspectjrt-1.9.6.jar(spring-aspects依赖)
aspectjweaver-1.9.6.jar(spring-aspects依赖)
commons-logging-1.1.1.jar
druid-1.2.14.jar
mysql-connector-java-8.0.27.jar
spring-aop-5.2.9.RELEASE.jar
spring-aspects-5.2.9.RELEASE.jar
spring-beans-5.2.9.RELEASE.jar
spring-context-5.2.9.RELEASE.jar
spring-core-5.2.9.RELEASE.jar
spring-expression-5.2.9.RELEASE.jar

切入点表达式

  • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强。

  • 语法结构:

    execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
    

    权限修饰符:public private 等...* 表示所有

  • 例1:对com.learn.dao.UserDao类里面的add方法进行增强

    execution(* com.learn.dao.UserDao.add(...))
    
  • 例2:对com.learn.dao.UserDao类里面的get开头方法进行增强

    execution(* com.learn.dao.UserDao.get*(...))
    
  • 例3:对com.learn.dao.UserDao类里面的所有方法进行增强

    execution(* com.learn.dao.UserDao.*(...))
    
  • 例4:对com.learn.dao.UserDao类里面的所有类所有方法进行增强

    execution(* com.learn.dao.*.*(...))
    

基于XML

示例

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                          http://www.springframework.org/schema/context 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.learn.bean.Book"/>
    <bean id="bookProxy" class="com.learn.proxy.BookProxyXML"/>

<!--    aop增强配置-->
    <aop:config>
<!--        切入点-->
        <aop:pointcut id="pcSet" expression="execution(* com.learn.bean.Book.set*(..))"/>

<!--        配置切面    把增强应用到切入点-->
        <aop:aspect ref="bookProxy">
<!--            增强(method)作用在具体方法上(pointcut-ref)-->
            <aop:before method="before" pointcut-ref="pcSet"/>
            <aop:after method="after" pointcut-ref="pcSet"/>
            <aop:around method="around" pointcut-ref="pcSet"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pcSet"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pcSet"/>
        </aop:aspect>
    </aop:config>
</beans>

public class Book{
    private String name;
    public void setName(String name) {
        System.out.println("setName...: " + name);
        this.name = name;
    }
	// setter getter...
}

增强类

public class BookProxyXML {

    public void before(){
        System.out.println("before...");
    }
    public void after(){
        System.out.println("after...");
    }
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around - before...");
        Object result = joinPoint.proceed();
        System.out.println("around - after...");
        return result;
    }
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }
}

测试

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Book book = context.getBean(Book.class);
book.setName("三国演义");

输出

before...
around - before...
setName...: 三国演义
afterReturning...
around - after...
after...

基于注解

示例

  • 开启组件扫描 与 开启AspectJ生成代理对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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.learn.proxy" />
    <context:component-scan base-package="com.learn.bean" />
    <aop:aspectj-autoproxy/>
</beans>
  • 类与增强类
@Component
public class Book {
    private Integer id;
    private String name;
	// setter getter...
}
// Book增强的类
@Component
@Aspect// 表示生成代理对象
public class BookProxy {
    // 前置通知
    public void before(){
        System.out.println("before...");
    }
}
  • 配置不同类型的通知
// Book增强的类
@Component
@Aspect // 表示生成代理对象
public class BookProxy {
    // 前置通知
    @Before("execution(* com.learn.bean.Book.getName(..))")
    public void before(){
        System.out.println("before...");
    }
}
  • 测试
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean10.xml");
Book book = context.getBean(Book.class);
book.setName("三国演义");
System.out.println(book.getName());
before...
三国演义
  • 各种通知
public void setName(String name) {
    System.out.println("setName: " + name);
    this.name = name;
}
package com.learn.proxy;

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

// Book增强的类
@Component
@Aspect // 表示生成代理对象
public class BookProxy {
    // 前置通知
    @Before("execution(* com.learn.bean.Book.setName(..))")
    public void before() {
        System.out.println("before...");
    }

    // 后置通知/最终通知 	方法执行完,不管有没有异常都会通知
    @After("execution(* com.learn.bean.Book.setName(..))")
    public void after() {
        System.out.println("after...");
    }

    // 异常通知
    @AfterThrowing("execution(* com.learn.bean.Book.setName(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }

    // 环绕通知
    @Around("execution(* com.learn.bean.Book.setName(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Around - before...");
        Object result = pjp.proceed();
        System.out.println("Around - after...");
        return result;
    }


    // 方法返回值之后
    @AfterReturning("execution(* com.learn.bean.Book.getName(..))")
    public void afterReturning() {
        System.out.println("AfterReturning...");
    }
}

执行顺序(正常)

Around - before...
before...
setName: 三国演义
after...
Around - after...

执行顺序(异常)

Around - before...
before...
setName: 三国演义
afterThrowing...
after...
Exception in thread "main" java.lang.ArithmeticException:....

@Pointcut

在Book类以set开头的方法和Student类以set开头的方法之前通知

@Pointcut("execution(* com.learn.bean.Book.set*(..))")
public void setBook(){}
@Pointcut("execution(* com.learn.bean.Student.set*(..))")
public void setStudent(){}
// 前置通知
@Before("setStudent() || setBook()")
public void before() {
    System.out.println("before...");
}

@Order

优先级,一个类有多个增强时,优先执行增强类

数值越小,优先级越高,@Order(1) 优先级高于 @Order(2)

// Book增强的类
@Component
@Aspect // 表示生成代理对象
@Order(1)
public class BookProxy {}

完全注解

Java代码替代上面的xml文件

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

加载时

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AOPConfig.class);

JDBC Template

准备工作

  • 导包

spring对jdbc的封装

需要的jar包

druid-1.2.14.jar

mysql-connector-java-8.0.27.jar

spring-jdbc-5.2.9.RELEASE.jar

spring-tx-5.2.9.RELEASE.jar

spring-orm-5.2.9.RELEASE.jar

添加并添加到项目依赖

  • 配数据库连接池
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--    引入属性文件,需要context名称空间-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--    配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
  • JDBC Template注入DruidDataSource
<!--    配置jdbc template 注入 dataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
    <property name="dataSource" ref="dataSource"/>
</bean>
  • 组件扫描
<!--    组件扫描-->
<context:component-scan base-package="com.learn.controller"/>
<context:component-scan base-package="com.learn.service"/>
<context:component-scan base-package="com.learn.dao"/>
<context:component-scan base-package="com.learn.bean"/>
  • 创建对应类和接口

dao

@Repository
public class StudentDao {
    @Resource
    private JdbcTemplate jdbcTemplate;
    public void insert(Student student) {
        String sql = "insert into `student` values(?,?,?)";
        Object[] args = {null,student.getName(), student.getSex()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println("StudentDao add --->> " + update);
    }
}

service

@Service
public class StudentServiceImpl implements StudentService {
    @Resource
    private StudentDao studentDao;

    @Override
    public void insert(Student student) {
        studentDao.add(student);
    }
}

Test

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
StudentService service = context.getBean(StudentService.class);
service.add(new Student("testAdd", "男"));

输出

十一月 14, 2022 11:07:14 下午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
StudentDao add --->> 1

CRUD简单示例

service

@Service
public class StudentServiceImpl implements StudentService {
    @Resource
    private StudentDao studentDao;
    @Override
    public void insert(Student student) {
        studentDao.insert(student);
    }
    @Override
    public void updateById(Student student) {
        studentDao.updateById(student);
    }
    @Override
    public void deleteById(Integer id) {
        studentDao.deleteById(id);
    }
    @Override
    public void selectAll() {
        studentDao.selectAll();
    }
}

dao

@Repository
public class StudentDao {
    @Resource
    private JdbcTemplate jdbcTemplate;

    public void insert(Student student) {
        String sql = "insert into `student` values(?,?,?)";
        Object[] args = {null,student.getName(), student.getSex()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println("StudentDao insert --->> " + update);
    }

    public void updateById(Student student) {
        String sql = "update `student` set `name` = ?,`sex` = ? where `id` = ?";
        Object[] args = {student.getName(), student.getSex(), student.getId()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println("StudentDao updateById --->> " + update);
    }

    public void deleteById(Integer id) {
        String sql = "delete from `student` where `id` = ?";
        int update = jdbcTemplate.update(sql, id);
        System.out.println("StudentDao deleteById --->> " + update);
    }

    public void selectAll() {
        String sql = "select * from `student`";
        List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Student>(Student.class));
        System.out.println("StudentDao selectAll --->> " + studentList.toString());
    }
}

Test

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
StudentService service = context.getBean(StudentService.class);
for (int i = 0; i < 5; i++) {
    service.insert(new Student("testAdd" + i, "男"));
}

System.out.println("----------------------------------------");
service.selectAll();

System.out.println("----------------------------------------");
service.updateById(new Student(5, "update", "女"));

System.out.println("----------------------------------------");
service.deleteById(1);

输出

十一月 14, 2022 11:31:57 下午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
StudentDao insert --->> 1
StudentDao insert --->> 1
StudentDao insert --->> 1
StudentDao insert --->> 1
StudentDao insert --->> 1
----------------------------------------
StudentDao selectAll --->> [Student{name='testAdd0', sex='男'}, Student{name='testAdd1', sex='男'}, Student{name='testAdd2', sex='男'}, Student{name='testAdd3', sex='男'}, Student{name='testAdd4', sex='男'}]
----------------------------------------
StudentDao updateById --->> 1
----------------------------------------
StudentDao deleteById --->> 1

批量操作

public void batchAdd(List<Object[]> batchArgs){
    String sql = "insert into `student` values(?,?,?)";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}
public void batchUpdate(List<Object[]> batchArgs){
    String sql = "update `student` set `name` = ?,`sex` = ? where `id` = ?";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}
public void batchUpdate(List<Object[]> batchArgs){
    String sql = "delete from `student` where `id` = ?";
    int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
    System.out.println(Arrays.toString(ints));
}

事务

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

四个特性(ACID)

  • 原子性:不可分割,一个失败都失败
  • 一致性:不出现脏数据、幽灵数据等
  • 隔离性:多对象操作同一条数据时,不相互影响
  • 持久性:提交后数据发生变化

代码环境

表数据

id name money

1 "Bank_A" 1000

2 "Bank_B" 1000

dao

@Repository
public class BankDao {
    @Resource
    private JdbcTemplate jdbcTemplate;

    // 少钱
    public void addMoney(){
        String sql = "update `bank` set money=money+? where `name`=?";
        // A银行多100块
        jdbcTemplate.update(sql, 100,"Bank_A");
    }

    // 多钱
    public void reduceMoney(){
        String sql = "update `bank` set money=money-? where `name`=?";
        // B银行少100块
        jdbcTemplate.update(sql, 100,"Bank_B");
    }
}

service

@Service
public class BankService {
    @Resource
    private BankDao bankDao;

    public void accountMoney(){
        // 转账
        bankDao.reduceMoney();
        // 收到转账
        bankDao.addMoney();    
    }
}

场景引入

模拟异常

@Service
public class BankService {
    @Resource
    private BankDao bankDao;

    public void bankMoney(){
        // 转账
        bankDao.reduceMoney();

        // 模拟异常
        int i = 10/0;

        // 收到转账
        bankDao.addMoney();
    }
}

异常

十一月 15, 2022 12:10:10 上午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.learn.service.BankService.accountMoney(BankService.java:18)
	at com.learn.test.TXTests.main(TXTests.java:11)

初步解决方案

public void bankMoney(){
    try{
        // 第一步:开启事务

        // 第二步:业务操作
        // 转账
        bankDao.reduceMoney();

        // 模拟异常
        int i = 10/0;

        // 收到转账
        bankDao.addMoney();

        // 第三步:没有异常,提交事务
    }catch (Exception e){
        // 第四步:出现异常,事务回滚
    }
}

事务管理操作

一般添加到JavaEE三层结构里的Service层

  • 编程式事务管理(每个Service都手写具体步骤)
  • 声明式事务管理(通过配置),底层使用AOP原理

事务管理API

  • PlatformTransactionManager (org.springframework.transaction)(接口)
    • CallbackPreferringPlatformTransactionManager (org.springframework.transaction.support)(接口)
      • WebSphereUowTransactionManager (org.springframework.transaction.jta)
    • AbstractPlatformTransactionManager (org.springframework.transaction.support)
      • CciLocalTransactionManager (org.springframework.jca.cci.connection)
      • JpaTransactionManager (org.springframework.orm.jpa)
      • DataSourceTransactionManager (org.springframework.jdbc.datasource)
      • JtaTransactionManager (org.springframework.transaction.jta)
        • WebLogicJtaTransactionManager (org.springframework.transaction.jta)
        • WebSphereUowTransactionManager (org.springframework.transaction.jta)
      • HibernateTransactionManager (org.springframework.orm.hibernate5)
    • ResourceTransactionManager (org.springframework.transaction.support)(接口)
      • CciLocalTransactionManager (org.springframework.jca.cci.connection)
      • JpaTransactionManager (org.springframework.orm.jpa)
      • DataSourceTransactionManager (org.springframework.jdbc.datasource)
      • HibernateTransactionManager (org.springframework.orm.hibernate5)

基于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: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 https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--    组件扫描-->
    <context:component-scan base-package="com.learn.controller"/>
    <context:component-scan base-package="com.learn.service"/>
    <context:component-scan base-package="com.learn.dao"/>
    <context:component-scan base-package="com.learn.bean"/>


    <!--    引入属性文件,需要context名称空间-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--    配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

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

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


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

<!--    aop配置-->
    <aop:config>
<!--        切入点-->
        <aop:pointcut id="pc" expression="execution(* com.learn.service.BankService.*(..))"/>
<!--        切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    </aop:config>

</beans>
@Service
public class BankService {
    @Resource
    private BankDao bankDao;
    public void bankMoney(){
            bankDao.reduceMoney();
            // 模拟异常
            int i = 10/0;
            bankDao.addMoney();
    }
}
@Repository
public class BankDao {
    @Resource
    private JdbcTemplate jdbcTemplate;
    // 少钱
    public void addMoney(){
        String sql = "update `bank` set money=money+? where `name`=?";
        jdbcTemplate.update(sql, 100,"Bank_A");
    }
    // 多钱
    public void reduceMoney(){
        String sql = "update `bank` set money=money-? where `name`=?";
        jdbcTemplate.update(sql, 100,"Bank_B");
    }
}
public class TXXMLTests {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean13.xml");
        BankService bankService = context.getBean(BankService.class);
        bankService.bankMoney();// 出现异常,事务回滚了
    }
}

基于注解

1、配置事务管理

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

2、引入名称空间并开启事务注解

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
<!--    开启事务注解	transaction-manager="transactionManager"r-->
<tx:annotation-driven />

?:如果:

<tx:annotation-driven transaction-manager="transactionManager"/> 

dea报:Redundant default attribute value assignment

3、添加注解@Transactional

@Service
@Transactional
public class BankService {
    @Resource
    private BankDao bankDao;
    public void bankMoney(){
        // 转账
        bankDao.reduceMoney();
        // 模拟异常
        int i = 10/0;
        // 收到转账
        bankDao.addMoney();
    }
}

此时执行bankMoney()抛异常,但是金额没变,事务回滚了

完全注解

TXConfig

@Configuration
@ComponentScan(basePackages = {"com.learn.dao","com.learn.service","com.learn.controller","com.learn.bean"})// 开启组件扫描
@EnableTransactionManagement // 开启事务
public class TXConfig {
    // 创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/db_learn?serverTimezone=UTC&useSSL=false&characterEncoding=utf8&useUnicode=true");
        dataSource.setUsername("root");
        dataSource.setPassword("0101");
        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){// 到ioc容器中找到dataSource并注入
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);
        return manager;
    }
}

BankService

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class BankService {
    @Resource
    private BankDao bankDao;
    public void bankMoney(){
        // 转账
        bankDao.reduceMoney();
        // 模拟异常
        int i = 10/0;
        // 收到转账
        bankDao.addMoney();
    }
}

BankDao

@Repository
public class BankDao {
    @Resource
    private JdbcTemplate jdbcTemplate;
    // 少钱
    public void addMoney(){
        String sql = "update `bank` set money=money+? where `name`=?";
        // A银行多100块
        jdbcTemplate.update(sql, 100,"Bank_A");
    }
    // 多钱
    public void reduceMoney(){
        String sql = "update `bank` set money=money-? where `name`=?";
        // B银行少100块
        jdbcTemplate.update(sql, 100,"Bank_B");
    }
}

Test

ApplicationContext context = new AnnotationConfigApplicationContext(TXConfig.class);
BankService bankService = context.getBean(BankService.class);
bankService.bankMoney();

声明式事务管理参数配置

propagation

  • propagation:事务传播行为

    • REQUIRED: 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。

      @Transactional(propagation = Propagation.REQUIRED)// 单独执行就启动新事务,别的事务方法调用我就直接加入该事务,不开启新事务
      
    • REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行.如果有事务正在运行,应该将它挂起

      @Transactional(propagation = Propagation.REQUIRES_NEW)//外层事务方法A调用方法B(也是属于事务的方法,成为内层事务)后出现毛病,内层事务正常提交,外层事务回滚
      
    • SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行.否则它可以不运行在事务中.

      @Transactional(propagation = Propagation.SUPPORTS)// 你调用我,你运行在事务中我也运行在事务中,你不是那我也就普通运行
      

propagation用法讲解:

1、PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启。

2、PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。

3、PROPAGATION_MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

4、PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务存在,则将这个存在的事务挂起。

5、PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。

6、PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。

7、 PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动事务,则按TransactionDefinition.PROPAGATION_REQUIRED属性执行

摘自:https://blog.csdn.net/fight_man8866/article/details/81297929

isolation

  • 事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题.有三个读问题:脏读、不可重复读、虚(幻)读。

    • 脏读: -一个未提交事务读取到另一个未提交事务的数据。

    • 不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

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

      例如:目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。此时,事务B插入一条工资也为5000的记录。这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。

  • isolation:事务隔离级别

脏读 不可重复读 幻读
READ_UNCOMMITTED (读未提交)
READ_COMMITTED(读已提交)
REPEATABLE_READ(可重复,MySQL默认)
SERIALIZABLE(串行化)
  • 示例设置
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)

timeout

事务需要在一定时间内提交,否则进行超市回滚,默认单位:秒,默认值:-1

readOnly

是否只读,默认值:false

当readOnly = true,只能执行查询操作

rollbackFor

设置哪些异常进行回滚

noRollbackFor

设置哪些异常不进行回滚

Spring5新功能

  • 日志

  • 函数式风格操作

  • @Nullable 参数|属性等 可以为空

  • Webflux(功能与webmvc类似,响应式编程,底层与webmvc有很大区别,异步非阻塞)SpringMVC采用命令式编程,Webflux采用异步响应式编程

  • ...

响应式编程

响应式编程(Reactor实现)

  • 响应式编程操作中,Reactor是满足Reactive 规范框架

  • Reactor 有两个核心类,Mono和Flux,这两个类实现接口Publisher, 提供丰富操作符。Flux对象实现发布者,返回N个元素; Mono实现发布者,返回0或者1个元素,

  • Flux 和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值,错误信号,完成信号,错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了。错误信号终止数据流同时把错误信息传递给订阅者。

  • 错误信号和完成信号都是终止信号,不能共存的

  • 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示是空数据流

  • 如果没有错误信号,没有完成信号,表示是无限数据流

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

新建springboot项目并引入依赖

<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.4.22</version>
</dependency>

编写代码

public class TestReactor {
    public static void main(String[] args) {
        // just 直接声明
        Flux.just(1,2,3);
        Mono.just(1);
        
        // 其他方法
        Integer[] array = {1,2,3,4};
        Flux.fromArray(array);
        
        List<Integer> list = Arrays.asList(1,2,3);
        Flux.fromIterable(list);

        Stream<Integer> stream = list.stream();
        Flux.fromStream(stream);
        // 运行后没有效果,需要订阅
    }
}

进行订阅

public class TestReactor {
    public static void main(String[] args) {
        // just 直接声明 并订阅
        Flux.just(1,2,3).subscribe(System.out::print);// 输出 123
        Mono.just(1).subscribe(System.out::print);// 输出 1
    }
}
  • 操作符

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

    • map:元素映射为新元素,如:1、2,3 - > 1、4、9(3个旧元素 -->> 3个新元素)
    • flatMap:元素映射为流,如:"abc","hhh","hello" -> abchhhhello(3个旧元素 -->> 流)

Webflux

  • SpringWebflux执行流程和核心API
    • 基于Reactor, 默认使用容器是Netty,Netty,是高性能的IO框架,异步非阻塞的框架。
    • 执行过程和SpringMVC相似的
    • 核心控制器DispatchHandler, 实现接口WebHandler.
  • 与之前mvc的区别,现在单个元素用Mono,多个元素用Flux

<dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
public interface WebHandler {
    Mono<Void> handle(ServerWebExchange exchange);
}
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);// 处理返回结果
        });
    }
}
  • SpringWebflux 里面DispatcherHandler,负责请求的处理。

    • HandlerMapping:请求查询到处理的方法。
    • HandlerAdapter:真正负责请求处理。
    • HandlerResultHandler:响应结果处理。
  • SpringWebfux实现函数式编程,两个接口: RouterFunction和HandlerFunction

    • RouterFunction:路由功能,请求转发到对应handler
    • HandlerFunction:处理请求并响应
  • 实现方式有两种:

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

注解编程模型

创建SpringBoot项目并引入webflux依赖

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

service

public interface UserService {
    // 根据id查用户
    Mono<User> getUserById();
    // 查询所有用户
    Flux<User> getAllUser();
    // 添加用户
    Mono<Void> saveUser(Mono<User> user);
}
public class User {
    private String name;
    private String gender;
    private Integer age;
	// ...
}
@Repository
public class UserServiceImpl implements UserService {
    private final Map<Integer, User> users = new HashMap<>();
    public UserServiceImpl() {
        this.users.put(1, new User("user1", "男", 18));
        this.users.put(2, new User("user2", "女", 18));
        this.users.put(3, new User("user3", "男", 20));
    }
    @Override
    public Mono<User> getUserById(Integer id) {
        return Mono.justOrEmpty(this.users.get(id));
    }
    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }
    @Override
    public Mono<Void> saveUser(Mono<User> userMono) {
        return userMono.doOnNext(person -> {
            int id = users.size() + 1;
            users.put(id, person);
        }).thenEmpty(Mono.empty());
    }
}
@RestController
public class UserController {
    @Resource
    private UserService userService;
    // id查询
    @GetMapping("/user/{id}")
    public Mono<User> getUserById(@PathVariable Integer id){
        return userService.getUserById(id);
    }
    // 查询所有
    @GetMapping("/user")
    public Flux<User> getUsers(){
        return userService.getAllUser();
    }
    // 添加
    @GetMapping("/save")
    public Mono<Void> saveUser(@RequestBody User user){
        Mono<User> userMono = Mono.just(user);
        return userService.saveUser(userMono);
    }

}

  • SpringMVC方式实现,同步阻塞的方式,基于SpringMVC+Servlet+Tomcat
  • SpringWebflux方式实现,异步非阻塞方式,基于SpringWebflux+Reactor+Netty

函数式编程模型

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

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

UserHandler

public class UserHandler {
    private final UserService userService;
    public UserHandler(UserService userService) {
        this.userService = userService;
    }
    // 根据id查询
    public Mono<ServerResponse> getUserById(ServerRequest request) {
        Integer id = Integer.valueOf(request.pathVariable("id"));
        // 空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        Mono<User> userMono = this.userService.getUserById(id);
        // 转换再返回
        return
                userMono.flatMap(
                        (Function<User, Mono<ServerResponse>>) user ->
                                ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono, User.class)
                                        .switchIfEmpty(notFound));
    }
    // 查询全部
    public Mono<ServerResponse> getAllUser(ServerRequest request) {
        Flux<User> userFlux = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userFlux, User.class);
    }
    // 添加用户
    public Mono<ServerResponse> saveUser(ServerRequest request) {// 存在异常 MonoOnErrorResume
        // 得到User
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.saveUser(userMono));
    }
}

Server

public class Server {
    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.createReactorServer();
        System.out.println("按任意键停止服务...");
        System.in.read();
    }
    // 创建路由
    public RouterFunction<ServerResponse> routerFunction(){
        UserServiceImpl userService = new UserServiceImpl();
        UserHandler userHandler = new UserHandler(userService);

        return RouterFunctions.route(
                GET("/user/{id}").and(accept(APPLICATION_JSON)),userHandler::getUserById)
                .andRoute(GET("/user").and(accept(APPLICATION_JSON)), userHandler::getAllUser)
                .andRoute(GET("/saveUser").and(accept(APPLICATION_JSON)), userHandler::saveUser);

    }
    // 创建服务器完成适配
    public void createReactorServer(){
        // 路由和handler适配
        RouterFunction<ServerResponse> router = routerFunction();
        HttpHandler httpHandler = toHttpHandler(router);

        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);

        // 创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }
}
  • 调用方式

    public class Client {
        public static void main(String[] args) {
            // 调用服务器地址
            WebClient webClient = WebClient.create("http://localhost:xxx");
            // 根据id查询
            User userMono = webClient.get()
                    .uri("/user/{id}", 1)
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToMono(User.class)
                    .block();
    
            System.out.println(userMono.getName());
            
            // 查询所有
            Flux<User> userFlux = webClient.get()
                    .uri("/user")
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToFlux(User.class);
            userFlux.map(User::getName)
                    .buffer().doOnNext(System.out::println).blockFirst();// blockFirst相当于订阅
        }
    }
    

learn from https://www.bilibili.com/video/BV1Vf4y127N5

posted @ 2022-11-15 18:01  夏末秋初~  阅读(4)  评论(0编辑  收藏  举报