前言

感谢黑马程序员

Spring概述

首先我们来看一张图片

Spring - 图1

这就是我们将要学习的SSM框架的基本架子,我们可以看到,由SpringMVC去处理视图层,Mybatis去处理持久化层,而Spring干什么呢?其实什么也不做,它不属于我们熟知的三层架构的任何一层,但其实是SSM中的核心。

Spring是什么

Spring,是Java全栈轻量级开发框架,现如今成为了最多的JavaEE企业应用开源框架

Spring官网:https://spring.io

Spring的核心

Spring的核心就两件事:IOC控制反转和AOP面向切面编程,基于这两个特性,可以大大简化我们编写JavaEE应用,实现快速开发

IOC控制反转是什么

  • 控制反转,就是将创建对象的活交给工厂区创建

打个比方,我现在要买个房子

以前的做法是:到处打听谁要买房,然后和具体的人去沟通协商,然后两家签订合同

现在的做法是:让中介去干这个活,我到最后只管签合同

AOP面向切面编程是什么

  • 面向切面编程,就是通过预编译和运行期动态代理的方式实现功能的维护

打个比方,比如现在我的程序从上到下已经全部开发完成了,现在有个需求说我要在哪个过程上加上一个新功能 这个时候我只需要在那个过程范围内添加一个新的功能即可 形象化来说,我们开发过程是从上到下竖向进行的,面向切面编程就是在这基础上横向添加

Spring的优势

  • 解耦
  • AOP
  • 声明式事务支持
  • 方便测试
  • 可以集成各种框架
  • 降低API使用难度
  • Spring源码是学习的范例

Spring的体系结构

Spring - 图2

问题分析:如何将程序简化

以前的使用案例

  1. package com.howling.Before.Dao;
  2. /**
  3. * 持久层接口
  4. */
  5. public interface AccountDao {
  6. void saveAccount();
  7. }
  1. package com.howling.Before.Dao;
  2. /**
  3. * 持久层
  4. */
  5. public class AccountDaoImpl implements AccountDao {
  6. public void saveAccount() {
  7. System.out.println("持久化层接口");
  8. }
  9. }
  1. package com.howling.Before.Service;
  2. /**
  3. * 业务层接口
  4. */
  5. public interface AccountService {
  6. void saveAccount();
  7. }
  1. package com.howling.Before.Service;
  2. import com.howling.Before.Dao.AccountDao;
  3. import com.howling.Before.Dao.AccountDaoImpl;
  4. /**
  5. * 业务层
  6. */
  7. public class AccountServiceImpl implements AccountService{
  8. private AccountDao accountDao = new AccountDaoImpl();
  9. public void saveAccount() {
  10. System.out.println("业务层调用持久化层");
  11. accountDao.saveAccount();
  12. }
  13. }
  1. package com.howling.Before;
  2. import com.howling.Before.Service.AccountService;
  3. import com.howling.Before.Service.AccountServiceImpl;
  4. public class Before {
  5. public static void main(String[] args) {
  6. AccountService accountService = new AccountServiceImpl();
  7. accountService.saveAccount();
  8. }
  9. }

Spring - 图3

以前做法的缺点分析

以前的做法耦合太严重,既然说到耦合,就说一下程序的耦合

耦合:程序之间的依赖关系,其中包括类和类之间的依赖关系,程序和程序之间的依赖关系

我们要做到解耦合,应该做到的程度是:编译器中看不到依赖,但是在运行中能够依赖。

使用工厂和配置文件来简化

工厂模式概述

工厂模式,属于23种设计模式中的一种,属于比较常使用的设计模式之一

工厂模式,顾名思义。

假如我们之前都是手工制品,现在我们要上流水线了。

说到工厂模式在提一嘴JavaBean,Bean这个词在英语单词中有豆子的意思,也有着可重用组件的含义。

之前我们一直说创建一个实体类,一个标准的实体类可以叫做JavaBean,其实JavaBean的含义是包含实体类的。

JavaBean>实体类,JavaBean是可重用组件的一部分


准备工作

1、编写持久化层和业务层

  1. package com.howling.FactoryDecoupl.Dao;
  2. /**
  3. * 持久层接口
  4. */
  5. public interface AccountDao {
  6. void saveAccount();
  7. }
  1. package com.howling.FactoryDecoupl.Dao;
  2. /**
  3. * 持久化层
  4. */
  5. public class AccountDaoImpl implements AccountDao {
  6. public void saveAccount() {
  7. System.out.println("持久化层");
  8. }
  9. }
package com.howling.FactoryDecoupl.service;

/**
 * 业务层接口
 */
public interface AccountService {
    void saveAccount();
}
package com.howling.FactoryDecoupl.service;

/**
 * 业务层
 */
public class AccountServiceImpl implements AccountService{
    public void saveAccount() {
        System.out.println("业务层");
    }
}

因为要使用工厂来创建,所以业务层没有调用持久化层,表现层根本没写

2、通过反射来创建对象,从而避免使用new关键字

  • bean.properties
# 在resources下面创建bean.prpperties
accountDao=com.howling.FactoryDecoupl.Dao.AccountDaoImpl
accountService=com.howling.FactoryDecoupl.service.AccountServiceImpl

使用工厂

创建Bean工厂

package com.howling.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 静态工厂
 */
public class BeanFactory {

    private static Properties properties;

