spring学习原理

目录

spring创建对象(小例)

通过.xml文件

IOC容器

概念

原理

工厂模式的小例

IOC的原理

IOC接口

Bean管理操作

基于xml文件实现

bean标签的一些属性

 使用set注入

使用有参构造注入(默认使用无参构造创建对象)

(1)字面量

(2)外部bean

(3)内部bean

(4)级联赋值

(5)集合属性

(6)集合中是对象

自动装配

外部属性文件

基于注解实现

bean管理

基于注解实现对象的创建

 基于注解实现属性注入

完全注解开发

spring中的Bean

 bean的作用域

如何设置 

 bean的生命周期

AOP

概念

底层与原理

有接口的情况

无接口的情况

JDK动态代理简单实现

1、创建接口并实现

2、使用proxy创建接口代理对象

AOP操作中的一些术语

1、连接点

2、切入点

3、通知(增强)

4、切面

5、切入点表达式

AOP操作

1、基于xml配置文件实现

2、基于注解实现

相同切入点的抽取

 多个增强类对同一个方法增强

 完全注解开发中如何配置

JdbcTemplate

概念

怎么用

1.配置数据源

2、在配置文件中创建jdbcTemplate对象

3、在dao类中,注入jdbcTemplate,操作数据库

事务操作

事务的四大特性

spring中的事务

编程式事务

声明式事务

spring事务中的API

事务的传播行为

事务的隔离级别

timeOut:超时时间

readOnly:是否只读

rollbackFor:回滚

noRollbackFor:不回滚

spring整合其他框架

整合log4j2

spring框架核心容器支持@Nullable注解

spring框架核心容器支持函数式风格GenericApplicationContext,lambda表达式

sping整合junit4测试框架

 sping整合junit5测试框架

SpringWebflux

介绍

异步非阻塞

webflux特点

响应式编程

 java8及其以前版本

Reactor的实现响应式编程

springwebflux执行流程和核心API

Netty

SpringWebflux的执行过程

API

 注解编程模型方式

函数式编程模型

 WebClient调用


spring创建对象(小例)

通过.xml文件

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

如何获取对象呢

//1.加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2.获取配置创建的对象
User user = context.getBean("user",User.class);
System.out.println(user);
user.add();

IOC容器

概念

  1. 控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
  2. 使用 IOC 目的:为了耦合度降低
  3. 通过.xml得到对象案例就是 IOC 实现

原理

  • xml解析、工厂模式,反射

工厂模式的小例

IOC的原理

IOC接口

IOC本质上就是一个工厂

 sprig提供了两个IOC容器的实现(两个接口)

  • BeanFactory——是spring内部使用的接口,不提供给开发人员使用,加载配置文件时候,不会创建对象,只有获取使用对象才会创建
  • ApplicationContext——BeanFactory接口的子接口,更丰富,提供给开发人员使用,加载配置文件时候,就会把配置文件中的对象都创建

这种耗时的对象创建工作,服务器启动就应该被创建,所以建议使用ApplicationContext

ApplicationContext的两个实现类

 这两个实现的主要区别就是在获取的时候所填的.xml文件名不同,前者要求全路径,后者要求相对路径(项目路径)

Bean管理操作

  1. spring创建对象
  2. spring注入属性

操作的方式主要有两种

  1. 基于xml文件实现
  2. 基于注解的方式实现

基于xml文件实现

bean标签的一些属性

id唯一标识
class类的全路径
name和id差不多

默认使用无参构造创建对象

注入属性

  • DI:依赖注入,就是注入属性     1.使用set注入   2.使用有参构造注入

 使用set注入

<bean id="user" class="com.ms.spring5.User">
    <property name="userName" value="张三"/>
    <property name="age" value="20"/>
</bean>

使用有参构造注入(默认使用无参构造创建对象)

构造函数有几个参数,那么就要写几个与之对应constructor-arg标签

<bean id="user" class="com.ms.spring5.User">
    <constructor-arg name="userName" value="张三"/>
    <constructor-arg index="1" value="20"/><!--也可以用index-->
</bean>

(1)字面量

        1、null值

