五万字的Spring5学习笔记,带你熟悉运用Spring5

推荐先学

  1. JAVA高级核心-JDBC-与数据库交互的一套标准规范
  2. 九万字的JavaWeb学习记录,从入门到入坟,更近一步

概述

在这里插入图片描述

介绍

  • 是轻量级开源的javaEE框架。
  • 可以解决企业开发应用的复杂性。
  • 核心:
    • IOC:控制反转,将创建对象过程交给Spring
    • AOP:面向切面,不修改源代码进行功能增强

特点

  • 少jar包,轻量级。
  • 方便复杂开发。
  • 方便与其他框架进行结合开发。
  • 方便操作数据库,尤其是事务管理。
  • 方便程序测试。
  • 方便API的开发。

入门

  • 导入jar包,网上下载,入门前导入五个jar包。

  • 首先是非Spring 官网的jar包:commons-logging-1.2.jar

  • 然后是四个Spring官方的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
  • 首先在src目录下编写一个xml文件,Spring config

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

    bean 标签表示创建一个对象,id为user,来自哪个包下的类。

  • 然后编写测试类,测试是否成功。

    package com.www.hyb.spring5.Test;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import com.www.hyb.spring5.User;
    
    public class SpringTestFirst {
        @Test
        public void FirstSpring(){
    //        加载src下的配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("firstSpringTes.xml");
    //        获取配置创建的对象
            User user = (User) context.getBean("user",User.class);
            System.out.println(user);
            user.out();
        }
    }
    

IOC容器

  • 为了降低两类之间的耦合度。
    • 加入有两个类,分别有两个方法,若是其中一个类想调用另一个类的方法,就要在该类中new另一个类的对象来调用其方法。
    • 这样可行,但是耦合度太高,若是另一个类的方法名发生改变,这边这个类的方法名也要发生改变。
    • 为了降低耦合度,提出了工厂模式,我们可以让new 另一个类的对象放在一个公共类里,这样,哪个类想调用这个类的方法直接调用工程类就可以了。
  • 但紧紧是工厂类还不行,IOC 可以将耦合度降得更低。
  • 主要是:xml解析+工厂模式+反射。

底层

  • IOC思想是底层是基于IOC容器完成的,其底层就是工厂模式。

  • xml文件:

    <bean id="user" class="www.hyb.com.User"></bean>
    
  • 工厂类:

    class UserFactory{
        public static User getUser(){
            //xml解析
            String S=xml文件中calss的值;
            Class class=Class.forName(s);
            return (User)class.newInstance();
        }
    }
    
  • 上面可以进一步减低耦合度,若是User类的路径改变,我们只要改变xml中的路径就可以了。

  • 在IOC中,主要有两个重要的接口:

    BeanFactory:Spring框架内部接口,不建议使用,在解析的xml文件时不会立马创建一个对象。

    ApplicationContext:BeanFactory子接口,建议使用,在解析xml文件时会立马创建一个对象,这样的好处时让服务器事先处理好。在此接口中,有两个重要的实现类:FileSystemXmlApplicationContext 该实现类解析xml文件夹时要将xml文件的全路径写入。ClassPathXmlApplicationContext 该实现类在解析xml文件时候直接将文件的名字放进去就可以了,但这个文件要放在src目录下。

Bean管理

xml管理

属性注入
  • 对象创建

    <bean id="user" class="com.hyb.www.spring5.User">
    
    </bean>
    
  • 属性注入:

    <property name="name" value="hyb"></property>
    
  • 创建User类,记住,要想设置哪个属性,就一定得创建其set方法

    package com.hyb.www.spring5;
    
    public class User {
    
    
        private String name;
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    
        public void setName(String name) {
            this.name=name;
        }
    }
    
  • 编写测试类:

        @Test
        public void FirstSpring(){
    //        加载src下的配置文件
            ApplicationContext context = new ClassPathXmlApplicationContext("firstSpringTes.xml");
    //        获取配置创建的对象
            User user = (User) context.getBean("user",User.class);
            System.out.println(user.toString());
        }
    
  • 前面说过类里一定要有set方法,其实不然,还有另一种注入方法,在类里写一个有参构造。

    private int id;
    
    public User(int id) {
        this.id = id;
    }
    

    其toString我们得重写一下。

  • 然后在xml文件里:

    <bean id="user" class="com.hyb.www.spring5.User">
        <property name="name" value="hyb"></property>
        <constructor-arg name="id" value="1"></constructor-arg>
    </bean>
    
  • 其测试类一样。

  • 所以,我们总结,xml管理的属性注入有两种方式分别对应java属性赋值的两种方式,set方法,有参构造方法,分别用两个标签对象两个方法赋值。

  • 下面了解一下其他注入的方式,命名空间注入:

    在xml文件头标签里我们可以看到有这么一个属性:

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

    我们可以起另一个属性与其类似,也放在xml文件夹头标签的属性里。

    xmlns:p="http://www.springframework.org/schema/p"
    
  • 然后我们注入属性的时候可以直接代替其set方法对应的注入方法,在bean标签的属性里注入:

        <bean id="user" class="com.hyb.www.spring5.User" p:name="hyb">
    <!--        <property name="name" value="hyb"></property>-->
            <constructor-arg name="id" value="1"></constructor-arg>
    
        </bean>
    
  • 当然这种方式只能代替set方法,不能代替有参构造,可读性不好,了解一下即可。

特殊注入
  • 空值注入:

        <bean id="user" class="com.hyb.www.spring5.User" >		<property name="name" value="">        	<null/>        </property>        <constructor-arg name="id" value="1"></constructor-arg>    </bean>
    
  • 特殊符号注入:

        <bean id="user" class="com.hyb.www.spring5.User" >		<property name="name" value="">        	<null/>        </property>        <constructor-arg name="id">        	<value>            	<![CDATE[<<1>>]]>            </value>        </constructor-arg>    </bean>
    

    也可以将<>进行转义:&lt,&gt

外部Bean注入
  • 加入有两个类,一个类需要调用另一个类的方法,那么利用注入的方法改怎么做?

  • 首先我们创建两个类,一个类里有一种属性是另一个类。

    package com.hyb.www.spring5.Dao.Impl;import com.hyb.www.spring5.Dao.UserDao;public class UserDaoImpl implements UserDao {    @Override    public void update() {        System.out.println("UserDaoImplement被调用了!");    }}
    
    package com.hyb.www.spring5.Service.Impl;import com.hyb.www.spring5.Dao.Impl.UserDaoImpl;import com.hyb.www.spring5.Dao.UserDao;public class ServiceImpl implements Service {    private UserDao userDao;    public void setUserDao(UserDaoImpl userDao) {        this.userDao=userDao;    }    @Override    public void out() {        userDao.update();    }}
    

    我们可以看到,ServiceImpl类里要调用DaoImpl类,就要将其当成自己的属性,然后给予set方法。

  • 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="userDao" class="com.hyb.www.spring5.Dao.Impl.UserDaoImpl">    </bean>    <bean id="service" class="com.hyb.www.spring5.Service.Impl.ServiceImpl">        <property name="userDao" ref="userDao" ></property>    </bean></beans>
    
  • ref属性代表要引入外部哪一个bean标签。