    static {

        try {
            properties = new Properties();

            InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");

            properties.load(inputStream);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 工厂模式创建bean工厂
     *
     * @param beanName bean的名称
     * @return bean
     */
    public static Object getBean(String beanName) {

        Object bean = null;

        try {
            String property = properties.getProperty(beanName);

            bean = Class.forName(property).newInstance();

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return bean;
    }

    /**
     * 创建一个对应类型T的对象
     *
     * @param beanName bean的名称
     * @param tClass   类型参数
     * @param <T>      类型
     * @return 对象
     */
    public static <T> T getBean(String beanName, Class<T> tClass) {

        T bean = null;

        try {
            String property = properties.getProperty(beanName);

            bean = (T) Class.forName(property).newInstance();

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return bean;
    }
}
package com.howling;

public class People {
    public void eat() {
        System.out.println("吃饭");
    }
}
people=com.howling.People

现在我们可以来进行配置文件和工厂的测试了


工厂模式的问题

现在的工厂模式是不完美的

  • 多例:工厂每次启动都会产生不同的实例,每次调用的实例均不相同,这样会导致内存大幅消耗

思路:

为了解决这个问题,我们引入单例模式,单例模式也是设计模式中的一种

1、我们使用单例模式对工厂进行改造,这样工厂每次返回的实例都是一个

2、之前我们每次创建一个新的对象都是使用Class.forName(beanName).newInstance();来进行创建的,假如我们要返回一个实例,那么这个语句只能执行一次

3、语句执行一次创建对象之后,我们将初始化的值存储起来以作备用,以后就不用创建,直接返回即可

单例工厂

package com.howling.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class SingleBeanFactory {

    private static Properties properties;

    private static Map<String, Object> factories = new HashMap<>();

    static {

        try {
            properties = new Properties();
            InputStream inputStream = SingleBeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(inputStream);


            // 获得所有的Key
            Enumeration<Object> enumeration = properties.keys();

            // 根据key进行遍历,将所有的东西装载进工厂中
            while (enumeration.hasMoreElements()) {

                String key = enumeration.nextElement().toString();

                String property = properties.getProperty(key);

                Object o = Class.forName(property).newInstance();

                factories.put(key, o);
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得bean对象
     *
     * @param name bean对象的名称
     * @return bean对象
     */
    public static Object getBean(String name) {

        Object o = null;

        try {
            o = factories.get(name);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return o;
    }

    /**
     * 获得bean对象
     *
     * @param name   bean对象的名称
     * @param tClass bean对象类型
     * @param <T>
     * @return bean对象
     */
    public static <T> T getBean(String name, Class<T> tClass) {

        T o = null;

        try {
            o = (T) factories.get(name);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return o;
    }
}

懒加载静态单例工厂

我们可以看到,在上面的过程中,单例工厂是没有办法进行按需加载的,也就是说它必须要一次性加载完成,即使配置文件中存放有很多的类,所以我们必须要对他进行简化,让它懒加载

package com.howling.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class LazySingleBeanFactory {
    private static Properties properties;

    private static final Map<String, Object> factories = new HashMap<>();

    static {
        try {
            properties = new Properties();
            InputStream inputStream = LazySingleBeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static Object getBean(String name) {
        Object o;
        if ((o = factories.get(name)) != null) {
            return o;
        }
        try {
            String value = properties.getProperty(name);
            o = Class.forName(value).newInstance();
            factories.put(name, o);
            return o;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static <T> T getBean(String name, Class<T> tClass) {
        T o;
        if ((o = (T) factories.get(name)) != null) {
            return o;
        }
        try {
            String value = properties.getProperty(name);
            o = (T) Class.forName(value).newInstance();
            factories.put(name, o);
            return o;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Spring快速开始

IOC

接下来我们要用Spring去实现上面我们解决的耦合问题,快速入门来体会一下Spring的感觉

1、准备持久层和业务层

package com.howling.SpringDemo1.Dao;

/**
 * 持久层接口
 */
public interface AccountDao {
    void saveAccount();
}
package com.howling.SpringDemo1.Dao;

/**
 * 持久层
 */
public class AccountDaoImpl implements AccountDao {
    public void saveAccount() {
        System.out.println("持久化层接口");
    }
}
package com.howling.SpringDemo1.Service;

/**
 * 业务层接口
 */
public interface AccountService {
    void saveAccount();
}
package com.howling.SpringDemo1.Service;

import com.howling.SpringDemo1.Dao.AccountDao;
import com.howling.SpringDemo1.Dao.AccountDaoImpl;

/**
 * 业务层
 */
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao = new AccountDaoImpl();

    public void saveAccount() {
        System.out.println("业务层调用持久化层");
        accountDao.saveAccount();
    }
}

2、导入Spring的依赖

<!--设置Spring-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

3、准备bean对象的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">

    <!--把对象的创建交给spring来管理
        id:配置文件中的key值,与在讲工厂模式的时候的key值相同
        class:配置文件中的value值,是全限定类名,与在讲工厂模式的时候的value值相同
    -->
    <bean id="accountService" class="com.howling.SpringDemo1.Service.AccountServiceImpl"></bean>
    <bean id="accountDao" class="com.howling.SpringDemo1.Dao.AccountDaoImpl"></bean>

</beans>

4、使用Spring获取bean对象

  • 改造Service
package com.howling.SpringDemo1.Service;

import com.howling.SpringDemo1.Dao.AccountDao;
import com.howling.SpringDemo1.Dao.AccountDaoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 业务层
 */
public class AccountServiceImpl implements AccountService {

    private static AccountDao accountDao = null;

    static {
        //获取bean.xml
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");

        //根据key获取对象
        accountDao = applicationContext.getBean("accountDao", AccountDao.class);
    }

    public void saveAccount() {
        System.out.println("业务层调用持久化层");
        accountDao.saveAccount();
    }
}
  • 改造Controller
package com.howling.SpringDemo1;


import com.howling.SpringDemo1.Service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 控制层
 */
public class SpringDemo1 {
    public static void main(String[] args) {

        //获取bean.xml文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");

        //根据key获取value,如果不指定Class则需要强制转换
        AccountService accountService = (AccountService) applicationContext.getBean("accountService");

        accountService.saveAccount();

    }
}

5、查看结果

Spring - 图4

上面的快速开始就是Spring的IOC,控制反转,其实在作用上感觉非常像我们刚才写的单例工厂,不用怀疑,它就是。 学习Spring一定要把单例工厂弄个门清,至少要会手写单例工厂,让别人看看你的Spring学会了。


Application和三种构造方法

下面我们来分析一下ApplicationContext

Spring - 图5

上面是ApplicationContext的体系结构,ApplicationContext是一个接口

1、我们可以看到他继承了BeanFactory

BeanFactory是Spring容器的顶层接口,他下面的实现接口有很多,其中我们常常使用的是ApplicationContext

2、ApplicationContext有三个比较常用的实现类

  • ClassPathXmlApplicationContext:基于Xml的配置,刚才已经演示过了
  • FileSystemXmlApplicationContext:也是基于Xml的配置
  • AnnotationConfigApplicationContext:基于注解的配置

好的类和方法的命名让人一看就大体知道这个是干嘛的,比如 ClassPathXmlApplicationContext:根据ClassPath(类路径)的xml创建 FileSystemXmlApplicationContext:根据文件的xml来创建,但是必须要有访问权限 AnnotationConfigApplicationContext:基于注解配置来创建


BeanFactory和Application的两个加载策略

1、BeanFactory:是延迟加载,也就是说什么时候根据id加载了对象什么时候真正创建对象

这个其实想一下我们没有完成单例的工厂模式就很好理解了 所以这个模式下其实不是单例模式,每次调用工厂都会产出一个新的单例 BeanFactory主要是面向Spring本身

2、ApplicationContext:立即加载

这个就是我们改造完成之后的单例工厂了 每次调用工厂都是返回相同的实例 ApplicationContext主要是面向开发者

其实我推荐使用ApplicationContext,因为这个继承了BeanFactory和其他的接口,功能相对于BeanFactory更加强大。

但是Spring是一个非常牛逼的框架,他会根据你的配置进行更改。


Spring的XML配置详解

Spring的Bean细节

Spring中bean.xml属性

  • id:唯一标识
  • name:名字,可以指定多个名字,使用逗号,分号,空格分隔
  • class:映射的类,要用全类名
  • scope:作用范围
    • singleton:单例(默认值)
    • prototype:多例(常用)
    • request:作用于web应用的请求范围
    • session:作用于web应用的会话范围
    • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时他是session
  • factory-method:可以让我们自己写bean的工厂,指定我们的方法来创建,有两种形式
    • 静态方法
    • 普通方法,需要借助factory-bean来使用
  • factory-bean:指定工厂,说明这个类是工厂类

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

6、destory-method:销毁时要执行的方法

<bean id="" class="" factory-bean="" factory-method="" init-method="" destroy-method="" scope=""></bean>

这里只列出了部分,还有其他的后面都会一一讲到

三种创建Bean对象的方式

在上面的xml属性我们讲过了,其中可以看到,还有两种创建对象的属性:指定工厂和指定静态工厂

那么现在我们就有三种创建bean对象的方式了,在这里总结一下

1、根据默认的构造函数创建:默认使用。(这里简略写一下,反正前面都已经写过了)

  • 编写类
/**
 * 持久层
 */
public class AccountDaoImpl implements AccountDao {
    public void saveAccount() {
        System.out.println("持久化层接口");
    }
}
  • 指定spring的配置文件:bean.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="accountService" class="com.howling.SpringDemo1.Service.AccountServiceImpl"></bean>
    <bean id="accountDao" class="com.howling.SpringDemo1.Dao.AccountDaoImpl"></bean>

</beans>
  • Spring创建bean对象
/**
 * 业务层
 */
public class AccountServiceImpl implements AccountService {

    private static AccountDao accountDao = null;

    static {
        //获取bean.xml
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");

        //根据key获取对象
        accountDao = applicationContext.getBean("accountDao", AccountDao.class);
    }

    public void saveAccount() {
        System.out.println("业务层调用持久化层");
        accountDao.saveAccount();
    }
}

使用这种方式创建的bean对象会根据默认的构造函数进行创建,Spring直接通过反射给你创建一个

2、指定静态工厂创建

  • 指定静态工厂
package com.howling.SpringDemo2.factory;

import com.howling.SpringDemo2.Dao.AccountDaoImpl;
import com.howling.SpringDemo2.Service.AccountServiceImpl;

public class BeanFactory {
    public static AccountServiceImpl getAccountService() {
        return new AccountServiceImpl();
    }

    public static AccountDaoImpl getAccountDao() {
        return new AccountDaoImpl();
    }
}
  • 指定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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao" class="com.howling.SpringDemo2.factory.BeanFactory" 
          factory-method="getAccountDao"></bean>

    <bean id="accountService" class="com.howling.SpringDemo2.factory.BeanFactory" 
          factory-method="getAccountService"></bean>

</beans>

除了这两个地方,其他都不用动

3、指定工厂方法创建

  • 指定工厂
package com.howling.SpringDemo3.factory;

import com.howling.SpringDemo3.Dao.AccountDaoImpl;
import com.howling.SpringDemo3.Service.AccountServiceImpl;

public class BeanFactory {
    public AccountServiceImpl getAccountService() {
        return new AccountServiceImpl();
    }

    public AccountDaoImpl getAccountDao() {
        return new AccountDaoImpl();
    }
}
  • 指定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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--定义工厂-->
    <bean id="beanFactory" class="com.howling.SpringDemo3.factory.BeanFactory"></bean>

    <!--指定工厂和工厂中的方法-->
    <bean id="accountDao" factory-bean="beanFactory" factory-method="getAccountDao"></bean>
    <bean id="accountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

</beans>

现在可能有人要问了:这不是还是new出了对象了么,不还是耦合了么

原因在于:第二种和第三种的方式是解决jar包的方法的。jar包中的代码是.class文件不能更改,所以只能new

也就是说,要是你想通过Spring来获得jar包中的某个方法就可以使用第二种和第三种方式。

我们自己写的话直接使用第一种方式即可

作用范围

作用范围在bean.xml属性中也已经讲过了,简单来说就是配置一个scope属性即可

之前我们讲过BeanFactory和ApplicationContext的区别,说这两个一个是多例一个是单例,但是可以根据配置具体更改

这就是我们说过的那个配置

scope:作用范围

  • singleton:单例(默认值)
  • prototype:多例(常用)
  • request:作用于web应用的请求范围
  • session:作用于web应用的会话范围
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时他是session

生命周期

友情提示,在看生命周期的时候,多想想我们之前写过的两个工厂,然后结合理解

多例对象的生命周期

出生:当使用对象时,对象创建

活着:当使用的时候活着

死亡:GC垃圾回收,Spring不管

单例对象的生命周期

出生:容器创建时立刻出生

活着:只要容器还在就一直活着

死亡:容器销毁,对象死亡

也就是说,单例对象的生命周期和容器的生命周期是完全一致的,只不过我们一般发现不了,因为main函数执行完成之后内存直接就释放了,也就是说容器还没来得及调用销毁方法就已经被释放了内存

但是我们想要手动关闭也是可以的,Spring中提供了关闭的方法,但是有个注意点

我们一般使用的接口是ApplicationContext,但是Application中并没有关闭的方法,只有它的子类才有,所以假如想要调用关闭的方法,使用多态是不可以的,比如下面这种就不可以

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

在传统的Java中,我们对于Bean的管理也就是new一个出来,然后用完等待GC,但是Spring中要考虑的显然不止这些东西,它的加载顺序还是比较复杂的

1、Bean的实例化:Spring启动,扫描需要被Spring管理的Bean,进行Bean的实例化

2、依赖注入:Bean实例化之后,对Bean的引入和值注入到Bean的属性中

3、注入Aware:Spring会检测是否实现了xxxAware接口,并将对应的Aware接口注入给Bean

4、BeanPostProcessor:经过上面的几个步骤,bean对象已经构造,但是在这个情况下还没有被使用

接口BeanPostProcessor下面有两个函数

  • postProcessBeforeInitialzation:bean对象传进来,早于Initialzation,所以叫做前置处理,所有的Aware接口在这一步注入
  • postProcessAfterInitialzation:bean对象传进来,晚于Initialzation,所以叫后置处理

5、InitializingBean与init-method:前置处理之后会执行这个,可以在这一步添加一些代码的逻辑,但是它不会把bean对象传进来,所以不能处理对象本身

6、DisposableBean和destroy-method:可以在bean销毁前执行逻辑,但是bean不会传递进来

Spring依赖注入

依赖注入概述

什么叫做依赖注入

依赖注入:Dependency Injection

依赖注入的意思是:在当前类中需要用到其他类的对象,像这种依赖关系以后我们就都交给Spring去管理了,这种依赖关系的维护就叫做依赖注入

什么数据能够进行依赖注入

能够注入的数据分类三类

1、所有的基本类型和String

2、复杂类型(集合)

3、其他bean类型(在配置文件或者注解中配置过的bean)

注入的方式

依赖注入的方式有三种

1、构造函数

2、set方法

3、注解

经常要变化的数据是不适合注入的

构造函数注入

标签

构造函数的标签:<constructor-arg></constructor-arg>,其中有以下几个属性

  • type:要注入的类型
  • index:要注入数据的索引位置(构造函数上)
  • name:要注入数据的名称呢过
  • value:要注入数据的值
  • ref:要注入数据的bean引用

构造函数注入的快速起步:基本类型的注入

1、写一个类,里面包含有参数的构造函数

package com.howling.ConstructorInjection.service;

public interface AccountService {
    void saveAccount();
}
package com.howling.ConstructorInjection.service;

public class AccountServiceImpl implements AccountService {

    private String name;
    private Integer age;

    /**
     * 使用构造函数注入
     *
     * @param name name是String类型
     * @param age  age是Integer类型
     */
    public AccountServiceImpl(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public void saveAccount() {
        System.out.println(this.age + "岁的" + this.name + "saveAccount...");
    }
}

2、编写Spring的bean.xml

  • ConstructorInjectionDemo1.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">


    <!-- 1、首先使用一个bean对象指定对应的类,这个类初始化的时候需要带有参数 -->
    <bean id="accountService" class="com.howling.ConstructorInjection.service.AccountServiceImpl">
        <!-- 2、constructor-arg:构造函数注入方式
            name:要注入的数据名称是什么
            value:要制定基本类型和String类型的数据,可以自动转换,比如这个18会转为Integer
         -->
        <constructor-arg name="name" value="张三"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
    </bean>

</beans>

3、进行测试

package com.howling.ConstructorInjection;

import com.howling.ConstructorInjection.service.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("ConstructorInjectionDemo1.xml");

        AccountServiceImpl accountService = context.getBean("accountService", AccountServiceImpl.class);

        accountService.saveAccount();
    }
}

Spring - 图6

bean类型的注入

如果一个类A要依赖另一个类B,那么B首先要作为一个Spring管理的bean对象,然后才能被A引入,当然A也要成为一个bean对象

下面我们来做一个bean类型的注入

1、改造上面的类

package com.howling.ConstructorInjection.service;

import java.util.Date;

public class AccountServiceImpl implements AccountService {

    private String name;
    private Integer age;

    /**
     * 一个Date类,作为bean对象来注入
     */
    private Date birthday;

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public void saveAccount() {
        System.out.println(this.age + "岁的" + this.name + "saveAccount..." + "要过他" + birthday + "的生日");
    }
}

2、做一个bean对象

  • 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对象,Spring会使用反射初始化一个出来-->
    <bean id="time" class="java.util.Date"></bean>

    <bean id="accountService" class="com.howling.ConstructorInjection.service.AccountServiceImpl">
        <constructor-arg name="name" value="张三"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>

        <!-- name还是指定类中的值,但是bean对象不能指定value,要使用引用的方式 -->
        <constructor-arg name="birthday" ref="time"></constructor-arg>
    </bean>

</beans>

3、测试

Spring - 图7

可以看到,创建了一个Date对象

复杂类型的注入

复杂类型有这么几种

1、数组

2、集合

  • List
  • Map

3、Properties

所以我们根据数据的结构,分了两部分:

1、array和list

2、map和props

下面进行案例

1、改造类

package com.howling.ConstructorInjection.service;

import java.util.*;

public class AccountServiceImpl implements AccountService {

    private String[] strings;
    private List<String> list;
    private Map<String, Object> map;
    private Properties properties;

    public AccountServiceImpl(String[] strings, List<String> list, Map<String, Object> map, Properties properties) {
        this.strings = strings;
        this.list = list;
        this.map = map;
        this.properties = properties;
    }

    @Override
    public void saveAccount() {
        Arrays.stream(strings).forEach(System.out::print);

        System.out.println();
        list.forEach(System.out::print);

        System.out.println();
        map.keySet().forEach(System.out::print);

        System.out.println();
        properties.keySet().forEach(System.out::print);
    }
}

2、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="accountService" class="com.howling.ConstructorInjection.service.AccountServiceImpl">

        <!--参数1:数组:array-value-->
        <constructor-arg index="0">
            <array>
                <value>a1</value>
                <value>a2</value>
                <value>a3</value>
            </array>
        </constructor-arg>

        <!--参数2:列表:list-value-->
        <constructor-arg index="1">
            <list>
                <value>l1</value>
                <value>l2</value>
                <value>l3</value>
            </list>
        </constructor-arg>

        <!--参数3:map:map-entry(key-value)-->
        <constructor-arg index="2">
            <map>
                <entry key="m1" value="m1"/>
                <entry key="m2" value="m2"/>
                <entry key="m3" value="m3"/>
            </map>
        </constructor-arg>

        <!--参数4:properties-->
        <constructor-arg index="3">
            <props>
                <prop key="p1">1</prop>
                <prop key="p2">2</prop>
                <prop key="p3">3</prop>
            </props>
        </constructor-arg>
    </bean>

</beans>

3、测试

package com.howling.ConstructorInjection;

import com.howling.ConstructorInjection.service.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("ConstructorInjectionDemo1.xml");

        AccountServiceImpl accountService = context.getBean("accountService", AccountServiceImpl.class);

        accountService.saveAccount();
    }
}

Spring - 图8

set注入

Set注入概述

简单来说,就是将构造方法的注入转换为了set方法注入(不需要get方法)

标签

Set方法的标签是<property></property>,主要有这么几个属性

1、name:从set方法中得到的名字,比如:setName—>Name—>name

2、value:值

3、ref:注入数据的bean引用

没错,set注入直接就是property,而且set注入是最常用的一种注入方式

实例

1、代码改造

package com.howling.ConstructorInjection.service;

import java.util.*;

public class AccountServiceImpl implements AccountService {

    private String name;
    private Integer age;
    private Date birthday;
    private String[] strings;
    private List<String> list;
    private Map<String, Object> map;
    private Properties properties;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public void setStrings(String[] strings) {
        this.strings = strings;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public void saveAccount() {
        Arrays.stream(strings).forEach(System.out::print);

        System.out.println();
        list.forEach(System.out::print);

        System.out.println();
        map.keySet().forEach(System.out::print);

        System.out.println();
        properties.keySet().forEach(System.out::print);
    }
}

2、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="time" class="java.util.Date"></bean>

    <bean id="accountService" class="com.howling.ConstructorInjection.service.AccountServiceImpl">
        <!-- 1、基础类型和字符串 -->
        <property name="name" value="张三"></property>
        <property name="age" value="18"></property>

        <!--bean类型-->
        <property name="birthday" ref="time"></property>

        <!--复杂类型-->
        <property name="strings">
            <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </property>

        <property name="list">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>

        <property name="map">
            <map>
                <entry key="1" value="1"></entry>
                <entry key="2" value="2"></entry>
                <entry key="3" value="3"></entry>
            </map>
        </property>

        <property name="properties">
            <props>
                <prop key="1">1</prop>
                <prop key="2">2</prop>
                <prop key="3">3</prop>
            </props>
        </property>
    </bean>

</beans>

3、测试

package com.howling.ConstructorInjection;

import com.howling.ConstructorInjection.service.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("ConstructorInjectionDemo1.xml");

        AccountServiceImpl accountService = context.getBean("accountService", AccountServiceImpl.class);

        accountService.saveAccount();
    }
}

Spring - 图9


Spring的注解详解

注解概述

首先明确一件事情:注解的方式和xml的方式本质上没有什么区别

注解按照作用分类

  • 用于创建对象
  • 改变作用范围
  • 和生命周期相关
  • 用于注入数据

环境准备

首先我们需要告诉Spring:我要用注解。

回想一下我们学习的JavaWeb,我们使用注解的使用是首先在web.xml上填写使用注解的命名空间,在Spring中也是这么干,那么我们的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.howling"></context:component-scan>
</beans>

1、我们需要beans的命名空间 2、component-scan中的base-package告诉了Spring在创建容器要扫描com.howling下面的包 3、当扫描包的时候就会发现包中的类上的注解


创建对象的注解

注解说明

1、@Component:组件

2、@Controller:一般用于控制层(表现层)

3、@Service:一般用于业务层

4、@Repository:一般用于持久层

说明一下,其实上面这四个的作用都是一样的,但是分开命名就是让我们程序员开发的时候比较好理解,但是其实用啥都一样

属性说明

注解肯定要有属性,属性名就是value,属性值是唯一的,用于找到这个类

1、当不写value时,默认value是类的小驼峰形式

package com.howling.Annotation.service;

import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("bean的创建");
    }
}

这里的value就是accountServiceImpl

2、当自定义value时,就是你自己定义的值

package com.howling.Annotation.service;

import org.springframework.stereotype.Component;

@Component(value = "accountService")
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("bean的创建");
    }
}
package com.howling.Annotation.service;

import org.springframework.stereotype.Component;

@Component("accountService")
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("bean的创建");
    }
}

因为只有一个value赋值,所以value写不写都行

例子

package com.howling.Annotation.service;

import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("bean的创建");
    }
}
package com.howling.Annotation;

import com.howling.Annotation.service.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("SpringApplication.xml");

        AccountServiceImpl accountServiceImpl = context.getBean("accountServiceImpl", AccountServiceImpl.class);

        accountServiceImpl.saveAccount();
    }
}

作用范围

注解

@Scope:指定bean的作用范围

属性

value:指定范围的取值