<bean id="user" class="com.ms.spring5.User">
    <property name="userName">
        <null/>
    </property>
</bean>

        2、属性值包含特殊符号

<bean id="user" class="com.ms.spring5.User">
    <property name="userName">
        <value>
            <![CDATA[<<张三>>]]]><!--输入的内容为<<张三>>-->
        </value>
    </property>
</bean>

(2)外部bean

<bean id="userService" class="com.ms.service.UserService">
    <property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.ms.dao.UserDaoImpl"/>
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();

UserService中要有UserDao的set方法 

(3)内部bean

<bean id="user" class="com.ms.spring5.User">
    <property name="userName" value="张三"/>
    <property name="age" value="20"/>
    <property name="info"><!--也可以通过ref外部bean的方式注入-->
        <bean class="com.ms.spring5.Info"><!--可以不写id-->
            <property name="news" value="你好"/>
            <property name="number">
                <null/>
            </property>
        </bean>
    </property>
</bean>

(4)级联赋值

<bean id="user" class="com.ms.spring5.User">
    <property name="userName" value="张三"/>
    <property name="age" value="20"/>
    <property name="info.news" value="你好"/><!--news要有get方法-->
</bean>

(5)集合属性

<bean id="user" class="com.ms.spring5.User">
    <property name="userName" value="张三"/>
    <property name="strings"><!--数组-->
        <array>
            <value>java</value>
            <value>python</value>
        </array>
    </property>
    <!--数组和list一样-->
    <property name="list"><!--list-->
        <list>
            <value>java</value>
            <value>python</value>
        </list>
    </property>
    <property name="map"><!--map-->
        <map>
            <entry key="姓名" value="张三"/>
            <entry key="性别" value="男"/>
        </map>
    </property>
    <property name="set"><!--set-->
        <set>
            <value>mysql</value>
            <value>redis</value>
        </set>
    </property>
</bean>

(6)集合中是对象

<bean id="user" class="com.ms.spring5.User">
    <property name="userName" value="张三"/>
    <property name="list">
        <list>
            <ref bean="info1"/>
            <ref bean="info2"/>
        </list>
    </property>
</bean>
<bean id="info1" class="com.ms.spring5.Info">
    <property name="news" value="你好"/>
    <property name="number" value="20"/>
</bean>
<bean id="info2" class="com.ms.spring5.Info">
    <property name="news" value="hello"/>
    <property name="number" value="20"/>
</bean>

自动装配

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

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

    <bean id="emp" class="com.ms.autowire.Emp" autowire="byType"/>

外部属性文件

以配置数据库为例

  1. 新建jdbc.properties文件
  2. 在配置文件中引入context名称空间
    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">
  3. 配置数据库信息
    <!--直接配置连接池-->
    <!--<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/userDb"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>-->
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"/>
        <property name="url" value="${prop.url}"/>
        <property name="username" value="${prop.userName}"/>
        <property name="password" value="${prop.password}"/>
    </bean>

基于注解实现

bean管理

spring提供管理bean的注解,都可以用来创建bean的实例,混用也是可以的

  • @Component
  • @Service
  • @Controller
  • @Repository

基于注解实现对象的创建

  1. 开启扫描配置
    1.引入context名称空间
    ​​​
    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">

    2.配置

    <!--开启组件扫描配置-->
    <context:component-scan base-package="com.ms.service,com.ms.dao"/><!--,隔开-->
    <context:component-scan base-package="com.ms"/>
  2. 添加注解
    package com.ms.service;
    
    import org.springframework.stereotype.Component;
    
    //value可以不写,默认是类名首字母小写
    @Component(value = "userService")//<bean id="userService" />
    public class UserService {
        
        public void add(){
            System.out.println("service  add()");
        }
    }
    
  3. 测试

 开启组件扫描的细节

<!--
    use-default-filters:默认是true,使用默认的filter知道所有类扫描
                        false:不使用默认的,自己配置
-->
<context:component-scan base-package="com.ms" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/><!--只扫描带controller注解的类-->
</context:component-scan>
<context:component-scan base-package="com.ms">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/><!--不扫描带controller注解的类-->
</context:component-scan>

 基于注解实现属性注入

