Spring5 总结

1. IOC

1.1 概念

  1. 控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
  2. 好处:降低耦合度

1.2 IOC 底层原理

XML解析、工厂模式、反射
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 IOC(接口)

  1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
  2. Spring 提供IOC容器两种实现方式:(两个接口)
    1. BeanFactory: IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
      加载配置文件时不会创建对象,在获取对象(使用)才去创建对象
    2. ApplicationContext: BeanFactory接口的子接口,提供更多强大的功能,一般由开发人员进行使用
      加载配置文件时就会把在配置文件对象进行创建
    3. ApplicationContext接口实现类
      FileSystemXMLApplicationContext:电脑盘符路径
      ClassPathXMLApplicationContext:src下类路径

在这里插入图片描述

1.4 IOC 操作 Bean 管理

  • Bean 管理指的是两个操作:
    • 创建对象
    • 注入属性
  • Bean 管理操作有两种方式:
    • 基于 XML 配置文件方式实现
    • 基于注解方式实现

1.4.1 基于 XML 方式

1.4.1.1 基于 XML 方式创建对象

<bean id="user" class="fan.com.domain.User"></bean>
  • 在 Spring 配置文件里,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
  • 在 bean 标签有很多属性:
    • id 属性:唯一标识,可以不使用 id,只留全类名,此时类名作为唯一标识
    • class 属性:类全路径(包类路径)
    • 创建对象时,默认是执行无参构造方法完成对象创建

1.4.1.2 基于 xml 方式注入属性

DI:依赖注入,就是注入属性

  1. 第一种注入方式:使用 set 方法注入

    public class User {
        private Integer id;
    	private String name;
    
        public void setId(Integer id) {
            this.id = id;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    
    <bean id="user" class="fan.com.domain.User">
    	<property name="id" value="1"></property>
    	<property name="name" value="张三"></property>
    </bean>
    
  2. 第二种注入方式:使用有参构造注入

    public class User {
        private Integer id;
        private String name;
    
        public void printValue(){
            System.out.println(id + ":" + name);
        }
        public User(Integer id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    
    <bean id="user" class="fan.com.domain.User">
    	<constructor-arg name="id" value="1"></constructor-arg>
    	<constructor-arg name="name" value="张三"></constructor-arg>
    </bean>
    
  3. p名称空间注入(需要依赖于 set 方法)

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p" // 名称空间注入
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    		<bean id="user" class="fan.com.domain.User" p:id="1" p:name="张三"></bean>
    </beans>
    

1.4.1.3 基于 XML 方式注入属性(其他类型属性)

  1. 字面量(前面注入的属性都是字面量)\

    • null 值
      <bean id="user" class="fan.com.domain.User">
      	<property name="name">
              <null></null>
          </property>
      </bean>
      
    • 特殊符号 <>
      <bean id="user" class="fan.com.domain.User">
      	<property name="id" value="1"></property>
      	<property name="name">
      		<value> <![CDATA[<<南京>>]]> </value>
      		<!-- <<南京>>,也可使用转义字符 >⁢ -->
      	</property>
      </bean>
      
  2. 注入属性-外部 bean(ref)
    创建两个类 service 和 dao,在 service 里调用 dao 的方法

    public class UserService {
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        public void printValue(){
            System.out.println("service add...");
            userDao.update();
        }
    }
    public class UserDao {
        public void update() {
            System.out.println("userDao update...");
        }
    }
    
    <bean id="userService" class="fan.com.service.UserService">
    	<!-- 使用ref -->
        <property name="userDao" ref="userDao"></property>
    </bean>
    <bean id="userDao" class="fan.com.dao.UserDao"></bean>
    
  3. 注入属性-内部 bean

    public class Dept {
        private String dname;
    
        public void setDname(String dname) {
            this.dname = dname;
        }
    }
    public class Emp {
        private Integer id;
        private String name;
        private Dept dept;
    
        public void setId(Integer id) {
            this.id = id;
        }
        public void setName(String name) {
            this.name = name;
        }
        public void setDept(Dept dept) {
            this.dept = dept;
        }
        public void printValue(){
            System.out.println(id + ":" + name + ":" + dept);
        }
    }
    
    <bean id="emp" class="fan.com.domain.Emp">
        <property name="id" value="1" />
        <property name="name" value="张三" />
    
        <property name="dept">
            <bean id="dept" class="fan.com.domain.Dept">
                <property name="dname" value="安保部" />
            </bean>
        </property>
    </bean>
    
  4. 注入属性-级联赋值

    <!-- 第一种方法 -->
    <bean id="emp" class="fan.com.domain.Emp">
    	<property name="id" value="1" />
    	<property name="name" value="张三" />
    	<property name="dept" ref="dept" />
    </bean>
    <bean id="dept" class="fan.com.domain.Dept">
    	<property name="dname" value="安保部" />
    </bean>
    <!-- 第二种方法 -->
    <bean id="emp" class="fan.com.domain.Emp">
    	<property name="id" value="1" />
    	<property name="name" value="张三" />
    	<property name="dept" ref="dept" />
    	<property name="dept.dname" value="财务部" />  // 需要 get 方法,获取到dept对象
    </bean>
    <bean id="dept" class="fan.com.domain.Dept" />
    
  5. 注入属性-数组、集合、map、set

    public class User {
        private int[] arrays;
        private List<String> lists;
        private Map<Integer,String> maps;
        private Set<String> sets;
    
        public void setArrays(int[] arrays) {
            this.arrays = arrays;
        }
        public void setLists(List<String> lists) {
            this.lists = lists;
        }
        public void setMaps(Map<Integer, String> maps) {
            this.maps = maps;
        }
        public void setSets(Set<String> sets) {
            this.sets = sets;
        }
    
        public void printValue(){
            System.out.println(Arrays.toString(arrays)); System.out.println(lists);
            System.out.println(maps); System.out.println(sets);
        }
    }
    
    <bean id="user" class="fan.com.domain.User">
        <property name="arrays">
            <array>
                <value>1</value>
                <value>2</value>
            </array>
        </property>
        <property name="lists">
            <list>
                <value>张三</value>
                <value>小张</value>
            </list>
        </property>
        <property name="maps">
            <map>
                <entry key="1" value="Map1" />
                <entry key="2" value="Map2" />
            </map>
        </property>
        <property name="sets">
            <set>
                <value>MySQL</value>
                <value>Redis</value>
            </set>
        </property>
    </bean>
    
  6. 注入多个对象属性

    public class User {
        private List<Course> courseList;
    
        public void setCourseList(List<Course> courseList) {
            this.courseList = courseList;
        }
        public void printValue(){
            System.out.println(courseList.toString());
        }
    }
    
    <bean>
    	<property name="courseList">
    		<list>
    			<ref bean="course1" />
    			<ref bean="course2" />
    		</list>
    	</property>
    </bean>
    <bean id="course1" class="fan.com.domain.Course">
        <property name="cname" value="Spring" />
    </bean>
    <bean id="course2" class="fan.com.domain.Course">
        <property name="cname" value="Spring5" />
    </bean>
    
  7. 提取集合公共部分

    <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" // 定义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="nameList">
            <value>张三</value>
            <value>李四</value>
            <value>王五</value>
        </util:list>
        <bean id="user" class="fan.com.domain.User">
            <property name="lists" ref="nameList"></property>
        </bean>
    </beans>
    

1.4.1.4 工厂 Bean(FactoryBean)

  1. Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean (FactoryBean)
  2. 普通 bean:在配置文件中定义 bean 类型就是返回类型
  3. 工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
    • 第一步创建类,让这个类作为工厂 bean,实现接口 FactoryBean
    • 第二步实现接口里面的方法,在实现的方法中定义返回的 bean 类型
<!-- 创建 bean -->
<bean id="user" class="fan.com.domain.User" /> // 创建 bean(user)
public class User implements FactoryBean<Course> {
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("abc");
        return course;
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}
public class Test1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Course course = context.getBean("user", Course.class); // 改成返回的 bean 类型(Course)
        System.out.println(course); // Course{cname='abc'}
    }
}

1.4.2 bean 作用域

  1. 在 Spring 里,默认情况下, bean 是单实例对象。安全的

    public class Test1 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            Course course = context.getBean("course", Course.class);
            Course course1 = context.getBean("course", Course.class);
            System.out.println(course);
            System.out.println(course1);
        }
    }
    

    在这里插入图片描述

  2. 设置为单实例还是多实例(scope)

    • 在 spring 配置文件 bean 标签里面有属性(scope),用于设置单实例还是多实例
    • scope 属性值(singleton、prototype、request、session、websocket、application)
      • 第一个值默认值,singleton,表示是单实例对象,加载 spring 配置文件时就会创建单实例对象
      • 第二个值 prototype,表示是多实例对象,不是在加载 spring 配置文件时创建对象,而是在 getBean 方法时创建多实例对象