  • singleton:单例,默认
  • prototype:多例

例子

package com.howling.Annotation.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("bean的创建");
    }
}

生命周期

注解

  • @PostConstruct:初始化要执行的方法
  • @PreDestory:销毁前要执行的方法

1、这俩都用在方法上 2、不能用多态来测试销毁方法,因为ApplicationContext没有这个方法,子类才有 3、多例模式下GC回收,close也没用 4、详情查看Spring的生命周期

package com.howling.Annotation.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("bean的创建");
    }

    @PostConstruct
    void init(){
        System.out.println("init");
    }

    @PreDestroy
    void destory(){
        System.out.println("destory");
    }
}
package com.howling.Annotation;

import com.howling.Annotation.service.AccountServiceImpl;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo1 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("SpringApplication.xml");

        AccountServiceImpl accountServiceImpl = context.getBean("accountServiceImpl", AccountServiceImpl.class);

        accountServiceImpl.saveAccount();

        context.close();
    }
}

Spring - 图10

注入数据

注解分类

  • @Autowried:先按照类型匹配,然后按照bean的id注入
  • @Qualifier:给类成员注入和@Autowried一起使用,给方法参数注入可以单独使用
  • @Resource:根据bean的id注入,不是Spring的注解
  • @Value:注入基本类型和String

@Autowried

这个注解会首先按照类型匹配,假如只有一个匹配的类型就按照那个类型注入,假如有多个匹配的类型就按照名称注入

而且@Autowried会调用的是无参构造,所以是这样的:类型—>名称—>无参构造

而且假如只有有参构造没有无参构造,它也没法调用,因为没有值给它调用

Spring - 图11

下面来进行情景一:只有一个继承类