spring中提供的注解

  • @Autowired    根据属性类型
  • @Qualifier      根据属性名称
  • @Resource    既可以根据类型,也可以根据名称   本身是javax中的
  • @Value           普通类型

完全注解开发

 创建配置类代替xml文件

1.配置类

package com.ms.config;

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

@Configuration
@ComponentScan(basePackages = "com.ms")//配置作用的范围
public class SpringConfig {

}

2.测试

@Test
public void testService2(){
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = context.getBean("userService", UserService.class);
    userService.add();
}

spring中的Bean

1、普通Bean     2、工厂Bean(FactoryBean)

  • 普通 bean:在配置文件中定义 bean 类型就是返回类型
  • 在配置文件定义 bean 类型可以和返回类型不一样
    1. 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
    2.   实现接口里面的方法,在实现的方法中定义返回的 bean 类型   
      public class MyBean implements FactoryBean {
      
          //定义返回的对象
          @Override
          public Object getObject() throws Exception {
              Info info = new Info();
              info.setNews("你好啊啊");
              info.setNumber(20);
              return info;
          }
      
          @Override
          public Class<?> getObjectType() {
              return null;
          }
      
          @Override
          public boolean isSingleton() {
              return FactoryBean.super.isSingleton();
          }
      }
      @Test
          public void myBean(){
              ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
              Info myBean = context.getBean("myBean", Info.class);//注意此时获取的bean.class
              System.out.println(myBean);
          }

 bean的作用域

在 Spring 里面,设置创建 bean 实例是单实例还是多实例
在 Spring 里面,默认情况下,bean 是单实例对象

如何设置 

bean标签的属性scope  

prototype(多实例):调用getBean才会创建

singleton(单实例):spring加载配置文件,就会创建单实例对象

<bean id="user" class="com.ms.spring5.User" scope="prototype">
    <property name="userName" value="张三"/>
    <property name="age" value="20"/>
</bean>

 

bean的其他属性

  • request:一次请求,每次创建对象放到request中
  • session:一次会话,每次创建对象放到session中

 bean的生命周期

  1. 通过构造器创建实例(也可以通过其他创建),无参构造
  2. 为bean的属性设置值和对其他bean的引用(set方法)
  3. before init:把bean的实例传递给bean后置处理器
  4. 调用bean的初始化方法(需要进行配置)
  5. after init:把bean的实例传递给bean后置处理器
  6. bean可以用了
  7. 容器关闭时,调用bean的销毁方法

Bean对象的代码

package com.ms.bean;


public class Orders {
    private String name;

    public Orders() {
        System.out.println("第一步:调用bean的无参构造创建了实例");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("第二步:调用set方法设置属性的值");
        this.name = name;
    }

    //创建执行初始化的方法
    public void initMethod(){
        System.out.println("第三步:执行初始化的方法");
    }

    //创建销毁时候的方法
    public void destroyMethod(){
        System.out.println("第五步:执行了销毁的方法");
    }
}

后置处理器代码

package com.ms.bean;

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


public class MyBeanPost implements BeanPostProcessor {
    @Nullable
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之前");
        return bean;
    }

    @Nullable
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之后");
        return bean;
    }
}

 xml配置

init-method:初始化执行的方法

 destroy-method:销毁执行的方法

<bean id="orders" class="com.ms.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
    <property name="name" value="手机"/>
</bean>
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.ms.bean.MyBeanPost"/>

 结果

AOP

概念

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

底层与原理

使用动态代理

有接口的情况

使用JDK动态代理

无接口的情况

使用CGLIB代理

JDK动态代理简单实现

参数:

  1. 类加载器
  2. 增强方法所在的类所实现的接口,可以是多个
  3. 实现InvocationHandler,创建代理对象,用于增强

1、创建接口并实现

package com.ms;

public interface UserDao {

    public int add(int a,int b);

    public String update(String id);

}
package com.ms;

public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

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

2、使用proxy创建接口代理对象

package com.ms;

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

public class JDKProxy {
    public static void main(String[] args) {
        Class[] interfaces = {UserDao.class};
        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        System.out.println("res:" + dao.add(1, 2));
    }
}