1.4.3 bean 生命周期

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

    public Course() {
        System.out.println("第一步:执行构造器方法");
    }
    public void setCname(String cname) {
        this.cname = cname;
        System.out.println("第二步:调用set方法");
    }
    // @PostConstruct
    public void initMethod(){
        System.out.println("第三步:执行初始化方法");
    }
    // @PreDestory
    public void destroyMethod(){
        System.out.println("第五步:执行销毁方法");
    }

}
public class Test1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        Course course = context.getBean("course", Course.class);
        System.out.println("第四步:获取创建 bean 实例对象");
        System.out.println(course);
        ((ClassPathXmlApplicationContext)context).close(); // 调用销毁方法
    }
}
<!-- 配置初始化方法和销毁方法 -->
<bean id="course" class="fan.com.domain.Course" init-method="initMethod" destroy-method="destroyMethod">
	<property name="cname" value="张三" />
</bean>

在这里插入图片描述

1.4.4 后置处理器 BeanPostProcessor

  • postProcessBeforeInitialization 在 Bean 实例化、依赖注入后,初始化前调用
  • postProcessAfterInitialization 在 Bean 实例化、依赖注入、初始化都完成后调用
    当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,值越大优先级越低
<!-- 创建 bean -->
<bean id="coursePost" class="fan.com.domain.CoursePost" />

实现 BeanPostProcessor 接口

public class CoursePost implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之前执行的方法");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之后执行的方法");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
  
    @Override
    public int getOrder() {
        return 5;
    }
}

在这里插入图片描述