package com.howling.Annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao{

    public void saveAccount() {
        System.out.println("继承1");
    }
}
package com.howling.Annotation.service;


import com.howling.Annotation.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao = null;

    public void saveAccount() {
        System.out.println("bean的创建");
    }
}

Spring - 图12


情景二:多个继承类

package com.howling.Annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao{

    public void saveAccount() {
        System.out.println("继承1");
    }
}
package com.howling.Annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl2 implements AccountDao {
    public void saveAccount() {
        System.out.println("继承2");
    }
}
package com.howling.Annotation.service;


import com.howling.Annotation.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao = null;

    public void saveAccount() {
        System.out.println("bean的创建");
    }
}

accountDao是名字,@Autowried也会根据这个名字去找对应的类,但是很遗憾找不到,所以注入不进去 在这里要注意一件事情,两个类都要去交给Spring管理,也就是说都要加上注解,要不然白写 Spring - 图13

情景三:有相同的bean名字

package com.howling.Annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao{

    public void saveAccount() {
        System.out.println("继承1");
    }
}
package com.howling.Annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl2 implements AccountDao {
    public void saveAccount() {
        System.out.println("继承2");
    }
}
package com.howling.Annotation.service;


import com.howling.Annotation.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDaoImpl = null;

    public void saveAccount() {
        System.out.println("bean的创建");
    }
}

Spring - 图14

@Qualifier

  • 给类成员注入的使用要和@Autowried配合使用,目的是为了消除歧义
  • 给方法参数注入可以单独使用,这里不讲,不好举例子

给类成员注入

我们之前看过@Autowried在多个继承类下要去找和属性名一致的bean的id,但是加入找不到就会报错

这个时候我们可以和@Qualifier配合,@Qualifier用于指定你要指定的bean的id

package com.howling.Annotation.service;


import com.howling.Annotation.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Autowired
    @Qualifier("accountDaoImpl")
    private AccountDao accountDao = null;

    public void saveAccount() {
        accountDao.saveAccount();
    }
}

这个时候假如有多个值,直接会找accountDaoImpl,即使有一个叫做accountDao的也会找accountDaoImpl

@Resource

@Resource其实就是@Autowried和@Qualifier的结合体,他直接根据bean的id注入,所以bean的id是必须写的

package com.howling.Annotation.service;


import com.howling.Annotation.dao.AccountDao;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class AccountServiceImpl implements AccountService {

    @Resource(name = "accountDaoImpl")
    private AccountDao accountDaoImpl2 = null;

    public void saveAccount() {
        accountDaoImpl2.saveAccount();
    }
}

@Value

@Value注解可以给属性赋值,有几种方式:

1、直接赋值

2、使用SpEL表达式赋值

3、读取配置文件赋值

下面依次来讲解(下面以基本类型来演示,但其实你完全可以使用复杂类型)

直接赋值

package com.howling.Annotation.service;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Value("18")
    private Integer age;

    public void saveAccount() {
        System.out.println(age);
    }
}

注意,这种方式可以直接写在属性上,有set方法也可以卸载set方法上

使用SpEL表达式赋值

package com.howling.Annotation.domain;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {

    private String username;

    @Value("张三")
    public void setUsername(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}
package com.howling.Annotation.service;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Value("#{user.username}")
    private String username;

    public void saveAccount() {
        System.out.println(username);
    }
}

通过SpEL表达式来进行其他类的读取,但是注意要有get方法,它是这样的user.username—>user.getUserName() 通过get方法来读取

读取配置文件赋值

1、首先需要在Spring的配置文件上加上一句话

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

    <!-- 表示读取classpath下面的application.properties文件 -->
    <context:property-placeholder location="classpath:application.properties">
    </context:property-placeholder>


    <context:component-scan base-package="com.howling"></context:component-scan>

</beans>

关于classpath在编译前后的对应关系我有一篇闲谈

2、application.properties文件

address=Asia/Shanghai

3、编写bean

package com.howling.Annotation.service;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements AccountService {

    @Value("${address}")
    private String address;

    public void saveAccount() {
        System.out.println(address);
    }
}

下面来看一下效果

Spring - 图15


其他注解

注解概述

1、@Configuration:指定配置类,主要作用是完全摆脱xml

2、@ComponentScan:指定创建容器时要扫描的包,在前面已经讲过了

3、@Bean:放在方法上,作用是将方法的返回值作为bean对象存到IOC容器里

4、@Import:在一个配置类中引入其他的配置类

5、@PropertySource:用于指定properties文件的路径,路径是编译后的classpath下的

6、@PropertySources:下面有多个@PropertySource

@Configuration

我们之前使用注解都是首先在配置文件上声明:我要用注解了,然后采用的注解。但是这种方式始终离不开xml注解。

我们在一开始讲过的创建IOC容器的时候说过,ApplicationContext由三个实现方法,我们之前一直用的是xml的方法,现在我们要换为注解的方法:new AnnotationConfigApplicationContext(配置类.class),使用这个方法就可以完全脱离xml了。

但是使用这个方法的配置就需要一个配置类,我们的配置类声明的注解为:@Configuration

下面来测试一下

1、编写配置类

package com.howling.config;

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

/**
 * @Configuration:加到类上,说明这个类是配置类
 * @ComponentScan("com.howling"):指定注解要扫描的包,和xml中指定的是一个道理
 */
@Configuration
@ComponentScan("com.howling")
public class SpringConfiguration {

}

2、编写Service

package com.howling.servlet;

import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("saveAccount");
    }
}

3、测试

package com.howling;

import com.howling.config.SpringConfiguration;
import com.howling.servlet.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringDemo4 {
    public static void main(String[] args) {
        ApplicationContext context = 
            new AnnotationConfigApplicationContext(SpringConfiguration.class);

        AccountServiceImpl accountServiceImpl = 
            context.getBean("accountServiceImpl", AccountServiceImpl.class);

        accountServiceImpl.saveAccount();
    }
}

Spring - 图16

4、其他说明

我们在controller中new AnnotationConfigApplicationContext(SpringConfiguration.class); 这一步已经将SpringConfiguration这个类的class字节码加载进去了,所以其实在这个类上不写@Configuration也是可以的 但是其他的情况是需要写的,比如配置类A中引用了配置类B,然后在创建的时候引入了配置类A。这个时候虽然引用的是配置类A,但是和上面不同,这里必须写上注解。 顺便说明一下,A调用B是有可能的,只需要在A中的注解改动一下:@Configuration(“要扫描的包”,”配置类2”) 为了我们的规范使用,建议不管怎么样都要加上@Configuration这个注解,但是看到没有加上注解也不要奇怪


@Import

在刚才,我们在最后说了配置类A调用配置类B是可以的,但是@Configuration(“要扫描的包”,”配置类2”),像这种方式是非常不好用的,我们有一种更好的方式,就是使用注解@Import(字节码)

使用这种方式,我们就可以在配置类A中引入配置类B,将其整合到一个配置中

package com.howling.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfigurationB {
}
package com.howling.config;

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

/**
 * @Configuration:加到类上,说明这个类是配置类
 * @ComponentScan("com.howling"):指定注解要扫描的包,和xml中指定的是一个道理
 */
@Configuration
@ComponentScan("com.howling")
@Import(ConfigurationB.class)
public class SpringConfiguration {

}

@PropertySource

关于这个注解,其实前面我们已经提了一嘴,就是可以指定properties文件的路径,只需要指定classpath下面的properties文件即可

关于classpath虽然说了很多遍但是还要再说一遍,在项目编译后java和resources目录下的文件都会到classes下面,这个classes就是我们说的classpath

举个例子

1、application.properties

address=Asia/Shanghai

2、配置类

package com.howling.config;

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

@Configuration
@PropertySource("classpath:application.properties")
public class ConfigurationB {
}
package com.howling.config;

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


@Configuration
@ComponentScan("com.howling")
@Import(ConfigurationB.class)
public class SpringConfiguration {

}

3、service

package com.howling.servlet;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {

    @Value("${address}")
    private String address;

    public void saveAccount() {
        System.out.println(address);
    }
}

4、演示

package com.howling;

import com.howling.config.SpringConfiguration;
import com.howling.servlet.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringDemo4 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);

        AccountServiceImpl accountServiceImpl = context.getBean("accountServiceImpl", AccountServiceImpl.class);

        accountServiceImpl.saveAccount();
    }
}

Spring - 图17

Spring整合Junit

Junit这么厉害是因为Junit集成了main方法,Junit不管我们是否采用了什么框架,他只会执行@Test注解上的内容

所以问题来了,Junit不知道我们使用了Spring,没法注入,所以我们需要想个办法,能让Junit在单元测试的时候能够注入

我们使用的方式是替换掉Junit中内置的main方法,这样就可以实现注入了

1、导入环境

        <!--替换Junit的main方法让Spring能够在单元测试中注入-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>

当使用Spring5.x的时候,Junit必须在4.12及以上,否则会报错

2、使用注解@RunWith(SpringJunit4ClassRunner.class)替换掉main方法

3、告知Spring运行器,Spring的IOC是基于xml还是基于注解,并且说明位置

  • locations:指定xml所在目录,加上classpath关键字表示在类路径下
  • classes:指定注解类所在的位置

4、测试

package howling;

import com.howling.servlet.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = com.howling.config.SpringConfiguration.class)
//@ContextConfiguration(locations = "classpath:SpringConfiguration.xml"):使用xml时
public class TestJunit {
    @Autowired
    private AccountService accountService;

    @Test
    public void account(){
        accountService.saveAccount();
    }
}

AOP

动态代理

动态代理概述

代理:在生活中,我们遇到了很多代理,比如买电脑买手机,找售后都是找代理。代理去代理厂家的产品,厂家卖的多,代理也抽成。

在Java中也有类似的概念,回想一下反射的内容,我们就很容易理解

动态代理:和代理差不多,不过更加高级一些

1、字节码随用随创建,随用随加载

2、不修改源码的基础上对方法继承增强

3、可以分类为:基于接口的动态代理,基于子类的动态代理

基于接口的动态代理

基于接口的动态代理,由JDK提供,需要Proxy类,这里是回顾前面讲过的反射的内容,更加具体的去看反射

1、首先我们来写厂家

package com.howling.proxy;

/**
 * 这里是厂家的接口,有一个方法是销售的方法
 */
public interface Producer {

    public void saleProducer(float money);
}
package com.howling.proxy;

/**
 * 厂家的实现
 */