class UserDaoProxy implements InvocationHandler {

    //创建谁的代理对象,就把谁传递过来
    //通过有参构造
    private Object obj;

    public UserDaoProxy(Object obj) {
        this.obj = obj;
    }

    //增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前
        System.out.println("方法之前执行...,方法名:" + method.getName() + ",传递的参数:" + Arrays.toString(args));
        //增强执行的方法
        Object res = method.invoke(obj, args);
        System.out.println("方法之后执行..." + obj);
        //方法之后
        return res;
    }
}

AOP操作中的一些术语

1、连接点

一个类中哪些方法可以被增强,这些方法称为连接点

2、切入点

实际被真正增强的方法

3、通知(增强)

实际增强的部分逻辑称为通知

  1. 前置通知——方法之前执行
  2. 后置通知——方法之后执行
  3. 环绕通知——方法前后都执行
  4. 异常通知——出现异常执行
  5. 最终通知——finally

4、切面

把通知应用到切入点的过程

5、切入点表达式

作用:知道对哪个类的哪个方法进行增强

语法结构

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

例:execution(*com.ms.dao.UserDao.add(..))//对add方法增强
       
execution(*com.ms.dao.UserDao.*(..))//对所有方法增强
       
execution(*com.ms.dao.*.*(..))//对dao包中所有类的所有方法增强

AOP操作

spring框架一般都是基于AspectJ实现AOP操作
AspectJ不是spring的部分,独立的AOP框架,可以单独使用

如何实现
准备两个类User(需要被增强的类),UserProxy(增强类)

1、基于xml配置文件实现

Book类

package com.ms.aopxml;

public class Book {

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

BookProxy类

package com.ms.aopxml;

public class BookProxy {

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

}

 结果

2、基于注解实现

   ①配置文件中开启注解扫描(也可以通过配置类)
   ②使用注解创建User和UserProxy对象
   ③在增强类上添加注解@Aspect
   ④在spring配置文件中开启生成代理对象

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

    <!--注解扫描-->
    <context:component-scan base-package="com.ms.aop"/>
    <!--开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy/>
    
</beans>

⑤配置不同类型的通知
    在增强类中,在作为通知方法上面添加通知类型注解,使用切入点表达式配置
代码即结果

package com.ms.aop;

import org.springframework.stereotype.Component;

@Component
public class User {

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

}
package com.ms.aop;

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

@Component
@Aspect//生成代理对象
public class UserProxy{

    //前置通知
    @Before(value = "execution(* com.ms.aop.User.add())")
    public void before(){
        System.out.println("before()");
    }

    //方法执行之后
    @After(value = "execution(* com.ms.aop.User.add())")
    public void after(){
        System.out.println("after()");
    }

    //方法给出返回值之后,无论有无异常都执行
    @AfterReturning(value = "execution(* com.ms.aop.User.add())")
    public void afterReturn(){
        System.out.println("afterReturn()");
    }

    //有异常执行
    @AfterThrowing(value = "execution(* com.ms.aop.User.add())")
    public void afterThrow(){
        System.out.println("afterThrow()");
    }


    @Around(value = "execution(* com.ms.aop.User.add())")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前()");
        proceedingJoinPoint.proceed();//执行被增强的方法
        System.out.println("环绕之后()");
    }


}

 

相同切入点的抽取

//相同切入点
@Pointcut(value = "execution(* com.ms.aop.User.add())")
public void pointDemo(){
    
}
//前置通知
@Before(value = "pointDemo()")
public void before(){
    System.out.println("before()");
}

 多个增强类对同一个方法增强

@Order(1)//数字越小优先级越高
@Before(value = "execution(* com.ms.aop.User.add())")
public void before(){
    System.out.println("person.proxy.before()");
}
//前置通知
@Order(2)
@Before(value = "execution(* com.ms.aop.User.add())")
public void before(){
    System.out.println("user.proxy.before()");
}

 完全注解开发中如何配置

新建配置类

package com.ms.config;

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

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

JdbcTemplate

概念

spring框架对JDBC的封装,使之方便的操作数据库

怎么用

1.配置数据源

上面有说过