内部bean
  • 和前面的内部bean返回来,将外部bean套进去就可以了。

    <bean id="service" class="com.hyb.www.spring5.Service.Impl.ServiceImpl">    <property name="userDao">        <bean class="com.hyb.www.spring5.Dao.Impl.UserDaoImpl">        </bean>    </property></bean>
    
级联bean
  • 像外部bean和内部bean注入,都是级联bean,还有一种级联bean注入方法,是修改一个类里的属性。

  • 这里我们在UserDaoImpl类里加入一个属性name,然后写其set方法,因为我们在ServiceImpl里调用其接口,所以我们要在UserDao中写name的set方法,就相当于其实现方法重写这个setName。

  • 下面xml:

        <bean id="service" class="com.hyb.www.spring5.Service.Impl.ServiceImpl">        <property name="userDao" ref="userDao">        </property>        <property name="userDao.name" value="hyb"></property>    </bean>    <bean id="userDao" class="com.hyb.www.spring5.Dao.Impl.UserDaoImpl">    </bean>
    

    我们可以看到,userDao是ServiceImpl里的属性,而userDao又是一个类,所以可以改变其name属性。

    但其底层是用get方法实现的,所以要在ServiceImpl里实现getUserDao。

    也就是说,当类a里有属性类b时,你要改变类b里的属性c,就要先创建b的对象,这里我们用了外部bean进行创建了,创建成功后,在类a里,产生getB方法,才能改变类b里的属性c。(记住,改变属性c,也是要在setC的基础上的。)

注入数组,集合,Map
  • 首先创建一个类。

    package com.hyb.www.spring5;import java.util.Arrays;import java.util.List;import java.util.Map;public class Student {    private String[] course;    private List<String> list;    private Map<String,Object> map;    public void setCourse(String[] course) {        this.course = course;    }    public void setList(List<String> list) {        this.list = list;    }    public void setMap(Map<String, Object> map) {        this.map = map;    }    @Override    public String toString() {        return "Student{" +                "course=" + Arrays.toString(course) +                ", list=" + list +                ", map=" + map +                '}';    }}
    
  • 然后编写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="student" class="com.hyb.www.spring5.Student"><!--        数组-->        <property name="course" >            <array>                <value>java</value>                <value>php</value>            </array>        </property>        <property name="list">            <list>                <value>张三</value>                <value>李四</value>            </list>        </property>        <property name="map">            <map>                <entry key="key1" value="value1"></entry>                <entry key="key2" value="value2"></entry>            </map>        </property>    </bean></beans>
    
  • 然后编写测试类

    @Testpublic void insertMap(){    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Collection.xml");    Student student = (Student) context.getBean("student");    System.out.println(student.toString());}
    
集合中放对象
  • 先创建一个类

    package com.hyb.www.spring5;public class Course {    private String name;    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "Course{" +                "name='" + name + '\'' +                '}';    }}
    
  • 然后编写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="student" class="com.hyb.www.spring5.Student"><!--        数组-->        <property name="course" >            <array>                <value>java</value>                <value>php</value>            </array>        </property>        <property name="list">            <list>                <value>张三</value>                <value>李四</value>            </list>        </property>        <property name="map">            <map>                <entry key="key1" value="value1"></entry>                <entry key="key2" value="value2"></entry>            </map>        </property>        <property name="courseList">            <list>                <ref bean="course1"></ref>                <ref bean="course2"></ref>            </list>        </property>    </bean>    <bean id="course1" class="com.hyb.www.spring5.Course">        <property name="name" value="java"></property>    </bean>    <bean id="course2" class="com.hyb.www.spring5.Course">        <property name="name" value="python"></property>    </bean></beans>
    

    可见,我们存放集合的时候发生了一些不同,必须得先创建出我们要存放进去的对象,然后用外联bean的方式存放到list集合当中。

抽取公共部分
  • 以集合为例,该集合只能用于本bean下使用,若是能抽取成公共的,就可减少了很多代码。

  • 首先我们还是新建一个类

    package com.hyb.www.spring5;import java.util.List;public class Book {    private List<String > name;    public void setName(List<String> name) {        this.name = name;    }    @Override    public String toString() {        return "Book{" +                "name=" + name +                '}';    }}
    
  • 然后是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: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="bookList">        <value>java</value>        <value>php</value>        <value>python</value>    </util:list>    <bean id="book" class="com.hyb.www.spring5.Book">        <property name="name">            <ref bean="bookList"></ref>        </property>    </bean></beans>
    

    注意,xml这里的头标签bean 里的属性是发生增加了的。

  • 然后是测试类

    @Testpublic void ListTest(){    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("List.xml");    Book book= (Book) context.getBean("book");    System.out.println(book.toString());}
    