1.4.5 XML 自动装配

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

  • byName,根据属性名称注入,注入值 bean 的id值和类属性名称一样
  • byType,根据属性类型注入,假如有两个同样的 bean 报错
<bean id="user" class="fan.com.domain.User" autowire="byName" />
<bean id="course" class="fan.com.domain.Course">
        <property name="cname" value="张三" />
</bean>

1.4.6 XML(引入外部属性文件)

<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:property-placeholder location="classpath:jdbc.properties" />
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${driverClass}" />
            <property name="url" value="${url}" />
            <property name="username" value="${userName}" />
            <property name="password" value="${password}" />
    </bean>
</beans>

1.5 基于注解方式

  1. 注解是代码特殊标记,格式: @注解名称(属性名称=属性值,属性名称=属性..
  2. 使用注解,注解作用在类上面,方法上面,属性上面
  3. 使用注解目的:简化 XML 配置

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

  1. @Component,可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。 使用时只需将该注解标注在相应类上即可。
  2. @Service,通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  3. @Controller,通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  4. @Repository,用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同

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

  • @Bean,通常是我们在标有该注解的方法中定义产生这个 bean 的逻辑,告诉 Spring 这个方法将会返回一个对象,这个对象要注册为 Spring 应用上下文中的 bean。@Bean 注解比 @Component 注解的自定义性更强

1.5.2 基于注解方式创建对象

  1. 开启组件扫描,扫描多个包

    <!-- 扫描上层目录 -->
    <context:component-scan base-package="fan.com" />
    <!-- 逗号隔开 -->
    <context:component-scan base-package="fan.com.dao, fan.com.service" />
    
  2. 创建类,在类上面添加创建对象注解

    // value 可以省略不写,默认值是类名称,首字母小写
    @Service(value = "userService")
    public class UserService {
    
    }
    
  3. 组件扫描细节配置

    • use-default-filters="false" 表示不适用默认 filter,自己配 filter
    • context:include-filter 设置需要扫描哪些内容
    • context:exclude-filter 设置哪些内容不需要扫描
    <context:component-scan base-package="fan.com" use-default-filters="false">
    	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
    

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

  1. @Autowired:根据属性类型进行自动装配。Spring 推荐使用
    @Service(value = "userService")
    public class UserService {
        @Autowired
        private UserDao userDao;
    
        public void add(){
            System.out.println("service add..");
            userDao.update();
        }
    }
    
  2. @Qualifier:根据属性名称进行注入,配合 @Autowired 进行使用,当有多个实现类时,需要根据名称注入
    @Autowired
    @Qualifier(value = "userDaoImpl1")
    private UserDao userDao;
    
  3. @Resource:可以根据类型注入、可以根据名称注入,位于 javax.annotation 包下。Spring 不推荐使用,JavaEE 推荐使用
    @Resource // 根据类型注入
    // @Resource(name = "userDaoImpl1") 根据名称注入
    private UserDao userDao;
    
  4. @Value:注入普通类型属性
    @Value("张三")
    private String name;
    

1.5.4 完全注解开发(配置类)

@Configuration
@ComponentScan(basePackages = {"fan.com"})
public class SpringConfig {
}

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

2. AOP

2.1 概念

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

在这里插入图片描述

2.2 AOP 底层原理

2.2.1 AOP 底层使用动态代理

有两种情况动态代理:

  • 第一种:有接口情况,使用 JDK 动态代理(创建接口实现类代理对象,增强类的方法)
    在这里插入图片描述
  • 第二种:没有接口情况,使用 CGLIB 动态代理(创建当前类子类代理对象,增强类的方法)
    在这里插入图片描述

2.2.2 AOP(JDK 动态代理)

使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象, 调用 newProxyInstance 方法,三个参数

  • 第一参数,类加载器
  • 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
  • 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的方法
public interface UserDao {
    public int add(int a, int b);
    public String update(String id);
}
public class UserDaoImpl implements UserDao {
    @Override
    public String update(String id) {
        return id;
    }
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}
public class JDKProxy {
    public static void main(String[] args) {
        Class[] interfaces = {UserDao.class}; // 类实现接口
        UserDaoImpl userDaoImpl = new UserDaoImpl();
        UserDao userDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDaoImpl));
        int result = userDao.add(1,2);
        // 代理对象UserDaoProxy返回的结果,add方法,返回为3,假如为update方法,返回null
        System.out.println(result);
    }
}
class UserDaoProxy implements InvocationHandler{
    private Object obj;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("add".equals(method.getName())){ // 判断是哪个方法才执行
            System.out.println("方法之前执行..." + method.getName() + "传递的参数:" + Arrays.toString(args));
            Object result = method.invoke(obj,args); // 方法执行,传入对象和参数
            System.out.println("方法之后执行" + obj);
            return result;
        }else {
            return null;
        }

    }
}

在这里插入图片描述