public class ProducerImpl implements Producer {

    /**
     * 厂家进行销售
     *
     * @param money
     */
    public void saleProducer(float money) {
        System.out.println("厂家赚得:" + money + "元");
    }
}

2、我们来写代理和测试

package com.howling.proxy;

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

/**
 * 模拟消费者进行消费
 */
public class Client {
    public static void main(String[] args) {

        //必须是final修饰才可以被代理
        final ProducerImpl producerImpl = new ProducerImpl();

        //动态代理,也就是我们模拟的代理商
        Producer producer = (Producer) Proxy.newProxyInstance(producerImpl.getClass().getClassLoader(), producerImpl.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //假如当前执行的方法是saleProducer
                if ("saleProducer".equals(method.getName())) {
                    float money = (Float) args[0];//读取参数值

                    //代理商进行抽成
                    money *= 0.8;

                    System.out.println("代理商抽两成的利润");

                    //返回代理商的方法
                    return method.invoke(producerImpl, money);
                }

                return method.invoke(method,args);
            }
        });

        //消费者去代理商那里进行购买,花了一万块
        producer.saleProducer(10000f);
    }
}

Spring - 图18

3、下面来讲一下几个参数

Proxy.newProxyInstance(ClassLoader,Class[],InvocationHandler)

1、类加载器,被代理的类的类加载器 2、字节码数组,这个的作用是让代理对象和被代理的对象有相同的方法,所以需要被代理对象的父级接口 3、提供增强代码,使用InvocationHandler(proxy,method,args) 1、proxy:当前被代理对象的引用,也就是InvocationHandler本身 2、method:当前方法的名称 3、args:当前执行的方法中的参数数组

基于子类的动态代理

基于子类的动态代理,我们需要一个外部的包CGLIB,他是一个功能强,性能高的代码生成包。

主要是为没有实现接口的类实现代理,算是对JDK代理的一个很好的补充。

1、依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_3</version>
        </dependency>

2、编写生产者,这里就不需要写接口了

package com.howling.cglib;

/**
 * 生产者
 */
public class Producer {

    /**
     * 实现销售方法
     * @param money
     */
    public void saleProducer(float money){
        System.out.println("销售"+money+"...");
    }
}

3、编写消费者

package com.howling.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 模拟消费者
 */
public class Client {


    public static void main(String[] args) {


        final Producer producer = new Producer();

        Producer proxyProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                if ("saleProducer".equals(method.getName())){
                    float money = (Float) objects[0];
                    return method.invoke(producer,money*0.8f);
                }
                return null;
            }
        });

        proxyProducer.saleProducer(10000f);

    }

}

4、结果

Spring - 图19

AOP概述

1、AOP:面向切面编程,通过预编译和运行期动态代理实现程序功能统一维护的技术

2、AOP是OOP(面向对象)的延伸,是软件开发的一个热点

3、AOP是Spring中的一个重要内容,是函数式编程的一种衍生模型

4、利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑的各个部分之间的耦合度降低,提高程序的可重用性

优势:减少重复代码,提高开发效率,维护方便


AOP有以下术语

1、连接点:JoinPoint:所谓连接点是那些被拦截到的点,在Spring中是方法,因为Spring只支持方法类型的连接点

也就是我们所说的要增强的方法

2、切入点:Pointcut:所为切入点是指我们要对哪些连接点进行拦截的定义

在事务中被增强的方法就叫做切入点 注意连接点和切入点的区别,连接点指的就是原来定义的方法,切入点指的是我们已经明确进行拦截的方法

3、通知:Active:拦截到连接点之后要做的事情

通知也就是指的增强,通知分为:前置通知,后置通知,异常通知,最终通知 现在想一下我们在JDK动态代理中的的invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

     //假如当前执行的方法是saleProducer
     if ("saleProducer".equals(method.getName())) {
           float money = (Float) args[0];//读取参数值

           //代理商进行抽成
           money *= 0.8;

           System.out.println("代理商抽两成的利润");

           //返回代理商的方法
           return method.invoke(producerImpl, money);
     }

     return method.invoke(method,args);
}

method.invoke(producerImpl, money)这段代码也就是我们最终要执行的方法,也就是我们的切入点,看好这个切入点 1、在切入点之前的代码的都是前置通知 2、在切入点之后的都是后置通知 3、出现异常,被捕获的都叫做异常通知 4、不论如何都要执行的就是最终通知 5、整个invoke方法就是环绕通知


如图: Spring - 图20

4、引介:Introduction:引介是一种特殊的通知,在不改变类代码的前提下,引介可以在运行期为类动态地添加一些方法或者成员变量

5、目标对象:Target:代理的目标对象

6、织入:Weaving:指把增强应用到目标对象来创建新的代理对象的过程,Spring使用动态代理,而AspectJ使用编译器和类装载器

7、代理:Proxy:一个类被织入之后,就会产生一个结果代理类

8、切面:Aspect:切入点+通知=切面


AOP其实非常简单,我们只需要

1、编写核心业务代码

2、抽取公共代码,制作为通知

3、在配置文件中声明切入点和通知的关系(切面)

基于XML的AOP配置

快速起步

1、pom.xml

        <!--Spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <!--用于解析切入点表达式-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

2、service

package com.howling.aop.service;

/**
 * 对账户的操作
 */
public interface AccountService {

    /**
     * 模拟保存操作
     */
    void saveAccount();

    /**
     * 模拟更新操作
     *
     * @param i
     */
    void updateAccount(int i);

    /**
     * 模拟删除操作
     *
     * @return
     */
    int deleteAccount();
}
package com.howling.aop.service;

public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("用户已保存");
    }

    public void updateAccount(int i) {
        System.out.println("用户已更新" + i);
    }

    public int deleteAccount() {
        System.out.println("用户已删除");
        return 0;
    }
}

3、通知

package com.howling.aop.utils;

/**
 * 模拟日志记录的工具类,一会就会将此方法进行织入
 */
public class Logger {

    public void printLogger() {
        System.out.println("日志已经开始记录...");
    }
}

4、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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--注意,上面是新的规范-->

    <!-- 首先将我们的Target交给Spring管理 -->
    <bean id="accountService" class="com.howling.aop.service.AccountServiceImpl"></bean>


    <!--将Logger也交给Spring管理-->
    <bean id="logger" class="com.howling.aop.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面,引用通知方法-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的方式,指定被增强的方法-->
            <aop:before method="printLogger" pointcut="execution(public void com.howling.aop.service.AccountServiceImpl.saveAccount())">
            </aop:before>
        </aop:aspect>
    </aop:config>

    <!--我们需要使用AspectJ来解析切入点表达式,所以我们需要AspectJ的支持-->
    <aop:aspectj-autoproxy  proxy-target-class="true"/>
</beans>

5、测试

package com.howling.aop;

import com.howling.aop.service.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
    public static void main(String[] args) {


        ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml");


        AccountServiceImpl accountServiceImpl = context.getBean("accountService", AccountServiceImpl.class);

        accountServiceImpl.saveAccount();

    }
}

Spring - 图21


切入点表达式

之前我们在快速起步的时候曾经见过切入点表达式

<aop:config>
    <aop:aspect id="logAdvice" ref="logger">
        <aop:before method="printLogger" pointcut="execution(public void com.howling.aop.service.AccountServiceImpl.saveAccount())">
        </aop:before>
    </aop:aspect>
</aop:config>

pointcut="execution(public void com.howling.aop.service.AccountServiceImpl.saveAccount())"

准确的说这并不是切入点表达式,只是指定了这个方法的访问修饰符,返回值,全类名,参数等等

这样的写法在开发中肯定是不可以的,接下来就是切入点表达式

1、访问修饰符可以省略

void com.howling.aop.service.AccountServiceImpl.saveAccount()

2、所有的返回值可以使用星号来表示

* com.howling.aop.service.AccountServiceImpl.saveAccount()

3、包名可以使用*来代替,但是有几级包就要写几个*

* *.*.*.*.AccountServiceImpl.saveAccount()

4、如果有多个子包,可以使用..代替当前包和子包

* *..AccountServiceImpl.saveAccount()

5、类名可以使用*来代替

* *..*.saveAccount()

6、方法名可以使用*来代替

* *..*.*

7、基本类型的参数可以直接写名称,引用类型需要全类名

* *..*.*(int,java.lang.String)

8、参数可以使用*来代替,但是必须要有参数才能够使用*

* *..*.*(*)

9、参数可以使用..来代替有参数或者无参数,参数可以为任意类型

* *..*.*(..)

所以最终可以简化为

* *..*.*(..)

注意:我们在开发过程中不建议使用全通配的表示方式,建议是切换到业务层底下再使用通配

* com.howling.aop.service.*.*(..)

四种常用的通知类型

  • 前置通知
  • 后置通知
  • 异常通知
  • 最终通知

  • Logger
package com.bean.utils;

/**
 * 模拟用于记录日志的工具类,里面提供了公共代码
 */
public class Logger {

    /**
     * 前置通知
     */
    public void beforePrintLog(){
        System.out.println("Logger类中的前置通知方法开始记录日志了。。。。。。");
    }

    /**
     * 后置通知通知
     */
    public void afterReturningPrintLog(){
        System.out.println("Logger类中的后置通知开始记录日志了。。。。。。");
    }

    /**
     * 异常通知
     */
    public void afterThrowsPrintLog(){
        System.out.println("Logger类中的异常通知方法开始记录日志了。。。。。。");
    }

    /**
     * 最终通知
     */
    public void afterPrintLog(){
        System.out.println("Logger类中的最终通知方法开始记录日志了。。。。。。");
    }

}
  • bean.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
  <!--注意,上面是新的规范-->

    <!--配置spring的IOC,把service对象配置进来
        我们想要对service方法进行增强,使service中执行任意一个方法前都执行一个日志
    -->
    <bean id="accountService" class="com.bean.service.impl.AccountServiceImpl"></bean>


    <!--我们有这个通知类,通知类就是记录日志,我们也交给spring-->
    <bean id="logger" class="com.bean.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面,引用通知方法-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置前置通知-->
            <aop:before method="beforePrintLog" pointcut="execution(* com.bean.service.impl.*.*(..))"></aop:before>

            <!--配置后置通知-->
            <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.bean.service.impl.*.*(..))"></aop:after-returning>

            <!--&lt;!&ndash;异常通知&ndash;&gt;-->
            <aop:after-throwing method="afterThrowsPrintLog" pointcut="execution(* com.bean.service.impl.*.*(..))"></aop:after-throwing>

            <!--&lt;!&ndash;最终通知&ndash;&gt;-->
            <aop:after method="afterPrintLog" pointcut="execution(* com.bean.service.impl.*.*(..))"></aop:after>
        </aop:aspect>
    </aop:config>