工厂bean

  • 从前面各类bean中可以看到,是受本身类型限制的,这便是工厂bean的由来。

  • 首先我们还是创建一个类,让其实现接口FactoryBean。

    package com.hyb.www.spring5.FactoryBean;
    
    import com.hyb.www.spring5.bean.Course;
    import org.springframework.beans.factory.FactoryBean;
    
    public class factoryBean implements FactoryBean<Course> {
    
    
        /*
        * 该方法决定了bean的返回类型
        * */
        @Override
        public Course getObject() throws Exception {
            Course course = new Course();
            course.setName("java");
            return course;
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    

    该类可以设置泛型,且里面有一个方法决定返回值类型。

  • 在xml文件了,我们直接new 这个bean对象就可以了。

    <bean id="factoryBean" class="com.hyb.www.spring5.FactoryBean.factoryBean">
    
    </bean>
    
  • 然后测试类我们便根据具体类型具体测试就可以了

    @Test
    public void testFactory(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("FactoryBean.xml");
        Course course = context.getBean("factoryBean", Course.class);
        System.out.println(course);
    }
    

bean的作用域

  • 作用在一个单实例或多实例中。
单实例
  • 前面的都是单实例,想验证是否是单实例,可以在测试类中,进行两次getBean,会发现这些对象的地址都是一样的。这些便是单实例,这是系统默认的,用singleton表示。
多实例
  • 我们可以自定义多实例,在xml文件中,创建bean对象的时候,加入scope属性,prototype表示多实例,singleton表示单实例对象,而session和request表示会话和请求。
区别
  • 单实例是在配置文件加载的时候就帮我们创建好了对象,只有一个,所以在测试的时候获取到的对象都是相同的。
  • 而多实例是在每次getBean的时候才会创建一个对象,所以多实例获取到的对象是不同的。

bean生命周期

生命历程
  • 首先是通过无参构造创建bean实例。
  • 调用set方法设置bean的属性值。
  • 把bean实例传给bean的后置处理器。
  • 调用bean初始化方法。
  • 后置处理器将bean传出来。
  • 获得一个可以使用的bean
  • 当容器关闭的时候,调用bean的销毁方法。
  • 在上面的生命历程中,要演示出后置处理器和初始化还有销毁方法,必须得自己设置内置属性,而且这个后置处理器可有可无。
举例
  • 首先,我们创建一个类

    package com.hyb.www.spring5.BeanLife;public class BeanLife {    private String name;    public BeanLife() {        System.out.println("第一步,调用了无参构造");    }    public void setName(String name) {        this.name = name;        System.out.println("第二步,调用了set方法");    }    public void init(){        System.out.println("调用了初始化方法");    }    public void destroy(){        System.out.println("调用了销毁方法!");    }    @Override    public String toString() {        return "BeanLife{" +                "name='" + name + '\'' +                '}';    }}
    

    在这个类里,我们写好该有的步骤,都输出一个语句,看看待会执行的步骤。

  • 然后编写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="beanLife" class="com.hyb.www.spring5.BeanLife.BeanLife"        init-method="init" destroy-method="destroy">        <property name="name" value="生命周期演示">        </property>    </bean>    <bean id="beanLifePost" class="com.hyb.www.spring5.BeanLife.BeanLifePost"></bean></beans>
    

    在xml中,id=beanLifePost 只是一个普通的bean new一个对象的标签,但这里其所属类继承了后置处理器,所以变成了后置处理标签。

    package com.hyb.www.spring5.BeanLife;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;import org.springframework.lang.Nullable;public class BeanLifePost implements BeanPostProcessor {    /*    * 为了演示,我们让他实现后置处理器    * 但是这个后置处理器里的两个方法是不能重写的    * 所以我们下面进行手动模拟一下重写    * */    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        System.out.println("进入了后置处理器1");        return bean;    }    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        System.out.println("进入了后置处理2");        return bean;    }}
    
  • 最后,我们编写测试类来测试

    @Testpublic void test(){    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("BeanLife.xml");    BeanLife beanLife = context.getBean("beanLife", BeanLife.class);        /*     * 下面输出便表示bean已经可以使用了     * */    System.out.println(beanLife.toString());    /*    * 该方法表示销毁,若关闭context,将会调用我们自定义的销毁方法    * */    context.close();}
    

自动装配

  • 前面的给属性赋值的过程中,都叫手动装配,还有一种装配,根据名字和类型进行装配。

  • 首先我们创建两个类:

    package com.hyb.www.spring5.autowire;public class B {}
    
    package com.hyb.www.spring5.autowire;public class A {    private B b;    public void setB(B b) {        this.b = b;    }    @Override    public String toString() {        return "A{" +                "b=" + b +                '}';    }}
    
  • 然后编写xml文件

        <bean id="a" class="com.hyb.www.spring5.autowire.A" autowire="byName"><!--        <property name="b" ref="b"></property>-->    </bean>    <bean id="b" class="com.hyb.www.spring5.autowire.B">    </bean>
    

    我们可以看见,自动装配时,要用autowrie 标签,并且byName表示按照名字装配,这个名字必须规定 要装配的类的id值要和被装配的类里的属性名一样,例如 这里的另一个bean标签中id名字要和类A里的属性B的名字一样。

    如果用byType属性,则一定得是一个类只能有一个bean,也就是说,如果用byType 自动装配,只能出现一个关于B的bean标签。

  • 注意,在开发中,一般很少用到自动装配,一般都是通过注解反射的方式进行自动装配。

引入外部属性文件

  • 这里我们以数据库连接池为例子,之前我们学到过,在进行数据库连接池连接的时候,要将数据库的一些信息都放在一个properties文件包里。
  • 那么在这里也是,只不过我们强调用xml文件来解析这个properties文件。
举例
  • 这里我们还是以durid来距离,我们先导入druid包。然后写一个properties的配置文件

    prop.driverClass=com.mysql.jdbc.Driverprop.url=jdbc:mysql://localhost:3306/hybprop.userName=rootprop.password=15717747056HYB
    
  • 之后我们用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"       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="druidSql.properties"></context:property-placeholder>    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">        <property name="driverClassName" value="${prop.driverClass}"></property>        <property name="url" value="${prop.url}"></property>        <property name="username" value="${prop.userName}"></property>        <property name="password" value="${prop.password}"></property>    </bean></beans>
    

    注意其头标签的属性是发生改变了的。

注解管理

  • 上面我们学过xml管理来创建对象,接下来学习注解管理创建对象。
  • 在Spring中,提供了四大注解来创建对象。
    • @Component:用在任何地方
    • @Service:建议用在Service层
    • @Controller:建议用在web层
    • @Respository:建议用在Dao层。
    • 以上四个注解任何地方都可使用,只是为了方便,建议用在该用的地方。
创建对象
  • 在下载好Spring 框架包中找到jar包spring-aop-5.2.9.RELEASE.jar引入

  • 开启组件扫描,让Spring这个容器扫描出哪个类有注解,要进行创建对象。

    • 首先要如引入外部属性文件的举例子那般,加入context命名空间
    • 然后才可以写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"       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">    <!--        base-package:定义包的路径,确定哪些文件需要被扫描。        具体路径具体被扫描,也可以写上级目录,表示该目录下都被扫描    -->    <context:component-scan base-package="com.hyb.www.spring5"></context:component-scan></beans>
    
  • 开启组件扫描后,编写含有注解的类

    package com.hyb.www.spring5.Annotation.Service;import org.springframework.stereotype.Component;/** 该value可以不写,默认是类名首字母小写* 而且,这个注解不是唯一的,只要是四大注解里的都行* */@Component(value = "userService")public class UserService {    public void out(){        System.out.println("UserService有了Component注解!");    }}
    
  • 然后编写测试类测试,测试类都一样

    @Testpublic void test1(){    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("AnnotationXml.xml");    UserService bean = context.getBean("userService", UserService.class);    bean.out();}
    
组件扫描细化
  • 在xml配置组件扫描的时候,可以更加细化,可以精确到有哪个注解的类进行扫描:
<context:component-scan base-package="com.hyb.www.spring5" use-default-filters="false">    <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/></context:component-scan>

use-default-filter=false表示不进行默认全部扫描。context:include代表 只要有Component类型的注解就进行扫描。

  • 还可以精确到哪个注解不进行扫描:
<context:component-scan base-package="com.hyb.www.spring5">    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan>
注解属性注入
  • 基于三大注解的属性注入:
    • @AutoWired 根据属性类型注入
    • @Qualifier 根据属性名称注入
    • @Resource 两者兼顾 不是Spring的包下,而是javax包下
    • @Value 普通类型注入
  • 注意,举例之前首先要开启组件扫描。