2.3 AOP 术语

  1. Joinpoint 连接点:那些被拦截到的点,类里面哪些方法可以被拦截到被增强,这些方法称为连接点
  2. Pointcut 切入点:指要对哪些 Joinpoint 进行拦截,即被拦截的连接点,实际被真正增强的方法,称为切入点
  3. Advice 通知(增强):实际增强的逻辑部分称为通知(增强)
    • 前置通知 @Before:方法执行之前执行
    • 后置/返回通知 @AfterReturning:方法返回结果之后执行
    • 环绕通知 @Around:方法之前和之后都执行
    • 异常通知 @AfterThrowing:异常时执行
    • 最终通知:@After(finally):方法执行之后执行
  4. Aspect 切面:一个动作,把通知应用到切入点的过程
  5. Target 目标:指代理的目标对象
  6. Weaving 织入:指把增强代码应用到目标上,生成代理对象的过程
  7. Proxy 代理:指生成的代理对象

在这里插入图片描述

2.4 AOP 操作

  1. Spring 框架一般都是基于 AspectJ 实现 AOP 操作
    AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用, 进行 AOP 操作
  2. 基于 AspectJ 实现 AOP 操作
    • 基于 XML 配置文件实现
    • 基于注解方式实现(实用)
  3. 切入点表达式
    • 作用:知道对哪个类里面的哪个方法进行增强
    • 语法结构:execution([权限修饰符] [返回类型(可省略)] [类全路径] [方法名称] ([参数列表]))
      • 举例 1:对 fan.com.dao.BookDao 类里面的 add 进行增强
        execution(* fan.com.dao.BookDao.add(..)) // 修饰符 包名.类名.方法名(参数..)
      • 举例 2:对 fan.com.dao.BookDao 类里面的所有的方法进行增强
        execution(* fan.com.dao.BookDao.* (..))
      • 举例 3:对 fan.com.dao 包里面所有类,类里面所有方法进行增强
        execution(* fan.com.dao.*.* (..))

2.4.1 基于注解方式(抽取公共切入点)

  1. 开启注解扫描和生成代理对象:

    <?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="fan.com" />
        <aop:aspectj-autoproxy />
    </beans>
    

    使用完全注解开发

    @Configuration
    @ComponentScan(basePackages = {"fan.com"})
    @EnableAspectJAutoProxy(proxyTargetClass = true) // 开启代理对象
    public class SpringConfig {
    }
    
  2. 类和增强类:

    @Component
    public class User {
        public void add(){
            System.out.println("user add...");
        }
    }
    
    @Component
    @Aspect
    public class UserProxy {
        @Pointcut(value = "execution(* fan.com.domain.User.add(..))") // 抽取公共切入点
        public void pointDemo(){
    
        }
        @Before(value = "pointDemo()")
        public void before() {
            System.out.println("前置通知before.........");
        }
        @After(value = "pointDemo())")
        public void after() {
            System.out.println("最终通知after.........");
        }
        @AfterThrowing(value = "pointDemo()")
        public void afterThrowing() {
            System.out.println("异常通知afterThrowing.........");
        }
        @AfterReturning(value = "pointDemo()")
        public void afterReturning() {
            System.out.println("后置通知afterReturning.........");
        }
        @Around(value = "pointDemo()")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable{
            try {
                System.out.println("环绕前置通知"); //前置通知@Before
                joinPoint.proceed(); //目标方法执行
                System.out.println("环绕返回通知"); //环绕返回通知@AfterReturning
            } catch (Throwable throwable) {
                System.out.println("环绕异常通知"); //环绕异常通知@AfterThrowing
                throw new RuntimeException(throwable);
            } finally {
                System.out.println("环绕最终通知"); //最终通知@After
            }
        }
    }
    

    执行顺序:* 正常情况:环绕前置 == @Before == 目标方法执行 ==@AfterReturning == @After == 环绕返回 ==环绕最终

    • 异常情况:环绕前置== @Before == 目标方法执行 == @AfterThrowing == @After == 环绕异常 == 环绕最终
      在这里插入图片描述
  3. 多个增强类对同一个方法进行增强,设置增强类优先级
    在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高

    @Component
    @Aspect
    @Order(1) // 设置优先级
    public class PersonProxy {
        @Pointcut(value = "execution(* fan.com.domain.User.add(..))")
        public void pointDemo(){
        }
    
        @Before(value = "pointDemo()")
        public void before() {
            System.out.println("person before.........");
        }
    }
    

    在这里插入图片描述

  4. 测试类

    public class Test1 {
        public static void main(String[] args) {
    		// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
            User user = ac.getBean("user", User.class);
            user.add();
        }
    }
    

2.4.2 AspectJ 配置文件

<bean id="book" class="fan.com.Book"></bean> 
<bean id="bookProxy" class="fan.com.BookProxy"></bean>
<aop:config> 
    <!--切入点--> 
    <aop:pointcut id="p" expression="execution(* fan.com.Book.buy(..))"/> 
    <!--配置切面 aop:aspect 大多用于面向切面编程,aop:advisor 大多用于事务管理--> 
    <aop:aspect ref="bookProxy"> 
        <!--增强作用在具体的方法上--> 
        <aop:before method="before" pointcut-ref="p"/> 
    </aop:aspect> 