</beans>
  • AOPTest
package com.bean.test;

import com.bean.service.IAccountService;
import com.bean.service.impl.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        IAccountService accountService = (IAccountService) context.getBean("accountService");

        accountService.saveAccount();
    }
}

使用标签配置切入点表达式

<aop:pointcut></<aop:pointcut>

  • id
  • expression

此标签可以写在<aop:aspect></aop:aspect>里面,那么只能在这里面使用,再来一个切面要重新配 所以我们把它挪到外面挪到外面之后可能发现会报错,所以注意这个东西必须在aop:aspect之前,因为这是约束。 不然就会报错,一定要注意


  • bean.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
  <!--注意,上面是新的规范-->

    <!--配置spring的IOC,把service对象配置进来
        我们想要对service方法进行增强,使service中执行任意一个方法前都执行一个日志
    -->
    <bean id="accountService" class="com.bean.service.impl.AccountServiceImpl"></bean>


    <!--我们有这个通知类,通知类就是记录日志,我们也交给spring-->
    <bean id="logger" class="com.bean.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面表达式-->
        <aop:pointcut id="pointCut" expression="execution(* com.bean.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面,引用通知方法-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置前置通知,引用切面表达式-->
            <aop:before method="beforePrintLog" pointcut-ref="pointCut" ></aop:before>

            <!--配置后置通知,引用切面表达式-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pointCut"></aop:after-returning>

            <!--异常通知,引用切面表达式;-->
            <aop:after-throwing method="afterThrowsPrintLog" pointcut-ref="pointCut"></aop:after-throwing>

            <!--最终通知,引用切面表达式-->
            <aop:after method="afterPrintLog" pointcut-ref="pointCut"></aop:after>
        </aop:aspect>
    </aop:config>


</beans>

Spring中的环绕通知

  • 环绕通知

Spring - 图22

这个是我们前面讲的基于动态代理的通知

  • 我们发现环绕通知就是这整个方法
  • 里面包含着
    • 前置通知
    • 方法调用
    • 后置通知
    • 异常通知
    • 最终通知

所以环绕通知在Spring中的地位非同一般

首先我们需要注意几件事:

  1. 既然环绕通知包含了这些东西,那么也就代表着在spring中可以在环绕通知中配置其他的通知
  2. 既然上面的动态代理图片中有明确的方法调用,所以在环绕通知中也应该进行方法调用,要不然就不会进行方法执行

掌握了以上几件事,我们开始敲代码

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


    <bean id="accountService" class="com.bean.service.impl.AccountServiceImpl"></bean>


    <bean id="logger" class="com.bean.utils.Logger"></bean>

    <aop:config>

        <aop:pointcut id="pointCut" expression="execution(* com.bean.service.impl.*.*(..))"></aop:pointcut>

        <aop:aspect id="logAdvice" ref="logger">
            <!--只配置了一个环绕通知,环绕通知的标签就是<aop:around></aop:around>-->
            <aop:around method="aroudnPrintLog" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
    </aop:config>


</beans>
  • Logger
package com.bean.utils;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 模拟用于记录日志的工具类,里面提供了公共代码
 */
public class Logger {

    /*
    * 我们刚才在事项里面说到
    *   1. 环绕通知必须要进行方法调用,否则方法不会执行
    *   2. 观看之前我们写的基于动态代理执行的方法,我们也可以进行其他四种通知的调用
    *   3. 我们也要有返回值
    *
    *    既然要进行方法调用,就要有参数
    *       ProceedingJoinPoint就是参数,用于获取方法
    *           - proceed():参数下面有一个方法proceed(),这个就相当于明确调用切入点方法
    *           - getArgs():用于获取切入点点方法的参数
    * */
    public Object aroudnPrintLog(ProceedingJoinPoint proceedingJoinPoint){

        Object returnValue = null;
        try {
            System.out.println("这叫前置通知");
            returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());   //这叫切入点方法调用
            System.out.println("这叫后置通知");
        } catch (Throwable throwable) {//注意这里使用的是Throwable,因为Exception拦不住它
            System.out.println("这叫异常通知");
            throwable.printStackTrace();
        }finally {
            System.out.println("这叫最终通知");
        }
        return returnValue;
    }
}

基于注解的AOP配置

快速起步

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

2、加入bean对象并且配置注解

  • service
package com.bean.service.impl;

import com.bean.service.IAccountService;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements IAccountService {
    public void saveAccount() {
        System.out.println("保存方法执行了");
    }

    public void updateAccount(int i) {
        System.out.println("更新方法执行了"+i);
    }

    public int deleteAccount() {
        System.out.println("删除方法执行了");
        return 0;
    }
}
  • Logger
package com.bean.utils;

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

/**
 * 模拟用于记录日志的工具类,里面提供了公共代码
 */
@Component//加入bean
@Aspect//注意,这里制定了这个Logger类是一个切面类
public class Logger {


    @Pointcut("execution(* com.bean.service.impl.*.*(..))")//配置切面表达式,id就是方法名称
    public void AspectPointCut(){};

    /**
     * 前置通知
     */
    @Before("AspectPointCut()")//指定前置通知,注意看好包,配置了切面表达式,注意一定要加括号
    public void beforePrintLog(){
        System.out.println("Logger类中的前置通知方法开始记录日志了。。。。。。");
    }

    /**
     * 后置通知通知
     */
    @AfterReturning("AspectPointCut()")//指定后置通知,配置切面表达式,注意加括号
    public void afterReturningPrintLog(){
        System.out.println("Logger类中的后置通知开始记录日志了。。。。。。");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("AspectPointCut()")//指定异常通知,配置切面表达式,注意加括号
    public void afterThrowsPrintLog(){
        System.out.println("Logger类中的异常通知方法开始记录日志了。。。。。。");
    }

    /**
     * 最终通知
     */
    @After("AspectPointCut()")//指定最终通知,配置切面表达式,注意加括号
    public void afterPrintLog(){
        System.out.println("Logger类中的最终通知方法开始记录日志了。。。。。。");
    }


    //环绕通知
    @Around("AspectPointCut()")//指定环绕通知,配置切面表达式,注意加括号
    public Object aroudnPrintLog(ProceedingJoinPoint proceedingJoinPoint){
        Object returnValue = null;
        try {
            System.out.println("这叫前置通知");
            returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());   //这叫切入点方法调用
            System.out.println("这叫后置通知");
        } catch (Throwable throwable) {//注意这里使用的是Throwable,因为Exception拦不住它
            System.out.println("这叫异常通知");
            throwable.printStackTrace();
        }finally {
            System.out.println("这叫最终通知");
        }
        return returnValue;
    }

}
  • AOPTest
package com.bean.test;

import com.bean.service.IAccountService;
import com.bean.service.impl.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        IAccountService accountService  = (IAccountService) context.getBean("accountServiceImpl");

        accountService.saveAccount();
    }
}

AOP注解中的问题,实际开发中应该怎么做

其实虽然注解好用,但是我不得不告诉你,Spring在完全使用注解方式执行AOP的时候会出现问题,就是顺序调用问题,比如下面的

Logger类中的前置通知方法开始记录日志了。。。。。。
保存方法执行了
Logger类中的最终通知方法开始记录日志了。。。。。。
Logger类中的后置通知开始记录日志了。。。。。。
  • 这是不使用环绕通知的时候进行的测试,代码写的没有问题,但是调用顺序出错了,其实是Spring有问题
  • 调用顺序为:前置通知—>最终通知—>后置通知
  • 所以假如使用注解,可能会出现问题

所以在这个方法下,采用半注解半代码的形式来配置(注解环绕通知)或者使用xml的形式,便可以避免调用顺序出错的问题


使用纯注解

1、首先看一下前面的不使用xml的时候使用的配置类

2、然后在配置类上加上一个@EnableAspectJAutoProxy来配置好切面类

3、下面是一个例子

Spring - 图23

  • java.config.SpringConfiguration
package config;

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

@Configuration
@ComponentScan("com.bean")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
  • java.com.bean.service.impl.AccountServiceImpl
package com.bean.service.impl;

import com.bean.service.IAccountService;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements IAccountService {
    public void saveAccount() {
        System.out.println("保存方法执行了");
    }

    public void updateAccount(int i) {
        System.out.println("更新方法执行了"+i);
    }

    public int deleteAccount() {
        System.out.println("删除方法执行了");
        return 0;
    }
}
  • java.com.bean.utils.Logger
package com.bean.utils;

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

/**
 * 模拟用于记录日志的工具类,里面提供了公共代码
 */
@Component//加入bean
@Aspect//注意,这里制定了这个Logger类是一个切面类
public class Logger {


    @Pointcut("execution(* com.bean.service.impl.*.*(..))")//配置切面表达式,id就是方法名称
    public void AspectPointCut(){};

    /**
     * 前置通知
     */
    @Before("AspectPointCut()")//指定前置通知,注意看好包,配置了切面表达式,注意一定要加括号
    public void beforePrintLog(){
        System.out.println("Logger类中的前置通知方法开始记录日志了。。。。。。");
    }

    /**
     * 后置通知通知
     */
    @AfterReturning("AspectPointCut()")//指定后置通知,配置切面表达式,注意加括号
    public void afterReturningPrintLog(){
        System.out.println("Logger类中的后置通知开始记录日志了。。。。。。");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("AspectPointCut()")//指定异常通知,配置切面表达式,注意加括号
    public void afterThrowsPrintLog(){
        System.out.println("Logger类中的异常通知方法开始记录日志了。。。。。。");
    }

    /**
     * 最终通知
     */
    @After("AspectPointCut()")//指定最终通知,配置切面表达式,注意加括号
    public void afterPrintLog(){
        System.out.println("Logger类中的最终通知方法开始记录日志了。。。。。。");
    }


    //环绕通知
    @Around("AspectPointCut()")//指定环绕通知,配置切面表达式,注意加括号
    public Object aroudnPrintLog(ProceedingJoinPoint proceedingJoinPoint){
        Object returnValue = null;
        try {
            System.out.println("这叫前置通知");
            returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());   //这叫切入点方法调用
            System.out.println("这叫后置通知");
        } catch (Throwable throwable) {//注意这里使用的是Throwable,因为Exception拦不住它
            System.out.println("这叫异常通知");
            throwable.printStackTrace();
        }finally {
            System.out.println("这叫最终通知");
        }
        return returnValue;
    }

}
  • test.com.bean.test.AOPTest