2、在配置文件中创建jdbcTemplate对象

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

3、在dao类中,注入jdbcTemplate,操作数据库

package com.ms.dao;

import com.ms.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;

@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public void add(Book book) {
        String sql = "insert into Book values(?,?,?)";
        jdbcTemplate.update(sql, book.getId(), book.getName(), book.getStatus());
    }

    @Override
    public int updateBook(Book book) {
        String sql = "update Book set name=?,status=? where id=?";
        return jdbcTemplate.update(sql, book.getName(), book.getStatus(), book.getId());
    }

    @Override
    public int deleteBook(String id) {
        String sql = "delete from Book where id=?";
        return jdbcTemplate.update(sql, id);
    }

    @Override
    public int count() {
        String sql = "select count(*) from Book";
        return jdbcTemplate.queryForObject(sql, Integer.class);
    }

    @Override
    public Book getById(String id) {
        String sql = "select * from Book where id=?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
    }

    @Override
    public List<Book> findAll() {
        String sql="select * from Book";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
    }

    //批量添加
    @Override
    public int addBatch(List<Book> books) {
        String sql="insert into Book values(?,?,?)";
        List<Object[]> batchArgs=new ArrayList<>();
        for (Book book : books) {
            Object[] o={book.getId(),book.getName(),book.getStatus()};
            batchArgs.add(o);
        }
        int[] update = jdbcTemplate.batchUpdate(sql, batchArgs);
        return update.length;
    }

    @Override
    public int updateBatch(List<Book> books) {
        String sql="update Book set name=?,status=? where id=?";
        List<Object[]> batchArgs=new ArrayList<>();
        for (Book book : books) {
            Object[] o={book.getName(),book.getStatus(),book.getId()};//这个顺序要和问号的顺序相同
            batchArgs.add(o);
        }
        return jdbcTemplate.batchUpdate(sql, batchArgs).length;
    }

    @Override
    public int deleteBatch(List<Book> books) {
        String sql="delete from Book where id=?";
        List<Object[]> batchArgs=new ArrayList<>();
        for (Book book : books) {
            Object[] o={book.getId()};//这个顺序要和问号的顺序相同
            batchArgs.add(o);
        }
        return jdbcTemplate.batchUpdate(sql, batchArgs).length;

    }


}

事务操作

事务的四大特性

  1. 原子性——要么都成功,要么都失败
  2. 一致性——操作之前,操作之后数据总量是不变的
  3. 隔离性——多事务之间不会影响
  4. 持久性——事务一旦提交,数据就会变化

dao层一般不包含事务,只操作数据库

service层(业务逻辑层)调用dao层,可能有事务

spring中的事务

编程式事务

@Autowired
private DataSource dataSource;
@Override
public void accountMoney(String moneyFrom, String moneyTo, Integer money) {
    PlatformTransactionManager manager=null;
    TransactionStatus ts=null;
    try {
        //1、开启事务
        manager=new DataSourceTransactionManager(dataSource);
        ts=manager.getTransaction(new DefaultTransactionDefinition());
        //2、业务逻辑
        userDao.addMoney(moneyTo,money);
        userDao.reduceMoney(moneyFrom,money);
        //3、无异常,事务提交
        manager.commit(ts);
    }catch (Exception e){
        e.printStackTrace();
        //4、事务回滚
        assert manager != null;
        assert ts != null;
        manager.rollback(ts);
    }
}

声明式事务

底层就是使用aop

注解方式

第一步:配置事务管理器

<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>>

第二步:开启事务注解

  1. 引入tx名称空间
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  2. 开启事务注解
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

第三步:在service类上,或其方法上添加注解@Transactional

              在类上则所有方法都有事务

              在方法上则该方法有事务

xml方式

第一步:配置事务管理器(和注解方式一样)

第二步:配置通知(增强的部分)

<!--配置通知-->
<tx:advice id="advice">
    <!--配置事务参数-->
    <tx:attributes>
        <!--指定事务添加到哪个方法-->
        <tx:method name="accountMoney" propagation="REQUIRED"/><!--添加事务到accountMoney()方法上-->
        <tx:method name="account*"/><!--添加事务到account开头的方法上-->
    </tx:attributes>