</aop:config>
  1. 定义切面 <aop:aspect>

    • 该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 <aop:aspect> 之前需要先定义一个普通的 Spring Bean
    • id 用来定义该切面的唯一表示名称,ref 用于引用普通的 Spring Bean
    <aop:config>
        <aop:aspect id="myAspect" ref="aBean">
            ...
        </aop:aspect>
    </aop:config>
    
  2. 定义切入点 <aop:pointcut>

    • <aop:pointcut>元素作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享
    • <aop:pointcut> 元素作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效
    • id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式
    <aop:config>
        <aop:pointcut id="myPointCut" expression="execution(* net.biancheng.service.*.*(..))"/>
    </aop:config>
    
  3. 定义通知

    <aop:aspect id="myAspect" ref="aBean">
        <!-- 前置通知 -->
        <aop:before pointcut-ref="myPointCut" method="..."/>
        <!-- 后置通知 -->
        <aop:after-returning pointcut-ref="myPointCut" method="..."/>
        <!-- 环绕通知 -->
        <aop:around pointcut-ref="myPointCut" method="..."/>
        <!-- 异常通知 -->
        <aop:after-throwing pointcut-ref="myPointCut" method="..."/>
        <!-- 最终通知 -->
        <aop:after pointcut-ref="myPointCut" method="..."/>
        .... 
    </aop:aspect>
    

3. JdbcTemplate

3.1 配置连接属性

<?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="fan.com" />
    <!-- 引入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" /> 
    <!-- 配置数据库连接池 Druid -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${user}"></property>
        <property name="password" value="${password}"></property>
    </bean>
    <!-- 配置 JdbcTemplate 对象,注入 DataSource -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

3.2 Dao层

public interface UserDao {
    User selectUser(int id); // 按id 查询
    List<User> selectAll(); // 查询所有
    int selectTotalCount(); // 查询总记录条数
    int updateUser(User user); // 按id 修改
    int insertUser(User user); // 插入
    int deleteUser(int id); // 按id 删除
    int[] batchAdd(List<Object[]> users); // 批量插入
}
@Component
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override // 按id 查询
    public User selectUser(int id) {
        String sql = "select * from user where id = ? ";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
        return user;
    }
    @Override // 查询所有
    public List<User> selectAll() {
        String sql = "select * from user";
        List<User> maps = jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));
        return maps;
    }
    @Override // 查询总记录条数
    public int selectTotalCount() {
        String sql = "select count(*) from user";
        int i = jdbcTemplate.queryForObject(sql, Integer.class);
        return i;
    }

    @Override // 按id 修改
    public int updateUser(User user) {
        String sql = "update user set name = ?, age = ? where id = ?";
        Object[] args = {user.getName(),user.getAge(),user.getId()};
        int update = jdbcTemplate.update(sql, args);
        return update;
    }

    @Override // 插入
    public int insertUser(User user) {
        Object[] args = {user.getId(),user.getName(),user.getAge()};
        String sql = "insert into user value(?,?,?)";
        int update = jdbcTemplate.update(sql, args);
        return update;
    }
    @Override // 批量插入
    public int[] batchAdd(List<Object[]> users) {
        String sql = "insert into user value(?,?,?)";
        int[] ints = jdbcTemplate.batchUpdate(sql, users);
        return ints;
    }

    @Override // 按id 删除
    public int deleteUser(int id) {
        String sql = "delete from user where id = ?";
        int update = jdbcTemplate.update(sql, id);
        return update;
    }
}

3.3 Service 层

@Service(value = "userService")
public class UserService {
    @Autowired
    private UserDao userDao;

    public User selectUser(int id){
        User user = userDao.selectUser(id);
        return user;
    }
    public List<User> selectAll() {
        List<User> users = userDao.selectAll();
        return users;
    }
    public int selectTotalCount() {
        int i = userDao.selectTotalCount();
        return i;
    }

    public int updateUser(User user){
        int i = userDao.updateUser(user);
        return i;
    }

    public int insertUser(User user){
        int i = userDao.insertUser(user);
        return i;
    }
    public int[] batchAdd(List<Object[]> users) {
        int[] ints = userDao.batchAdd(users);
        return ints;
    }

    public int deleteUser(int id){
        int i = userDao.deleteUser(id);
        return i;
    }
}

3.4 测试类

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = ac.getBean("userService", UserService.class);
      
        User user = new User(2,"ff",18);
        int i = userService.updateUser(user);
        System.out.println(i); // 1
        List<User> users = userService.selectAll();
        System.out.println(users);
      
        List<Object[]> users = new ArrayList<>();
        Object[] user1 = {6, "6", 6};
        Object[] user2 = {7,"7",8};
        Object[] user3 = {8, "7", 8};
        users.add(user1);
        users.add(user2);
        users.add(user3);
        int[] ints = userService.batchAdd(users);
    }
}

4. 事务操作

4.1 概念

事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。典型场景:银行转账

  • lucy 转账 100 元 给 mary
  • lucy 少 100,mary 多 100
<?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">
    <context:component-scan base-package="fan.com" />
    <context:property-placeholder location="classpath:jdbc.properties" />
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${user}"></property>
        <property name="password" value="${password}"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

Dao 层

public interface PriceDao {
    public void reduce();
    public void add();
}
@Component
public class PriceDaoImpl implements PriceDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void reduce() {
        String sql = "update price set price = price - ? where name = ?";
        jdbcTemplate.update(sql,100,"张三");
    }
    @Override
    public void add() {
        String sql = "update price set price = price + ? where name = ?";
        jdbcTemplate.update(sql,100,"李四");
    }
}

Service 层

@Service
public class PriceService {
    @Autowired
    private PriceDao priceDao;