package com.bean.test;

import com.bean.service.IAccountService;
import com.bean.service.impl.AccountServiceImpl;
import config.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);

        IAccountService accountService  = (IAccountService) context.getBean("accountServiceImpl");

        accountService.saveAccount();
    }
}

Spring中的JDBCTemplate

基本概述

Spring - 图24

上面这张图是持久层总图,我们今天的主角是JdbcTemplate,可以看到对JDBC进行了薄薄封装

它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。

  • 操作关系型数据的:
    • JdbcTemplate
    • HibernateTemplate
  • 操作 nosql 数据库的:
    • RedisTemplate
  • 操作消息队列的:
    • JmsTemplate
  • 我们今天的主角在spring-jdbc-5.0.2.RELEASE.jar
  • 还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
  • 不可避免地要导入数据库驱动

JDBCTemplate的作用

与数据库进行交互,实现对表的CRUD

如何创建该对象

原始方式

1、依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bean</groupId>
    <artifactId>SpringAOP</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>
</project>

2、表

create table account(
    id int primary key auto_increment,
    name varchar(40),
    money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

3、实体类

package com.bean.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Float money;

    public Account() {
    }

    public Account(Integer id, String name, Float money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

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

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

4、使用JDBCTemplate

package com.bean.jdbcTemplate;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class JdbcTemplateDemo {
    public static void main(String[] args) {

        //准备数据源:JdbcTemplate内置数据源
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        //JdbcTemplate
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        //设置数据源
        jdbcTemplate.setDataSource(dataSource);

        jdbcTemplate.execute("select * from account");

    }
}

使用Spring来创建

XML创建

1、bean.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

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

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

</beans>

2、JDBCTemplateDemo

package com.bean.jdbcTemplate;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class JdbcTemplateDemo {
    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        JdbcTemplate jdbcTemplate = (JdbcTemplate)context.getBean("jdbcTemplate");

        jdbcTemplate.execute("insert into account (name,money) values ('dd',1000)");

    }
}

注解创建

1、注解配置

  • config.SpringConfiguration
package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@ComponentScan("com.bean")
public class SpringConfiguration {

    @Bean
    public JdbcTemplate jdbcTemplateBean(){
        return new JdbcTemplate(dataSourceBean());
    }

    @Bean
    public DriverManagerDataSource dataSourceBean(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }


}
  • JDBCTemplateDemo
package com.bean.jdbcTemplate;

import config.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class JdbcTemplateDemo {
    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);

        JdbcTemplate jdbcTemplate = (JdbcTemplate)context.getBean("jdbcTemplateBean");

        jdbcTemplate.execute("insert into account (name,money) values ('dd',1000)");

    }
}

注解不能和xml一起,否则报错,前面已经讲过了


JDBCTemplate的CRUD

一个Demo

  • JDBCTemplateDemo
package com.bean.jdbcTemplate;

import com.bean.domain.Account;
import config.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import java.util.List;

public class JdbcTemplateDemo {
    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);

        JdbcTemplate jdbcTemplate = (JdbcTemplate)context.getBean("jdbcTemplateBean");

//        增加:使用update,没啥好说的的,注意类型是float
        jdbcTemplate.update("insert into account (name,money) values(?,?)","new Account",1000f);
//        删除:使用update,没啥好说的的,注意类型是float
        jdbcTemplate.update("delete from account where id=?",5);
//        更新:没啥好说的的,注意类型是float
        jdbcTemplate.update("update account set name=?,money=? where id=?","update test",100f,6);

//        查询
//        查询所有:查询所有,使用query方法,spring使用了这个BeanPropertyRowMapper封装进去了类型,然后直接封装进去
        List<Account> accounts = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
        for (Account account : accounts) {
            System.out.println(account);
        }

//        查询一个:这里也可以使用查询出List的,不过输出第0位就可以了,注意这里使用的构造函数中的最后一个参数为可变参数,只有在jdk1.5之后才能使用
        List<Account> account = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), 7);
        System.out.println(account.isEmpty()?"无参数":account.get(0));

//        查询一行一列(聚合函数):这里需要的就是一个类型,当查到的时候会自动转变为这个类型,不过一般推荐Long,因为假如int范围不够了呢
        //注意这里使用的是queryForObject
        jdbcTemplate.queryForObject("select count(id) from account",Long.class);
        //加参数:jdbcTemplate.queryForObject("select count(id) from account where money>?",Long.class,1500);

    }
}

下面是实际开发中的写法,结合Spring使用


使用XML配置

1、bean.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--首先配置dao-->
    <bean id="accountDaoImpl" class="com.bean.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplateBean"></property>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

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


</beans>

2、Account

package com.bean.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Float money;

    public Account() {
    }

    public Account(Integer id, String name, Float money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

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

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • AccountDaoImpl
package com.bean.dao.impl;

import com.bean.dao.IAccountDao;
import com.bean.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;

public class AccountDaoImpl implements IAccountDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Account findById(Integer id) {

        List<Account> accounts = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);

        return accounts.isEmpty()?null:accounts.get(0);
    }

    public Account findByName(String name) {

        List<Account> accounts = jdbcTemplate.query("select * form account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);

        //假如没有查到,返回null
        if (accounts.isEmpty()){
            return null;
        }
        //假如有多个,返回异常
        if (accounts.size()>1)
        {
            throw new RuntimeException("结果不唯一");
        }
        //否则返回唯一一个
        return accounts.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=?",account.getName(),account.getMoney());
    }
}
  • JDBCTemplateDemo
package com.bean.jdbcTemplate;

import com.bean.dao.IAccountDao;
import com.bean.dao.impl.AccountDaoImpl;
import com.bean.domain.Account;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JdbcTemplateDemo {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        IAccountDao accountDao = (IAccountDao) context.getBean("accountDaoImpl");
        System.out.println(accountDao.findById(7));
    }
}

使用注解配置

  • SpringConfiguration
package config;

import com.bean.dao.impl.AccountDaoImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
@ComponentScan("com.bean")
public class SpringConfiguration {

//    声明dao
    @Bean
    public AccountDaoImpl accountDao(){
        return new AccountDaoImpl();
    }
//    声明JdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }
//    声明数据源
    @Bean
    public DriverManagerDataSource dataSource(){
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setPassword("root");
        return driverManagerDataSource;
    }

}
  • Account
package com.bean.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Float money;

    public Account() {
    }

    public Account(Integer id, String name, Float money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

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

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
  • AccountDaoImpl
package com.bean.dao.impl;

import com.bean.dao.IAccountDao;
import com.bean.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

//    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
////        this.jdbcTemplate = jdbcTemplate;
////    }

    public Account findById(Integer id) {

        List<Account> accounts = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);

        return accounts.isEmpty()?null:accounts.get(0);
    }

    public Account findByName(String name) {

        List<Account> accounts = jdbcTemplate.query("select * form account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);

        //假如没有查到,返回null
        if (accounts.isEmpty()){
            return null;
        }
        //假如有多个,返回异常
        if (accounts.size()>1)
        {
            throw new RuntimeException("结果不唯一");
        }
        //否则返回唯一一个
        return accounts.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=?",account.getName(),account.getMoney());
    }
}
  • JDBCTemplateDemo
package com.bean.jdbcTemplate;

import com.bean.dao.IAccountDao;
import com.bean.dao.impl.AccountDaoImpl;
import com.bean.domain.Account;
import config.SpringConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JdbcTemplateDemo {
    public static void main(String[] args) {
//        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountDao accountDao = (IAccountDao) context.getBean("accountDaoImpl");
        System.out.println(accountDao.findById(7));
    }
}

Spring中的事务控制

Spring控制我们需要明确的

1、javaEE体系分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案

2、Spring框架为我们提供了一组事务控制的接口,具体后面介绍,这组接口是在spring-tx-5.0.2.RELEASSE.jar中

3、Spring中的事务控制都是基于AOP的,它既可以使用编程的方式进行实现,也可以使用配置的方式进行实现

Spring中事务控制的API介绍

1、依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

2、事务管理器

PlatformTransacionManager管理器,这是个事务接口,包含三个具体操作

  • 获取事务状态信息:TransactionStatus getTransaction(TransactionDefinition definition)
  • 提交事务:void commit(TransactionStatus status)
  • 回滚事务:void rollback(TransactionStatus status)

实现类

  • DataSourceTransactionManager
    • 包:org.springframework.jdbc.datasource.DataSourceTransactionManager
    • 使用Spring JDBC或者iBatis进行持久化数据时使用
  • HibernateTransactionManager
    • 包:org.springframework.orm.hibernate5.HibernateTransactionManager
    • 使用Hibernate版本进行持久化数据时使用

3、事务的定义信息

TransactionDefinition:事物的定义信息对象,里面有如下方法

  • String getName():获取事务对象名称
  • int getIsolationLevel():获取事务隔离级别
    • ISOLATION_DEFAULT:默认级别,归属下列某一种
    • ISOLATION_READ_UNCOMMITTED:可以读取未提交数据
    • ISOLATION_READ_COMMITTED:只能读取已提交数据,解决脏读问题(Oracle默认级别)
    • ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可重复度的问题(MYSQL默认)
    • ISOLATION_SERIALIZABLE:是否读取其他事务提交添加后的数据,解决幻影读问题
  • int getPropagetionBehavior():获取事务传播行为
    • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务(默认),增删改的事务
    • SUPPORTS:支持当前事务,加入当前没有事务,就以非事务方式执行(没有事务),只有查询才能用的事务
    • MANDATORY:使用当前事务,如果当前没有事务,抛出异常
    • REQUERS_NEW:新建事务,如果当前在事务中,就把当前事务挂起
    • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    • NEVER:以非事务方式运行,加入当前存在事务,抛出异常
    • NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行REQUIRED类似的操作
  • int getTimeout():获取事务超时时间:默认值为-1(没有超时时间)。假如有,则以秒为单位进行设置
  • boolean isReadOnly():获取事务是否只读:建议查询时设置为只读

4、事务的状态信息

TransactionStatus接口描述了某个时间点上事务的状态信息,包含有六个基本操作

  • 刷新事务:void flush()
  • 获取是否是存在存储点:boolean hasSavepoint()