</tx:advice>

第三步:配置切入点和切面

<!--配置切入点和切面-->
<aop:config>
    <!--切入点-->
    <aop:pointcut id="pt" expression="execution(* com.ms.service.impl.UserServiceImpl.*(..))"/>
    <!--切面-->
    <aop:advisor advice-ref="advice" pointcut-ref="pt"/>
</aop:config>

完全注解发开中的声明式事务 

package com.ms.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

@Configuration
@ComponentScan(basePackages = "com.ms")
@EnableTransactionManagement//开启事务注解
public class TxConfig {

    //创建数据库的连接池
    @Bean
    public DruidDataSource addDataSource() throws IOException {
        DruidDataSource dataSource = new DruidDataSource();
        ClassPathResource pathResource = new ClassPathResource("jdbc.properties");
        Properties properties = PropertiesLoaderUtils.loadProperties(pathResource);
        dataSource.setDriverClassName(properties.getProperty("prop.driverClass"));
        dataSource.setUrl(properties.getProperty("prop.url"));
        dataSource.setUsername(properties.getProperty("prop.userName"));
        dataSource.setPassword(properties.getProperty("prop.password"));
        return dataSource;
    }

    //创建jdbcTemplate
    @Bean
    public JdbcTemplate addJdbcTemplate(DataSource dataSource){//这样会到ioc容器中按类型注入
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    //创建事务管理器
    @Bean
    public DataSourceTransactionManager addDataSourceTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

}

spring事务中的API

对应不同操作数据库方法所对应的实现类,jdbcTemplate,mybatis操作数据库使用DataSourceTransactionManager

这些属性在注解中配置和在配置文件中配置都是一样的

事务的传播行为

@Transactional(propagation = Propagation.REQUIRED)
<!--配置通知-->
<tx:advice id="advice">
    <!--配置事务参数-->
    <tx:attributes>
        <!--指定事务添加到哪个方法-->
        <tx:method name="accountMoney" propagation="REQUIRED"/><!--添加事务到accountMoney()方法上-->
        <tx:method name="account*"/><!--添加事务到account开头的方法上-->
    </tx:attributes>
</tx:advice>

REQUIRED:单独执行方法1,开启事务A,如果方法2调用了方法1,事务A会加入到方法2开启的事务B中

REQUIRED_NEW:方法2如果失败,方法1依然能提交

SUPPORTS:如果方法单独执行,可以不开启事务,如果它调用了一个有事务的方法,那么她它就加入到所调用方法的事务中

事务的隔离级别

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

若不考虑事务的隔离性面临的三个问题

  1. 脏读——一个事务读取到了另一个事务未提交的数据
  2. 不可重复读——一个事务当读到了另一个事务修改的数据,可能下次读取的值就变化了
  3. 幻(虚)读——一个未提交的事务读到了另一个事务新添加的数据

timeOut:超时时间

事务需在一定时间内提交,不提交就回滚

默认值:-1,不设置,秒为单位

@Transactional(timeout = 10)

readOnly:是否只读

true:只能查询

false:默认值,可查可改

@Transactional(readOnly = true)

rollbackFor:回滚

设置出现哪些异常要回滚

@Transactional(rollbackFor = IndexOutOfBoundsException.class)

noRollbackFor:不回滚

设置出现哪些异常不回滚

spring整合其他框架

整合log4j2

1、所需要的jar包,也可以通过maven引入

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>

spring框架核心容器支持@Nullable注解

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

spring框架核心容器支持函数式风格GenericApplicationContext,lambda表达式

@Test
public void testLambda(){
    GenericApplicationContext context = new GenericApplicationContext();
    //把book对象注册
    context.refresh();//内容清空
    //指定bean的名字注册
    context.registerBean("book",Book.class,()->new Book("1","西游记","3"));
    System.out.println(context.getBean("book", Book.class));
    //不指定bean的名字注册
    context.registerBean(Book.class,()->new Book("2","水浒传","2"));
    System.out.println(context.getBean("com.ms.entity.Book"));
}

sping整合junit4测试框架

相关依赖

package com.ms.test;

import com.ms.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)//指定单元测试框架
@ContextConfiguration("classpath:bean1.xml")//加载配置文件
public class SpringTest {