    public void account(){
        priceDao.reduce();
        int a = 10 / 0;
        priceDao.add();
    }
}

4.2 接口

PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 是事务的 3 个核心接口

4.2.1 PlatformTransactionManager 接口

PlatformTransactionManager 接口用于管理事务,接口定义如下:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

在这里插入图片描述

4.2.2 TransactionDefinition 接口

TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下

public interface TransactionDefinition {
    int getPropagationBehavior();
    int getIsolationLevel();
    String getName();
    int getTimeout();
    boolean isReadOnly();
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2.3 TransactionStatus 接口

TransactionStatus 接口提供了一些简单的方法来控制事务的执行和查询事务的状态,接口定义如下:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
}

在这里插入图片描述

4.3 Spring 事务管理介绍

  1. 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
  2. 在 Spring 进行事务管理操作有两种方式:编程式事务管理和声明式事务管理(实用)
  3. 声明式事务管理
    • 基于注解方式(实用)
    • 基于 xml 配置文件方式
  4. 在 Spring 进行声明式事务管理,底层使用 AOP 原理
  5. Spring 事务管理 API
    提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

在这里插入图片描述

4.4 注解声明式事务管理

  1. 配置事务管理器
    <!-- 创建事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource" ref="dataSource" /> // 注入数据源
    </bean>
    
  2. 开启事务注解
    <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" // 引入名称空间 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 ">
    	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
    	</bean>
    	<!-- 开启事务注解 -->
    	<tx:annotation-driven transaction-manager="transactionManager" />
    </beans>
    
  3. 在 service 类上面(或者 service 类里面方法上面)添加事务注解
    • @Transactional,这个注解添加到类上面,也可以添加方法上面
    • 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
    • 如果把这个注解添加方法上面,为这个方法添加事务

4.5 声明式事务管理参数配置

在这里插入图片描述

  1. propagation:事务传播行为:多事务方法直接进行调用,这个过程中事务 是如何进行管理
    在这里插入图片描述
    在这里插入图片描述
  2. ioslation:事务隔离级别
  3. timeout:超时时间
    • 事务需要在一定时间内进行提交,如果不提交进行回滚
    • 默认值是 -1 ,设置时间以秒单位进行计算
  4. readOnly:是否只读
    • 读:查询操作,写:添加修改删除操作
    • readOnly 默认值 false,表示可以查询,可以添加修改删除操作
    • 设置 readOnly 值是 true,设置成 true 之后,只能查询
  5. rollbackFor:回滚
    设置出现哪些异常进行事务回滚
  6. noRollbackFor:不回滚
    设置出现哪些异常不进行事务回滚

4.6 XML 声明式事务管理

<!--1 创建事务管理器--> 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <!--注入数据源--> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
 
<!--2 配置通知--> 
<tx:advice id="txadvice"> 
    <!--配置事务参数--> 
    <tx:attributes>
        <!--指定哪种规则的方法上面添加事务--> 
        <tx:method name="account" propagation="REQUIRED"/> 
        <!--<tx:method name="account*"/>--> 
    </tx:attributes> 
</tx:advice> 
 
<!--3 配置切入点和切面--> 
<aop:config> 
    <!--配置切入点--> 
    <aop:pointcut id="pt" expression="execution(* fan.com.service.PriceService.*(..))"/> 
    <!--配置切面 aop:advisor 大多用于事务管理,aop:aspect 大多用于面向切面编程-->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/> 
</aop:config>

4.7 完全注解开发声明式事务管理

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

4.8 事务失效的原因(重要)

4.8.1 数据库引擎不支持

以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。 从 MySQL 5.5.5 开始的默认存储引擎是 InnoDB,之前默认的都是 MyISAM。所以这点需要注意,底层引擎不支持事务,再怎么操作事务都无效。

4.8.2 没有被 Spring 管理

// @Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

4.8.3 方法不是 public 的

@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

4.8.4 自身调用问题

@Service
public class OrderServiceImpl implements OrderService {
    public void update(Order order) {
        updateOrder(order);
    }
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

上面的 update 方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务不管用。

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrder(Order order) {
        // update order
    }
}

上面这次在 update 方法上加了 @Transactional,而 updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务不管用。

因为它们发生了自身调用,调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效。

解决方案:

<!-- 在Spring配置中添加标签 -->
<aop:aspectj-autoproxy expose-proxy="true"/>
<!-- <aop:config expose-proxy="true"> -->
// 在代码的调用中要求使用代理对象去调用即可:
((ServiceA ) AopContext.currentProxy()).insert();

4.8.5 数据源没有配置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

4.8.6 不支持事务

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
        // update order
    }
}

Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起

4.8.7 异常被 catch

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
			// 异常被 catch,但又不抛出来
        }
    }
}

4.8.8 异常类型错误

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            throw new Exception("更新错误");
        }
    }

}

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:

@Transactional(rollbackFor = Exception.class)

这个配置仅限于 Throwable 异常类及其子类

5. Spring5 新特性

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

5.1 自带的通用日志封装 log4j2

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

5.1.1 引入依赖

<dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.13.3</version>
</dependency>
<dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.13.3</version>
</dependency>
<!-- 使用slf4j日志门面 -->
<dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.13.3</version>
</dependency>