Dao
package com.hyb.www.spring5.Annotation.Dao;public interface UserDao {    public void add();}
package com.hyb.www.spring5.Annotation.Dao;public interface OrderDao {    public void order();}
DaoImpl
package com.hyb.www.spring5.Annotation.Dao.UserDaoImpl;import com.hyb.www.spring5.Annotation.Dao.OrderDao;import org.springframework.stereotype.Repository;@Repository(value = "orderDaoImpl")public class OrderImpl implements OrderDao{    @Override    public void order() {        System.out.println("OrderDao实现类!");    }}
package com.hyb.www.spring5.Annotation.Dao.UserDaoImpl;

import com.hyb.www.spring5.Annotation.Dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("UserDao实现类!");
    }
}
Service
package com.hyb.www.spring5.Annotation.Service;

import com.hyb.www.spring5.Annotation.Dao.OrderDao;
import com.hyb.www.spring5.Annotation.Dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;


/*
* 该value可以不写,默认是类名首字母小写
* 而且,这个注解不是唯一的,只要是四大注解里的都行
* */
@Service(value = "userService")
public class UserService {

    /*
    * 自动类型注入,不用set方法,内部给我们封装好了。
    * */
    @Autowired

    /*
    * 但是上面的注解有个弊端,若是我们UserDao层下有多个实现类
    * 根据类型注入就显得有心无力了(不知道要注入到哪个类型的类),所以需要第二个搭配一起使用
    * 那就是名称注入!
    * */
    @Qualifier(value = "userDaoImpl")
    private UserDao userDao;

    /*
     * 上面两个注解要做到全面得两个结合使用
     * 但还有更加好用的,只不过不是Spring官方的
     * 而是java本身的javax包下的注解
     * 它在隐匿了name属性的时候是类型注入
     * 也可以用name属性实现名称注入
     * */
    @Resource(name="orderDaoImpl")
    private OrderDao orderDao;

    /*
    * Value注解的方便在于省去xml的方式给属性赋值,
    * 当然我们也可以按照传统的方法赋值,但是不符合Spring的思想
    * */
    @Value(value="hyb")
    private String name;

    public void out(){
        System.out.println("UserService有了Component注解!"+name);
        userDao.add();
        orderDao.order();
    }
}
test
@Testpublic void test1(){    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("AnnotationXml.xml");    UserService bean = context.getBean("userService", UserService.class);    bean.out();}
完全注解开发
  • 前面的注解属性注入中,尽管xml文件很简洁了,但是还是会有组件扫描开启的标签,完全注解开发便可以省去这个标签。甚至省去xml配置文件。

  • 它的思想便是将xml文件的配置信息放在一个类。

    package com.hyb.www.spring5.Annotation.Config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    /*
    * 第一个注解表示完全注解开发
    * 第二个注解表示开启组件扫描
    * */
    @Configuration
    @ComponentScan(basePackages = {"com.hyb.www.spring5"})
    public class AnnotationConfig {
    }
    
  • 然后我们删除xml文件,进行测试

    @Test
    public void test1(){
        ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
        UserService bean = context.getBean("userService", UserService.class);
        bean.out();
    
    }
    
  • 完全注解开发到这里了解一下即可,因为要用相关知识开发的话,会用另一个框架Springboot。

AOP

  • 面向切面编程。
  • 这是oop的创新,降低了软件各部分功能的耦合度,能更有利于分布式开发。
  • 通俗来讲就是让一个软件分不同部分开发维护,降低后期升级的难度。

底层

  • AOP的底层是动态代理:
    • 第一种是有接口的情况,假如有一个接口,有一个方法login,那么其实现类要实现这个方法。而AOP则是通过代理模式创建一个代理对象来完成和login一样的功能。该名为jdk动态代理。
    • 若是没有接口,AOP会创建其子类为代理对象,完成一样的功能。
package com.hyb.www.spring5.Proxy;public interface UserDao {    public int add(int a,int b);    public String update(String id);}
package com.hyb.www.spring5.Proxy;public class UserDaoImpl implements UserDao{    @Override    public int add(int a,int b) {        return a+b;    }    @Override    public String update(String id) {        return id;    }}
package com.hyb.www.spring5.Proxy;import com.hyb.www.spring5.bean.User;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class JDKProxy {    public static void main(String[] args) {        Class[] interfaces={UserDao.class};        UserDao userDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserProxy(new UserDaoImpl()));        int add = userDao.add(1, 2);        System.out.println(add);        String update = userDao.update("1");        System.out.println(update);    }}class UserProxy implements InvocationHandler{    Object object;    public UserProxy(Object o) {        this.object=o;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        Object o = method.invoke(object, args);        return o;    }}

术语

  • 连接点:假设一个类里有多个方法需要被增强,这些需要被增强的方法就被称为连接点。
  • 切入点:确实被增强了的方法伪切入点。
  • 通知(增强),已经被增强了的方法的增强部分。
    • 前置通知
    • 后置通知。
    • 环绕通知。
    • 异常通知。
    • 最终通知。
  • 切面:把通知应用到切入点的动作叫做切面

准备工作

  • 引入一个Spring框架依赖,三个外部依赖。
  • 准备切入点表达式,方便知晓对哪个方法进行增强。
    • execution([],[],[]) execution(* com.dao.add) 表示所有权限,对com下的dao的add进行增强。若是表示对该包下所有方法和类增强,那么也可以用*代替。