事务是以节点来提交的,每一个节点都是一个储存点,当事务回滚的时候,只会回滚到上个节点,而不是从头来

  • 获取事务是否完成:boolean isCompleted()
  • 获取事务是否为新的事务:boolean isNewTransaction()
  • 获取事务是否回滚:boolean isRollbackOnly()
  • 设置事务回滚:void setRollbackOnly()

环境准备

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bean</groupId>
    <artifactId>SpringAOP</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>

基于XML的事务控制

1、配置事务管理器:注入DataSource

2、配置事务通知

  • 注入事务的约束
<?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:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     https://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
     https://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop
     https://www.springframework.org/schema/aop/spring-aop.xsd">
  • 配置事务管理器的bean对象:包为:org.springframework.jdbc.datasource.DataSourceTransactionManager
  • 使用标签<tx:advice></tx:advice>标签配置事务通知
    • id:配置唯一标识
    • transaction-manager:事务管理器
  • <tx:advice></tx:advice>内部配置事务的属性
    • <tx:attributes></tx:attributes>
    • <tx:method>
      • name指定service中的方法
      • isolation

      用于指定事物的隔离级别,默认值为DEFAULT,表示使用数据库的默认隔离级别

      • propagetion
      • 用于指定事物的传播行为,默认值为REQUIRED,表示一定会有事务,这是增删改的选择
      • 查询可以选择为SUPPORTS
      • read-only

      指定事物是否只读,只有查询方法才能设置为true,默认值为false,表示读写

      • timeout

      用于指定事物的超时时间,默认值为-1,表示永不超时,如果指定数值则以秒为单位

      • rollback-for

      用于指定一个异常,碰到这个异常则回滚,其他异常不会滚。 没有默认值,表示任何异常都回滚

      • no-rollback-for

      用于指定一个异常,碰到这个异常不回滚,其他异常都回滚。 没有默认值,表示任何异常都回滚

3、配置AOP的通用切入点表达式

4、建立事务通知和切入点表达式的对应关系:使用<aop:advisor></aop:advicor>


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

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="accountService" class="com.bean.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <bean id="accountDao" class="com.bean.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!--上面是配的bean对象,下面开始编写事务-->

    <!--首先要有一个事务管理器,包为:org.springframework.jdbc.datasource.DataSourceTransactionManager
        事务管理器内要配置dataSource
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--现在配置事务的状态
        id 为唯一标识
        transaction-manager为配置事务管理器
    -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务属性-->
        <tx:attributes>
            <!--
                <tx:method></tx:method>
                    name:要配置的方法名称,比如我们这里用AOP配置的全都是service.impl包下的,所以
                        * :匹配全部的方法(虽然也就那么一个transer方法)
                        find*:将来假如有方法为find开头的时候,匹配折现方法
                        优先级:find* 高于 *
                    isolation:指定事物的隔离级别,默认值为DEFAULT,表示数据库的默认隔离级别
                    read-only:是否只读
                    propagation:指定事物的传播行为,默认为REQUIRED,为增删改的,查询可以为SUPPORTS
                    timeout:指定超时时间
                    rollback-for:用于指定一个异常,发生了此异常则回滚,其他异常不回滚。默认值无,表示任何异常都回滚。
                    no-rollback-for:指定一个异常,发生此异常不回滚,其余异常全都回滚。默认值无,表示任何异常都回滚。
            -->
            <tx:method name="*" read-only="false" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"></tx:method>
        </tx:attributes>
    </tx:advice>


    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.bean.service.impl.*.*(..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"></aop:advisor>
    </aop:config>

</beans>
  • AccountDaoImpl
package com.bean.dao.impl;

import com.bean.dao.IAccountDao;
import com.bean.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class AccountDaoImpl implements IAccountDao {

    JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public Account findById(Integer id) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),id);
        return accounts.isEmpty()?null:accounts.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}
  • AccountServiceImpl
package com.bean.service.impl;

import com.bean.dao.IAccountDao;
import com.bean.dao.impl.AccountDaoImpl;
import com.bean.domain.Account;
import com.bean.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void transfer(Integer Transferer, Integer Payee, Float money) {
        System.out.println("transer");

        Account accountMinus = accountDao.findById(1);

        Account accountAdd = accountDao.findById(2);

        accountMinus.setMoney(accountMinus.getMoney()-money);

        accountAdd.setMoney(accountAdd.getMoney()+money);

        accountDao.updateAccount(accountMinus);
//        int i = 1/0;
        accountDao.updateAccount(accountAdd);
    }
}
  • 测试
import com.bean.dao.impl.AccountDaoImpl;
import com.bean.service.IAccountService;
import com.bean.service.impl.AccountServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig
public class TransactionTest {
    @Test
    public void Test(){

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        IAccountService accountService = (IAccountService) context.getBean("accountService");

        accountService.transfer(1,2,100F);
    }
}
  • 效果

效果就是:

  • 当没有报错时:转账正常执行
  • 当出现错误时:回滚

基于注解的事务控制

1、导入关于context的名称空间

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

2、配置事务管理器

3、开启Spring对注解的支持,使用<tx:annotation-drivern/>

4、在需要事务支持的时候使用@Transactional注解


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

    <!--首先配置一下spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.bean"></context:component-scan>


    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--
        使用注解进行事务控制的配置
        1. 配置事务管理器
        2. 开启spring对注解事务的支持:使用<tx:annotation-driven/>,里面配置事务管理器
        3. 在需要事务支持的时候使用@Transactional注解
    -->


    <!--事务管理器还得留下-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

</beans>
  • AccountDaoImpl
package com.bean.dao.impl;

import com.bean.dao.IAccountDao;
import com.bean.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import java.util.List;

@Component
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public Account findById(Integer id) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),id);
        return accounts.isEmpty()?null:accounts.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}
  • AccountServiceImpl
package com.bean.service.impl;

import com.bean.dao.IAccountDao;
import com.bean.dao.impl.AccountDaoImpl;
import com.bean.domain.Account;
import com.bean.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)  //这里是配置事务控制的注解的,配置的全局为只读型的配置
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }


    //这里单独配置,配置可写型的配置
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void transfer(Integer Transferer, Integer Payee, Float money) {
        System.out.println("transer");

        Account accountMinus = accountDao.findById(1);

        Account accountAdd = accountDao.findById(2);

        accountMinus.setMoney(accountMinus.getMoney()-money);

        accountAdd.setMoney(accountAdd.getMoney()+money);

        accountDao.updateAccount(accountMinus);
//        int i = 1/0;
        accountDao.updateAccount(accountAdd);
    }
}
  • 测试
import com.bean.dao.impl.AccountDaoImpl;
import com.bean.service.IAccountService;
import com.bean.service.impl.AccountServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class TransactionTest {

    @Autowired
    private  IAccountService accountService;


    @Test
    public void Test(){
        accountService.transfer(1,2,100F);
    }
}

事务回滚规则

默认只接受不受检查(Error和RuntimeException),但是可以利用rollbackFor的参数(类的class数组)指定遇到这个异常来回滚

可以通过noRollbackFor来定义参数(类的class数组)来指定遇到异常不回滚


Spring5新特性介绍

与JDK相关的升级

1、JDK版本要求

spring5.02017-9发布了GA(通用)版本,该版本基于jdk8编写的,所以jdk8之前的版本无法使用 同时tomcat要求8.5以上 我们使用jdk8构建工程,可以降低版本编译,但是不能使用jdk8以下版本构建工程 IDE同时需要更新,eclipse 4.7.2之前的就甭想了

2、升级说明

1、基于JDK8的反射增强 2、@NonNu11注解和@Nullable注解的使用 用@Nullable和@NotNull注解来显示表明可为空的参数和以及返回值。 这样就够在编译的时候理空值而不是在运行时抛出NullPointerExceptions。 3、日志记录方面 Spring Framework 5.0带来了Commons Logging 桥接模块的封装,它被叫做spring-jcl 而不是标准的Commons Logging。 当然,无需任何额外的桥接,新版本也会对Log4j2.x,SLF4J,JUL( java.util. logging) 进行自动检测。

核心容器的升级

Spring Framework 5.0现在支持候选组件索引作为类路径扫描的替代方案。

该功能已经在类路径扫描器中添加,以简化添加候选组件标识的步骤。

应用程序构建任务可以定义当前项目自己的META-INE/spring.components文件。

在编译时,源模型是自包含的,JPA实体和Spring组件是已被标记的。

从索引读取实体而不是扫描类路径对于小于200 个类的小型项目是没有明显差异,但对大型项目影响较大。

加载组件索引开销更低。因此,随着类数的增加,索引读取的启动时间将保持不变。

加载组件索引的耗费是廉价的。因此当类的数量不断增长,加上构建索引的启动时间仍然可以维持一一个常数,

不过对于组件扫描而言,启动时间则会有明显的增长。

这个对于我们处于大型spring项目的开发者所意味着的,是应用程序的启动时间将被大大缩减。

虽然20或者30秒钟看似没什么,但如果每天要这样登上好几百次,加起来就够你受的了。使用了组件索引的话,就能帮
助你每天过的更加高效。
你可以在Spring的Jira上了解更多关于组件索引的相关信息。

JetBarins Kotlin语言支持

这个不多赘述

响应式编程风格

此次spring发行版本的一个激动人心的特性就是新的响应式堆栈WEB框架。

这个堆栈完全的响应阻塞,适合于事件循环风格的处理,可以进行少量线程的扩展。

Junit5支持

完全支持JUnit 5 Jupiter, 所以可以使用JUnit 5来编写测试以及扩展。

此外还提供了一个编程以及扩展模型,Jupiter子项目提供了一个测试引擎来在Spring上运行基于 Jupiter的测试。

另外,spring Framework 5还提供了在Spring TestContext Framework 中进行并行测试的扩展。

针对响应式编程模型,spring-test 现在还引入了支持spring webFlux的WebTestclient 集成测试的支持,类似于MockMvc,并不需要一一个运行着的服务端。

使用一个模拟的请求或者响应,WebTestClient就可以直接绑定到webFlux服务端设施。

你可以在这里找到这个激动人心的TestContext 框架所带来的增强功能的完整列表。

依赖库的更新

终止支持的类库:

  • Portlet .
  • Velocity .
  • JasperReports .
  • XMLBeans .
  • JDO.
  • Guava .

支持的类库:

  • Jackson 2.6+
  • EhCache 2.10+ 1 3.0 GA
  • Hibernate 5.0+
  • JDBC 4.0+
  • XmlUnit 2 .x+
  • OkHttp 3.X+
  • Netty 4.1+