5.1.2 配置文件 log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="INFO">
    <!--定义所有的appender -->
    <appenders>
        <!--这个输出控制台的配置 -->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式 -->
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息 -->
        <logger name="org.springframework" level="INFO"></logger>
        <logger name="org.mybatis" level="INFO"></logger>
        <root level="info">
            <appender-ref ref="Console" />
        </root>
    </loggers>
</configuration>

5.1.3 手动打印日志

public class UserLog {
    public static final  Logger log = LoggerFactory.getLogger(UserLog.class);
    public static void main(String[] args) {
        log.info("hello log4j2");
        log.warn("hello log4j2");
    }
}

5.2 Spring5 核心容器支持 @Nullable 注解

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

  1. 注解用在方法上面,方法返回值可以为空
    在这里插入图片描述
  2. 注解使用在方法参数里面,方法参数可以为空
    在这里插入图片描述
  3. 注解使用在属性上面,属性值可以为空
    在这里插入图片描述

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

//函数式风格创建对象,交给 spring 进行管理
public void testGenericApplicationContext() {
	//1 创建 GenericApplicationContext 对象 
	GenericApplicationContext context = new GenericApplicationContext();
	//2 调用 context 的方法对象注册 
	context.refresh();
	context.registerBean("user1",User.class,() -> new User());
	//3 获取在 spring 注册的对象 
	// User user = (User)context.getBean("com.atguigu.spring5.test.User"); 
	User user = (User)context.getBean("user1");
	System.out.println(user);
}

5.4 Spring5 支持整合 JUnit5

5.4.1 整合 JUnit4

@RunWith(SpringJUnit4ClassRunner.class) // 单元测试框架 
@ContextConfiguration("classpath:bean.xml") // 加载配置文件 
public class JTest4 { 
    @Autowired 
    private UserService userService; 
    @Test 
    public void test1() { 
        userService.accountMoney(); 
    } 
} 

5.4.2 Spring5 整合 JUnit5

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

6. Webflux

Spring5 新添加的模块,用于 Web 开发。功能和 SpringMVC 类似,Webflux 使用响应式编程出现的框架。传统 Web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet3.1 以后才支持,核心是基于 Reactor 的相关 API 实现的。

6.1 异步非阻塞(NIO)

异步与非阻塞都是针对对象不一样

  • 异步和同步针对调用者
    调用者发送请求,如果等着对方回应之后才去做其他事情就是同步;如果发送请求之后不等着对方回应就去做其他事情就是异步
  • 阻塞和非阻塞针对被调用者
    被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞;受到请求之后马上给出反馈然后再去做事情就是非阻塞

6.2 SpringMVC 与 Webflux 对比

Webflux

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

在这里插入图片描述

  • 两个框架都可以使用注解方式,都运行在 Tomcat 等容器中
  • SpringMVC 采用命令式编程,Webflux 采用异步响应式编程

6.3 响应式编程(Java 实现)

6.3.1 概念

响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

例: 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似 "=B1+C1" 的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。

6.3.2 Java8 及其之前版本

提供的观察者模式两个类 Observer 和 Observable

public class ObserverDemo extends Observable {
    public static void main(String[] args) {
        ObserverDemo observerDemo = new ObserverDemo();

        observerDemo.addObserver((o, arg) -> {
            System.out.println("发生变化");
        });
        observerDemo.addObserver((o, arg) -> {
            System.out.println("手动被观察者通知,准备改变");
        });

        observerDemo.setChanged();
        observerDemo.notifyObservers();
    }
}

在这里插入图片描述

6.3.3 Java9 及其之后版本

使用 Flow 这个 API,Flow 是 JDK 对 Reactive Stream (响应式流/反应流) 的实现,Reactive Stream 是一套基于发布/订阅模式的数据处理规范。

响应式流从 2013 年开始,作为提供非阻塞背压的异步流处理标准的倡议。 它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞,或订阅者需要有无限制的缓冲区或丢弃。
更确切地说,Reactive 流目的是“找到最小的一组接口,方法和协议,用来描述必要的操作和实体以实现这样的目标:以非阻塞背压方式实现数据的异步流”。

响应式流 (Reactive Stream) 规范诞生,定义了如下四个接口:

  • Subscription:接口定义了连接发布者和订阅者的方法
  • Publisher:接口定义了发布者的方法
  • Subscriber:接口定义了订阅者的方法
  • Processor<T,R>:接口定义了处理器

6.4 响应式编程(Reactor 实现)

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

6.4.1 引入 POM 依赖

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

6.4.2 实现