    @Autowired
    private UserService userService;

    @Test
    public void testSpringTest(){
        userService.accountMoney("1","2",100);
    }

}

 sping整合junit5测试框架

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")//加载配置文件
public class SpringTest {
    @Autowired
    private UserService userService;
    @Test
    public void testSpringTest(){
        userService.accountMoney("1","2",100);
    }
}

也可以使用一个复合注解

@SpringJUnitConfig(locations = "classpath:bean1.xml")

SpringWebflux

介绍

spring5新添加的模块,用于web开发,功能和springmvc类似,webflux使用响应式编程

springmvc基于servlet容器,webflux是一种异步非阻塞框架,在servlet3.1之后才支持,核心是Reactor的相关API实现的

异步非阻塞

异步:调用者发送请求,无需等服务器回应再做其他操作

同步:调用者发送请求,需等服务器回应再做其他操作

阻塞:被调用者收到请求不立即给出反馈

非阻塞:被调用者收到请求立即给出反馈

webflux特点

1、非阻塞式,可以在不扩充硬件的基础上,提高系统吞吐量和伸缩性

2、函数式编程

3、与springmvc的比较
     

二者都可以使用注解方式,都运行在tomcat服务器中

springmvc采用命名式编程,webflux采用异步响应式编程

webflux可以用在微服务网关中

响应式编程

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

 java8及其以前版本

提供的观察者模式两个类 Observer 和 Observable
package com.ms.demo.demorector8.rector8;

import java.util.Observable;

public class ObserverDemo extends Observable {

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

}

java8以后版本就过时了

Reactor的实现响应式编程

通俗点讲,这就是一种规范

Reactor有两个核心的类,Mono,Flux,都实现了Publisher,提供了丰富的操作符。

Flux对象实现发布者,返回n个元素;Mono也实现发布者,返回0或1个元素

Mono,Flux都是数据流的发布者,可以发出三种数据信号:元素值,错误信号,完成信号(后两者都是终止信号,告诉订阅者数据流结束)

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

代码演示

引入依赖

<!--rector依赖-->
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.1.5.RELEASE</version>
</dependency>
package com.ms.demo.demorector8.recator8;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class TestReactor {

    public static void main(String[] args) {
        //just直接申明
        Flux.just(1,2,3,4);//多个
        Mono.just(1);//1或0个
        
        //其他
        Integer[] arr={1,2,3,4};
        Flux.fromArray(arr);
        
        List<Integer> list= Arrays.asList(arr);
        Flux.fromIterable(list);

        Stream<Integer> stream=list.stream();
        Flux.fromStream(stream);

        Flux.error(new Exception("错误"));
    }
}

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

 操作符

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

  1. map——元素映射为新元素
  2. flatMap——元素映射为流
    把每个元素转换流,把转换之后多个流合并大的流

springwebflux执行流程和核心API

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

Netty

BIO,阻塞方式

NIO,非阻塞方式

SpringWebflux的执行过程

 SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler,负责请求处理

其他的控制器

  • HandlerMapping——请求需要的处理方法
  • HandlerAdapter——真正负责请求处理(业务逻辑)
  • HandlerResultHandler——响应结果处理

 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
public interface WebHandler {

	/**
	 * Handle the web server exchange.
	 * @param exchange the current server exchange
	 * @return {@code Mono<Void>} to indicate when request handling is complete
	 */
	Mono<Void> handle(ServerWebExchange exchange);

}

 

API

两个接口

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

 注解编程模型方式

和springmvc使用相似,只需要把相关依赖配置到项目中(spring-boot-starter-webflux),SpringBoot 自动配置相关运行容器,默认情况下使用 Netty 服务器

说明 :

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

 第一步:配置文件中指定端口号

              

 第二步:创建先关包和类

和springmvc一样的

第三步:代码

UserService

package com.ms.demowebflux.service;

import com.ms.demowebflux.entity.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface UserService {

    //和springmvc有所区别
    Mono<User> getUserById(Integer id);//一个元素

    Flux<User> getAllUser();//多个元素

    Mono<Void> saveUser(Mono<User> userMono);//无返回值

}

 UserServiceImpl