AspectJ 注解

  • 创建一个最原始的类,在里面随便写一个需要增强的方法。

    package com.hyb.www.spring5.AOP.Annotation;public class User {    public void add(){        System.out.println("这个方法将要被增强!");    }}
    
  • 然后写一个增强类,设想这个类里的方法比add方法执行。

    package com.hyb.www.spring5.AOP.Annotation;public class UserProxy {    public void before(){        System.out.println("该方法在add方法之前执行。");    }}
    
  • 在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.hyb.www.spring5.AOP">            </context:component-scan></beans>
    

    注意,这里多了一个名称空间发生改变

  • 使用注解创建两个类的对象

    在两个类上加上@Component注解。

  • 在增强类上加入@Aspect注解

  • 在Spring文件中开启生成代理对象。

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    

    还是在原来的xml文件中、

  • 配置不同类型的通知。

    在增强类里,在作为通知方法上面添加通知类型注解,使用切入点表达式配置

    /** @before注解表示前置通知* */@Before(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")public void before(){    System.out.println("该方法在add方法之前执行。");}
    
  • 测试类测试

    @Testpublic void test(){    ApplicationContext context = new ClassPathXmlApplicationContext("Aop.xml");    User user = context.getBean("user", User.class);    user.add();}
    
  • 类似的,还有其他通知,和before类似。

    package com.hyb.www.spring5.AOP.Annotation;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component@Aspectpublic class UserProxy {    /*    * @before注解表示前置通知    * */    @Before(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")    public void before(){        System.out.println("该方法在add方法之前执行。");    }    /*    * 后置通知    * */    @After(value ="execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")    public void after(){        System.out.println("该方法在add方法之后执行。");    }    /*    * 异常通知    * 当方法出现异常时候的通知    * */    @AfterThrowing(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")    public void afterThrowing(){        System.out.println("异常通知。");    }    /*    * 返回通知    * 和后置通知一样,都是在方法之后执行    * 但是返回通知是在方法执行出返回结果之后才通知    * */    @AfterReturning(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")    public void afterReturning(){        System.out.println("这是返回通知!");    }    /*    * 环绕通知    * 在方法之前之后都会执行    * */    @Around(value = "execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")    public void Around(){        System.out.println("这是环绕通知!");    }}
    

切入点抽取

  • 我们可以看到,在上面的AspectJ中,很多注解上都有相同的value属性,也就是切入点,所以这里可进行切入点抽取。
  • 我们可以定义一个空方法,在上面放一个名为@pointcut的注解。
/** 切入点抽取* */@Pointcut(value ="execution(* com.hyb.www.spring5.AOP.Annotation.User.add())")public void pointCut(){}
  • 在其他注解方法里,我们就可以直接调用此方法就可以了
@Around(value = "pointCut()")

优先级

  • 若是有多个注解类,可以在类上加入@Order() 注解,括号里填入数字,数值越小,优先级越高,更先执行。

完全注解开发

  • 和上面的完全注解开发都一样,一样用注解来代替xml文件里的不同标签。
@Configuration@ComponentScan(basePackages = {"com.hyb.www.spring5"})@EnableAspectJAutoProxy(proxyTargetClass = true)public class ConfigAnnotation {}

JdbcTemplate

概念

  • 是Spring对JDBC的封装,用来更方便的操作数据库。

准备工作

  • 在对其操作前,需要引入几个相关的依赖

    spring-jdbc-5.2.9.RELEASE.jar
    spring-orm-5.2.9.RELEASE.jar
    spring-tx-5.2.9.RELEASE.jar

    druid-1.2.6.jar
    mysql-connector-java-8.0.26.jar

  • 在Spring配置文件中配置连接池

    prop.driverClass=com.mysql.cj.jdbc.Driverprop.url=jdbc:mysql://localhost:3306/hybprop.userName=rootprop.password=15717747056HYB
    
    <?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:property-placeholder location="druidSql.properties"></context:property-placeholder>    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">        <property name="driverClassName" value="${prop.driverClass}"></property>        <property name="url" value="${prop.url}"></property>        <property name="username" value="${prop.userName}"></property>        <property name="password" value="${prop.password}"></property>    </bean></beans>
    
  • 配置JdbcTemplate对象,注入DataSource

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">    <property name="dataSource" ref="dataSource">            </property></bean>
    
  • 创建Service类和Dao类,Service注入Dao,Dao中注入JDBCTemplate对象

    <!--    开启组件扫描-->    <context:component-scan base-package="com.hyb.www.spring5.JDBCT"></context:component-scan>
    
    public interface Dao {}
    
    @Repositorypublic class DaoImpl implements Dao{//    注入JDBCT    @Autowired    private JdbcTemplate jdbcTemplate;}
    
    @org.springframework.stereotype.Servicepublic class Service {//    注入Dao    @Autowired    private Dao dao;}
    

添加

  • 首先,在上面准备工作的所配置的数据库中随便创建一个表。

  • 第二步,根据这个表的属性创建一个类

    package com.hyb.www.spring5.JDBCADD;public class UserJ {    private String Jid;    private String Jname;    private String Jstatus;    public String getJid() {        return Jid;    }    public void setJid(String jid) {        Jid = jid;    }    public String getJname() {        return Jname;    }    public void setJname(String jname) {        Jname = jname;    }    public String getJstatus() {        return Jstatus;    }    public void setJstatus(String jstatus) {        Jstatus = jstatus;    }    @Override    public String toString() {        return "UserJ{" +                "Jid='" + Jid + '\'' +                ", Jname='" + Jname + '\'' +                ", Jstatus='" + Jstatus + '\'' +                '}';    }}
    
  • 然后在准备工作的Service中创建一个add方法

    package com.hyb.www.spring5.JDBCT.Service;import com.hyb.www.spring5.JDBCADD.UserJ;import com.hyb.www.spring5.JDBCT.DAO.Dao;import org.springframework.beans.factory.annotation.Autowired;@org.springframework.stereotype.Servicepublic class Service {//    注入Dao    @Autowired    private Dao dao;    public void add(UserJ userJ){        dao.add(userJ);    }}
    
  • 并且在对应Dao层实现操作数据库

    public interface Dao {    void add(UserJ userJ);}
    
    @Repositorypublic class DaoImpl implements Dao{//    注入JDBCT    @Autowired    private JdbcTemplate jdbcTemplate;    @Override    public void add(UserJ userJ) {        String sql="insert into userJ(Jid,Jname,Jstatus) values(?,?,?)";        jdbcTemplate.update(sql,userJ.getJid(),userJ.getJname(),userJ.getJstatus());    }    }
    
  • 类似的,更新,删除都是用jdbcTemplate 里的update方法。

  • 而查询返回某个值的方法,名为queryForObject(sql,Class class) 第二参数为返回值类型,若是要求返回String 就写String.clss 若是Integer类型就写Integer.class

  • 查询返回对象:即数据表的一行,queryForObject(sql,RowMapper rowMapper,Object …args) 第二个参数是Spring提供的一个封装返回类型的一个接口,咱们可以用其new BeanPropertyRowMapper<类名>(类名.class) 代替,中间是其实现类,后面是要封装在哪个类中。

  • 查询返回集合:query方法,返回List集合,参数和queryForObject一样,但是没有第三个参数

  • 批量添加:batchUpdate(sql,List<Object[] o>) 第二个参数代表将多行数据放在List集合中,而Object[] o代表存放一行数据的数组。这个数据的顺序要根据占位符来。

  • 批量修改,批量删除:和批量添加一样。就是存放数据的集合一定要按照占位符来存放。

事务

概念

  • 是数据库操作的基本单元,是一组操作,并且要求所有操作都得成功,整个事务才会成功。
  • 最常见的实例就是转账问题,有转入和转出两种操作,但要求使用事务来实现,不然若是中间出现了问题,会造成不必要的损失。
  • 事务有ACID特性,原子性(不可分割,各种操作互相联系),一致性(操作前后数据量不变),持久性(事务提交后数据是持久存在的),和隔离性(多事务操作不会产生影响)。

准备工作

搭建环境

  • 第一步:创建一个数据库表,分别有id,name,money属性,并且为其添加两条记录,都有1000块钱。

  • 第二步:和JdbcTemplate的准备工作一样

  • 第三步:在Dao层 创建两个方法,分别用来操作数据表中,某个人的钱财流动。(即进行数据表的更新操作)

  • 第四步:在Service层中,创建一个方法,调用Dao层的这两个方法。

  • 本质上气势就是JdbcTemplate中的更新操作,只不过这里有两个更新操作。只要会写sql语句就可以了。

  • 但这只是环境的搭建,理论这是可以了,如果转账过程出现了问题,钱财就会莫名的丢失,这肯定是不行,这便是事务诞生的使命。

事务操作

  • 根据JavaEE三层结构思想,一般将事务加到Service层上。

编程式

  • 用代码实现,基本不用,尤其麻烦

声明式

  • 声明式分别注解和xml配置,一般用注解方式进行。
注解方式
  • 底层使用AOP原理

  • 在Spring事务管理中,为不同框架提供了不同的接口实现类,但这些框架都共用一个接口,叫做事务管理器(PlatformTransactionManager)。

  • 所以第一步,我们得先创建事务管理器

    <!--    创建事务管理器,不同jdbc模板,管理不一样-->
        <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--        ref为上面的数据库连接部分-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
  • 再使用这个事务管理器:

    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
    
  • 在Service类上或者其中的方法上加上事务的注解

    • 在其类上,代表所有方法都使用该注解。
    • 而配置在方法上,只限定该方法。
    @Transactional
    public class Service{...}
    
参数配置
  • 在@Transactional 里可以填入很多参数

    • propagation :多事务方法之间调用时对事务的管理,称为事务的传播行为。

      比如有两个方法,a和b。

      事务的传播行为在Spring中一共有七种:

      REQUIRED :如果a方法本身有事务,调用了b方法后,b使用当前的a方法里的事务。如果a方法本身没有事务,那么调用了b方法后,会创建一个事务。该行为为propagation默认值。

      REQUIRED_NEW :无论a方法有没有事务,调用了b后,都会创建一个新的事务。

    • isolation:事务的隔离级别,是对多事务之间进行隔离,不然会出现脏读,不可重复读,虚读,幻读等影响。

      在mysql中,主要有四个隔离级别,每个级别都分别能解决不同的问题。

      事务隔离级别脏读不可重复读幻读
      读未提交(read-uncommitted)
      不可重复读(read-committed)
      可重复读(repeatable-read)
      串行化(serializable)
    • timeout:超时时间,事务要在一定时间内进行提交,超出时间让事务回滚。默认值为-1,表示不超时,以秒为单位。

    • readOnly:表示只读。默认值是false 表示可以进行一切操作,true表示只能查询。

    • rollbackFor:设置当出现哪些异常时进行实物回滚。

    • noRollbackFor:设置出现哪些异常不进行事务回滚。

xml方式(了解)
  • 首先,创建事务管理器

    <!--    创建事务管理器,不同jdbc模板,管理不一样-->    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <!--        ref为上面的数据库连接部分-->        <property name="dataSource" ref="dataSource"></property>    </bean>
    
  • 配置通知:

        <tx:advice id="txAdvice"><!--        配置事务参数,标签属性可以配置其隔离级别还有其他-->        <tx:attributes>            <tx:method name="方法名" 其他参数配置/>        </tx:attributes>    </tx:advice>
    
  • 配置切入点和切面

    <!--配置切入点和切面--><aop:config>    <!--配置切入点-->    <aop:pointcut id="p" expression="execution表达式"/>    <!--配置切面,配置到具体方法上-->    <aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor></aop:config>
    
完全注解开发
  • 创建配置类
package com.hyb.www.spring5.JDBCTransaction;

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.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration //配置类
@ComponentScan(basePackages = "com.hyb.www.spring5")
//开启事务
@EnableTransactionManagement
public class txConfig {
//    创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
//        prop.driverClass=com.mysql.cj.jdbc.Driver
//        prop.url=jdbc:mysql://localhost:3306/hyb
//        prop.userName=root
//        prop.password=15717747056HYB
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/hyb");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("15717747056HYB");
        return druidDataSource;
    }

//    创建JdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        /*
        * 下面的调用可以直接调用上面的方法,但是这样效率不太好,因为每次set都会创建一次
        * 因为上面调用一次就创建好了,且IOC容器了已经存在了这个DataSource对象,所以我们直接传参数过来就可以了。
        * */
        jdbcTemplate.setDataSource(dataSource);

        return jdbcTemplate;

    }

//    创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSourde){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSourde);
        return dataSourceTransactionManager;
    }
}

Spring5 新功能

日志

  • 自带了通用的日志封装,移除了日志封装Log4jConfigListener ,并用Log4j2进行了代替。

    • 使用前先导入四个jar包依赖

      ​ log4j-1.2-api-2.14.1.jar
      ​ log4j-core-2.14.1.jar
      ​ log4j-slf4j18-impl-2.14.1.jar

      ​ slf4j-api.jar

    • 创建名字为log4j2的xml文件

      <?xml version="1.0" encoding="UTF-8"?>
      <!--
          日志级别:
          OFF>FATAL>ERROR>WARN>INFO>DEBUG>TRACE(可以看到日志内部各种详细输出)>ALL...
      -->
      <Configuration status="INFO">
          <Appenders>
              <!--输出日志信息到控制台-->
              <console name="Console" target="SYSTEM_OUT">
                  <!--控制日志输出的格式-->
                  <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n">
      
                  </PatternLayout>
              </console>
          </Appenders>
          <Loggers>
              <!--root用于指定根目录,如果没有Loggers,使用root输出-->
              <root level="info">
                  <appender-ref ref="Console">
                      
                  </appender-ref>
              </root>
          </Loggers>
      </Configuration>
      

@NUllable注解

  • 可以使用在方法,属性,参数上,表示可以为空。

函数式风格

  • 支持Lambda表达式

  • 我们先创建一个User类,然后创建一个test

    package com.hyb.www.spring5.newSpring5;
    
    import org.junit.Test;
    import org.springframework.context.support.GenericApplicationContext;
    
    public class test {
        @Test
        public void testGenericApplicationContext(){
            GenericApplicationContext context = new GenericApplicationContext();
    //        清空内容
            context.refresh();
    //        注册,使用Lambda表达式
            context.registerBean("user",User.class, User::new);
            /*
            * getBean方法里的user是上面注册的时候设置的。
            * 若是上面注册的时候不设置,下面的name就要是类的包路径
            * */
            Object user = context.getBean("user");
            System.out.println(user);
        }
    }
    

Junit4

  • 主要对Junit4进行整合,首先要引入spring-test-5.2.9.RELEASE.jar

  • 创建测试类,用注解完成测试,这里我们测试前面事务的Service

    package com.hyb.www.spring5.Test;
    
    import com.hyb.www.spring5.JDBCTransaction.Service.Service;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    //注解测试固定注解
    @RunWith(SpringJUnit4ClassRunner.class)
    //引入配置文件
    @ContextConfiguration("classpath:JDBCT.xml")
    public class Annotation_Test {
    //    注入对象
        @Autowired
        private Service service;
    
        @Test
        public void test(){
            service.Account();
        }
    
    }
    

Junit5

  • 使用之前先导入Junit5的包,其他和Junit4一样,但是记住创建@Test的时候,要穿件Junit5的
package com.hyb.www.spring5.Test;


import com.hyb.www.spring5.JDBCTransaction.Service.Service;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:JDBCT.xml")
public class Junit5Test {

    @Autowired
    private Service service;

    @Test
    public void test(){
        service.Account();
    }

}

整合

  • 两个测试类上的两个注解,可以用一个注解来整合

    @SpringJunitConfig(location="xml文件路径")
    

SpringWebFlux

特点

  • 是Spring5新出的模块,用于web,功能SpringMVC类似,但是底层区别很大,是应对响应式编程而出现的框架。
  • 使用传统的web框架,比如SpringMVC,这些基于Servlet容器,而WebFlux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的。
  • 非阻塞式:在有限的资源下,提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程。
  • 函数式编程:基于Java8,可以使用函数式编程,即Lambda表达式。

和SpringMVC进行比较

  • 两个框架都可以使用注解方式,都运行在Tomcat服务器中,
  • SpringMVC采用命令式编程。

响应式编程 (RP)

  • 电子表格就是一种响应式编程,当一个表格的值发生变化,其他有关联的表格会立马发生变化。
  • 在java8 中提供了一个叫观察者的模式,其有两个实现类:Observer,Observable ,即观察其他是否发生变化,自己才是否反映。
  • 而到了java9,这两个类被Flow 取代,出现Reactor为基础的响应式编程。

Reactor

  • 响应式编程操作,Reactor是满足Reactive规范的框架。
  • 在Reactor中有两个核心类:Mono,Flux ,其都实现了接口Publisher,提供了丰富的操作符。
  • Flux实现发布者,返回多个元素,而Mono返回一个元素 或者 0个元素。两者都是数据流的发布者,都可以发出三种信号:元素值,异常信号,完成信号,后两个信号为终止信号,用于告诉订阅者数据流结束了。异常信号会终止,也会将错误信息传递给订阅者。
  • 三种信号的特点:错误信号和完成信号都代表终止,不能共存。如果没有发送任何元素值,而是直接发送信号,代表空流。而如果没有错误信号,也没有完成信号,则代表无限流。
  • 操作符:
    • Map 将元素映射成新的元素
    • flatMap 元素映射成流

举例

  • 创建一个SpringBoot项目,new Project->Spring initializr->next 选择java8版本。

  • 创建完毕后,修改pom.xml文件

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    

    其中version标签里的值,变为以上。

    <dependencies>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.1.5.RELEASE</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    在上面依赖标签中,增加一个新的以来,第一个dependcy为新的依赖。

    若是版本标红原因,请在setting里,选择File | Settings | Build, Execution, Deployment | Build Tools | Maven 勾选always update snapshots 然后返回页面,点击窗口右栏的Maven,先点击clean,再点击install,然后左上角的回旋按钮,刷新一段时间,退出。

  • 创建完xml文件,请看一个类

    package com.hyb.www.demoreactor;
    
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.lang.reflect.Array;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class TestReactor {
    
        public static void main(String[] args) {
    
            /*
            *下面只是发送,并没有订阅,当运行程序,只能输出一段日志
            * */
    
    
            /*
            * Flux返回多个元素
            * */
            Flux<Integer> flux = Flux.just(1, 2, 3, 4);
            /*
            * 返回单个元素
            * */
            Mono<Integer> mono = Mono.just(1);
    
            /*
            * 只有订阅了,数据流才会发出
            * */
            Flux.just("a","b").subscribe(System.out::println);
            Mono.just("ahahha").subscribe(System.out::println);
    //        a
    //        b
    //        ahahha
    
            /*
            * 还有其他方法
            * */
    
            /*数组*/
            Integer[] integer= {1,2,3,4};
            Flux.fromArray(integer);
            /*集合*/
            List asList = Arrays.asList(integer);
            Flux.fromIterable(asList);
            /*流*/
            Stream stream = asList.stream();
            Flux.fromStream(stream);
    
            Flux.error(new Exception("错误信号"));
    
        }
    }
    

基于注解编程模型

  • 首先,创建一个Springboot工程,然后,配置xml文件信息,配置与举例相似,只不过不要了加入依赖那步骤要换成另一个

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

    上面的要改成下面的。

    同样的,若是报红,可以像举例那样去解决。

  • 在application.properties下配置端口号

    server.port=8081
    
  • 创建一个包,entity,里面有一个类

    package com.hyb.www.demoreactor.entity;
    
    public class User {
        private Integer id;
        private String name;
    
        public User() {
        }
    
        public User(Integer id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
  • 再创建一个包,service 在本包下创建一个service 接口

    package com.hyb.www.demoreactor.service;
    
    import com.hyb.www.demoreactor.entity.User;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    public interface UserService {
        /*
        * 根据id查询用户,返回一个User
        * 可以使用Mono,返回一个或者0个元素
        * */
        Mono<User> queryUserById(Integer id);
    
        /*
        * 查询所有用户,返回多个User
        * 可以使用Flux
        * */
        Flux<User> queryAllUser();
    
        /*
        * 添加一个用户
        * 可以使用MOno,传入一个对象
        * */
        Mono<Void> addUser(Mono<User> user);
    }
    
  • 然后在service包下创建一个ServiceImpl包,在这个包里创建一个接口实现类

    package com.hyb.www.demoreactor.service.Impl;
    
    import com.hyb.www.demoreactor.entity.User;
    import com.hyb.www.demoreactor.service.UserService;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class UserServiceImpl implements UserService {
    
        /*
        * 这里应该操作数据库的
        * 但是为了方便,我们先在User里放一些固定的数据
        * 而这些固定的数据,我们可以通过其空参构造器放入
        * */
        private final Map<Integer,User> map=new HashMap<>();
    
        public UserServiceImpl() {
            this.map.put(1,new User(1,"hyb"));
            this.map.put(2,new User(2,"zyl"));
            this.map.put(3,new User(3,"hlf"));
        }
    
        @Override
        public Mono<User> queryUserById(Integer id) {
    //        该方法表示just返回可以为空
    //        参数代表这个类里的map属性里的通过id得到的对象
            return Mono.justOrEmpty(this.map.get(id));
        }
    
        @Override
        public Flux<User> queryAllUser() {
            return Flux.fromIterable(this.map.values());
        }
    
        @Override
        public Mono<Void> addUser(Mono<User> user) {
            /*
            * doOnNext代表遍历user
            * 然后用Lambda表达式将数据传入map中
            * 最后的函数运算,代表设置流为空,让流停止
            * 不然前面说过,会变成无限流
            * */
            return user.doOnNext(person->{
                Integer id=map.size();
                map.put(id,person);
            }).thenEmpty(Mono.empty());
        }
    }
    
  • 然后创建一个controller包,里面创建一个包

    package com.hyb.www.demoreactor.controller;
    
    import com.hyb.www.demoreactor.entity.User;
    import com.hyb.www.demoreactor.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    @RestController
    public class UserController {
    //    属性注入
        @Autowired
        private UserService userService;
    
    //    添加
        /*
        * 这个注解里,代表名字为user 传入id属性
        * 而在参数的注解里,会根据注解自动获取id属性
        * */
        @GetMapping("/user/{id}")
        public Mono<User> getUser(@PathVariable Integer id){
            return userService.queryUserById(id);
        }
    
    //    查询所有
        @GetMapping("/user")
        public Flux<User> getUsers(){
            return userService.queryAllUser();
        }
    
    //    添加一个
        /*
        * 参数里是SpringMVC里的内容
        * 代表获取一个JSon的注解
        * */
        @PostMapping("/saveuser")
        public Mono<Void> saveUser(@RequestBody User user){
            Mono<User> useMono = Mono.just(user);
            return userService.addUser(useMono);
        }
    }
    
  • 测试

    在这个工程项目下有一个这样的类,我们写好代码后,直接启动该类就可以了,DemoreactorApplication。

    启动成功后,控制台会显示出我们端口号是8081,这是之前设置的端口号。接下来,我们到浏览器,输入localhost:8081/user/1

    代表查询id为1的用户,而localhost:8081/user 代表查询所有用户

    同样的,若是版本标红原因或者出错,请在setting里,选择File | Settings | Build, Execution, Deployment | Build Tools | Maven 勾选always update snapshots 然后返回页面,点击窗口右栏的Maven,先点击clean,再点击install,然后左上角的回旋按钮,刷新一段时间,退出。

基于函数式编程模型

要求
  • 需要自己初始化服务器。
  • 有两个核心接口,一个是RouterFunction 用来请求转发,一个是HandlerFunction用来做响应。
  • 其对应Servlet中,对于request和response有两个类型,ServerRequest,ServerResponse。
步骤
  • 首先,在前面基于注解编程模型,我们删除controller,其余保留

  • 然后新建一个handler包,里面建一个类,名为UserHandler

    package com.hyb.www.demoreactor.handler;
    
    import com.hyb.www.demoreactor.entity.User;
    import com.hyb.www.demoreactor.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;
        }
    //    根据id查询
        public Mono<ServerResponse> getUserById(ServerRequest serverRequest){
    //        获取id
            String id = serverRequest.pathVariable("id");
    //        将id转化为int类型
            Integer i = Integer.valueOf(id);
    //        空值判断
            Mono<ServerResponse> noFound = ServerResponse.notFound().build();
    //        根据id查询
            Mono<User> userMono = this.userService.queryUserById(i);
            /*
            * 下面是将元素映射成流返回
            * 注意,flatMap里是Lambda表达式,
            * ok表示操作成功,然后设置返回类型是Json类型
            * body表示需要转换的数据
            * 最后的表示空值判断
            * */
    //        userMono.flatMap(person->ServerResponse.ok().bodyValue(person)).switchIfEmpty(noFound);
            return userMono.flatMap(person->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userMono,User.class)).switchIfEmpty(noFound);
        }
    //    查询所有
        public Mono<ServerResponse> getUsers(ServerRequest serverRequest){
    //        获取User
            Flux<User> users = this.userService.queryAllUser();
            return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
        }
    
    //    添加用户
        public Mono<ServerResponse> addUser(ServerRequest serverRequest){
    //        调用传来的数据转为Mono类型,因为我们传来的数据是一个,所有要转换成Mono类型
            Mono<User> userMono = serverRequest.bodyToMono(User.class);
    //        build表示订阅,只有订阅了,才会有响应,而有了响应,才会添加成功
            return ServerResponse.ok().build(this.userService.addUser(userMono));
        }
    
    
    }
    
  • 该类如同controller类一般,实现其中方法。

  • 其次,我们在这三个包的上一级,创建一个类Service

    package com.hyb.www.demoreactor;
    
    import com.hyb.www.demoreactor.handler.UserHandler;
    import com.hyb.www.demoreactor.service.Impl.UserServiceImpl;
    import com.hyb.www.demoreactor.service.UserService;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.HttpHandler;
    import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
    import org.springframework.web.reactive.function.server.RequestPredicate;
    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.GET;
    import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
    import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
    
    public class Service {
    //    创建路由
        public RouterFunction<ServerResponse> routerFunction(){
            UserService userService = new UserServiceImpl();
            UserHandler userHandler = new UserHandler(userService);
    //        设置路由
                /*
                * 设置路由为链式编程
                * GET为请求方式,里面是请求地址,将id放进去
                * accept为接受方式,为JSON形式
                * 后面是要调用查询方法的Lambda表达式
                * 最后andRoute是链式编程
                * */
            return RouterFunctions.route(
                    GET("/user/{id}").and(accept(APPLICATION_JSON)),
                    userHandler::getUserById).andRoute(GET("/user").and(accept(APPLICATION_JSON)),userHandler::getUsers);
        }
    
    //    创建服务器完成适配
        public void CreateReactorServer(){
    //        获取路由
            RouterFunction<ServerResponse> route = routerFunction();
    //        转发到http协议
            HttpHandler httpHandler = toHttpHandler(route);
    //        获取适配器对象
            ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);
    //        创建服务器
            HttpServer httpServer = HttpServer.create();
    //        完成适配
            httpServer.handle(reactorHttpHandlerAdapter).bindNow();
        }
    
    //    最终调用
        public static void main(String[] args) throws IOException {
            Service service = new Service();
            service.CreateReactorServer();
            System.out.println("enter out");
            System.in.read();
        }
    }
    
  • 启动main方法后,控制台最后会出现一段英文

    找到 L:/0:0:0:0:0:0:0:1:61970 最后面的几个数字就是你的端口号,在浏览器访问的时候可以localhost:端口号/ 浏览方式和基于注解编程模型一样,同样的,若是出现错误,首先用老方法解决一下,还是不行就是程序错误。记住,端口号是L开头才有,其余的不是。

使用WebClient浏览
  • 前面我们要测试的话,必须去浏览器浏览,但我们也可以在控制台浏览,但浏览之前,我们得首先启动,获取端口号。
package com.hyb.www.demoreactor;


import com.hyb.www.demoreactor.entity.User;
import com.hyb.www.demoreactor.service.Impl.UserServiceImpl;
import com.hyb.www.demoreactor.service.UserService;
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=WebClient.create("http://localhost:51401");
//   根据id查询
        /*
        * get为get请求,uri为请求地址
        * accept接受类型为JSon
        * 后面将其初始化
        * 然后将类转换为Mono
        * 最后是订阅
        * */
        User user = webClient.get().uri("/user/{id}", "1").accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();

//        查询所有
        /*
        * buffer()为缓冲数据流
        * doOnNext为遍历输出
        * blockFirst也类似订阅
        * */
        Flux<User> userFlux = webClient.get().uri("/user").accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);
        userFlux.map(User::toString).buffer().doOnNext(System.out::println).blockFirst();

    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值