public class ReactorDemo {
    public static void main(String[] args) {
        // just 方法直接声明
        Flux<Integer> flux = Flux.just(1, 2, 3, 4);
        flux.subscribe(System.out::println);
      
        Mono<Integer> mono = Mono.just(1);
        mono.subscribe(System.out::print);

        // 其他方法
        Integer[] integers = {1, 2, 3, 4};
        Flux.fromArray(integers);

        List<Integer> list = Arrays.asList(integers);
        Flux.fromIterable(list);

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

在这里插入图片描述

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

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

6.4.3 操作符

对数据流进行一道道操作,称为操作符

  1. map:元素映射为新元素
    在这里插入图片描述
  2. flatMap:元素映射为流。把每个元素转换成流,再把转换后的多个流合并成大的流
    在这里插入图片描述

6.4.4 Spring Webflux 执行流程和核心 API

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

  1. BIO
    在这里插入图片描述
  2. NIO。通过 Channel(通道),在 Selector(选择器)里进行注册。实现多路复用,对每个通道选择不同的状态
    在这里插入图片描述
  3. Spring Webflux 执行过程和 SpringMVC 相似
    Spring Webflux 核心控制器 DispatchHandler,实现接口 WebHandler,其中有一个 handle 方法
    public Mono<Void> handle(ServerWebExchange exchange) { // http 请求响应信息
    	if (this.handlerMappings == null) {
    		return createNotFoundError();
    	}
    	if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
    		return handlePreFlight(exchange);
    	}
    	return Flux.fromIterable(this.handlerMappings)
    			.concatMap(mapping -> mapping.getHandler(exchange))
    			.next()
    			.switchIfEmpty(createNotFoundError())
    			.flatMap(handler -> invokeHandler(exchange, handler))
    			.flatMap(result -> handleResult(exchange, result));
    }
    
  4. Spring Webflux 里面 DispatcherHandler,负责请求的处理
    • HandlerMapping:请求查询到处理的方法
    • HandlerAdapter:真正负责请求处理
    • HandlerResultHandler:响应结果处理
  5. Spring Webflux 实现函数式编程,两个接口:RouterFunction(路由处理)和 HandlerFunction(处理函数)

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

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

6.5.1 引入 POM 依赖

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

6.5.2 Controller

@RestController
public class UserController {

	@Resource
	private UserService userService;

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

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

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

6.5.3 Entity 和 Service

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String id;
    private String name;
    private String gender;
}

public interface UserService {
	Mono<User> getUserById(int id);

	Flux<User> getAllUser();

	Mono<Void> addUser(Mono<User> userMono);
}

@Service
public class UserServiceImpl implements UserService {

	public static final Map<Integer, User> MAP = new HashMap<>();

	public UserServiceImpl() {
		MAP.put(1, new User("user1", "张三", "男"));
		MAP.put(2, new User("user2", "李四", "女"));
		MAP.put(3, new User("user3", "王五", "男"));
	}

	@Override
	public Mono<User> getUserById(int id) {
		return Mono.justOrEmpty(MAP.get(id));
	}

	@Override
	public Flux<User> getAllUser() {
		return Flux.fromIterable(MAP.values());
	}

	@Override
	public Mono<Void> addUser(Mono<User> userMono) {
		return userMono.doOnNext(user -> {
			MAP.put(MAP.size() + 1, user);
		}).thenEmpty(Mono.empty());
	}
}

在这里插入图片描述
在这里插入图片描述

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

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

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

6.6.1 Entity 和 Service

与 6.5.3 同

6.6.2 handler

创建 Handler(具体实现方法)

public class UserHandler {

    private final UserService userService;

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

    public Mono<ServerResponse> getUserById(ServerRequest serverRequest) {
        // 获取 Id 值
        Integer id = Integer.valueOf(serverRequest.pathVariable("id"));
        // 空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        // 调用 service 方法得到数据
        Mono<User> userMono = userService.getUserById(id);
        // 把 userMono 进行转换返回
        return userMono.flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromObject(user))).switchIfEmpty(notFound);
    }

    public Mono<ServerResponse> getAllUser(ServerRequest serverRequest) {
        Flux<User> allUser = userService.getAllUser();

        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                .body(userMono, User.class);
//        return userMono.flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
//                .body(BodyInserters.fromObject(user))).switchIfEmpty(notFound);
    }

    public Mono<ServerResponse> addUser(ServerRequest serverRequest){
        // 得到 user 对象
        Mono<User> userMono = serverRequest.bodyToMono(User.class);

        return ServerResponse.ok().build(userService.addUser(userMono));
    }
}

6.6.3 Server

先创建 Router 路由,再创建服务器完成适配,最后进行调用。

public class Server {

	// 3. 调用
    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.createReactorServer();
        System.in.read();
    }

    // 1. 创建 Router 路由
    public RouterFunction<ServerResponse> routerFunction() {
        UserServiceImpl userService = new UserServiceImpl();
        UserHandler userHandler = new UserHandler(userService);

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

    // 2. 创建服务器完成适配
    public void createReactorServer(){
        // 路由和 handler 适配
        RouterFunction<ServerResponse> routerFunction = routerFunction();
        HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
        ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);

        // 创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(handlerAdapter).bindNow();
    }
}

6.6.4 Client

使用 WebClient 调用

public class Client {
    public static void main(String[] args) {
        // 调用服务器地址
        WebClient webClient = WebClient.create("http://localhost:1289");

        // 根据 Id 查询
        String id = "1";
        User block = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON)
                .retrieve().bodyToMono(User.class).doOnNext(System.out::println).block();
        System.out.println(block);

        // 查询所有
        webClient.get().uri("/users").accept(MediaType.APPLICATION_JSON)
                .retrieve().bodyToFlux(User.class).doOnNext(System.out::println).blockLast();
    }
}

在这里插入图片描述
在这里插入图片描述

posted @ 2022-12-21 13:52  凡223  阅读(5)  评论(0编辑  收藏  举报