package com.ms.demowebflux.service.impl;

import com.ms.demowebflux.entity.User;
import com.ms.demowebflux.service.UserService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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


@Service
public class UserServiceImpl implements UserService {

    //数据
    private final Map<Integer,User> users=new HashMap<>();

    public UserServiceImpl() {
        this.users.put(1,new User("张三","男",20));
        this.users.put(2,new User("李四","女",34));
        this.users.put(3,new User("王五","男",12));
    }

    @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(u -> {
            int id = users.size() + 1;
            this.users.put(id, u);
        }).thenEmpty(Mono.empty());//值清空表示终止
    }
}

UserController

package com.ms.demowebflux.controller;

import com.ms.demowebflux.entity.User;
import com.ms.demowebflux.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("getUserById/{id}")
    public Mono<User> getUserById(@PathVariable Integer id){
        return userService.getUserById(id);
    }

    @GetMapping("getAllUser")
    public Flux<User> getAllUser(){
        return userService.getAllUser();
    }

    @PostMapping("saveUser")
    public Mono<Void> saveUser(@RequestBody User user){
        return userService.saveUser(Mono.just(user));
    }


}

第四步:测试

函数式编程模型

 需要自己初始化服务器

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

SpringWebflux 请 求 和 响 应 不 再 是 ServletRequest 和 ServletResponse ,而是ServerRequest 和 ServerResponse

第一步:不在需要控制层controller

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

package com.ms.demowebflux2.handler;

import com.ms.demowebflux2.entity.User;
import com.ms.demowebflux2.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class UserHandler {

    private final UserService userService;

    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    public Mono<ServerResponse> getUserById(ServerRequest request) {
        Integer id = Integer.valueOf(request.pathVariable("id"));
        //控制处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        Mono<User> userMono = userService.getUserById(id);
        //userMono转换返回
        //使用reactor操作符flatMap
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono, User.class).switchIfEmpty(notFound);
    }

    public Mono<ServerResponse> getAllUser(ServerRequest request) {
        Flux<User> users = userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users, User.class);
    }

    public Mono<ServerResponse> saveUser(ServerRequest request) {
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(userService.saveUser(userMono));
    }

}

第三步:初始化服务器,编写Router

package com.ms.demowebflux2;

import com.ms.demowebflux2.handler.UserHandler;
import com.ms.demowebflux2.service.UserService;
import com.ms.demowebflux2.service.impl.UserServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;
import java.io.IOException;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

public class Server {

    //1、创建路由
    public RouterFunction<ServerResponse> routerFunction() {
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);
        return RouterFunctions.route(GET("/user/getUserById/{id}").and(accept(APPLICATION_JSON)), handler::getUserById)
                .andRoute(GET("/user/getAllUser").and(accept(APPLICATION_JSON)), handler::getAllUser)
                .andRoute(POST("/user/saveUser").and(accept(APPLICATION_JSON)), handler::saveUser);
    }

    //2、创建服务完成适配
    public void createReactorServer(){
        //路由
        RouterFunction<ServerResponse> routerFunction = routerFunction();
        //handler
        HttpHandler httpHandler=toHttpHandler(routerFunction);
        //适配
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        //服务器
        HttpServer httpServer=HttpServer.create().port(8002);//指定端口,否则随机生成
        httpServer.handle(adapter).bindNow();
    }

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


}

第四步:测试

 WebClient调用

不使用浏览器调用

新建Client类

package com.ms.demowebflux2;

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

public class Client {
    public static void main(String[] args) {
        WebClient webClient = WebClient.create("http://localhost:8002");

        Integer id = 1;
        User user = webClient
                .get()
                .uri("user/getUserById/{id}", id)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve().bodyToMono(User.class)
                .block();
        System.out.println("============================"+user+"============================");

        Flux<User> userFlux = webClient
                .get().uri("user/getAllUser")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve().bodyToFlux(User.class);
        userFlux.map(User::toString).buffer().doOnNext(System.out::print).blockFirst();
    }
}

 测试

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值