Spring 是分层的Java SE/EE 应用full-stack(全栈式)轻量级的开源框架。 full-stack 全栈式:它对各种主流技术和框架进行了整合,同时对三层架构提供了解决方案。 提供了表现层SpringMvc和持久层Spring JDBC Template以及业务层Spring核心:事物和对象管理等企业级应用技术。 Spring包括两大核心:IOC(Inverse Of Control : 控制反转 把对象的创建权交给spring管理)和 AOP(Aspect Oriented Programming:面向切面编程 在不修改源码的情况下对方法进行增强) 为内核

轻量级:轻量级和重量级的划分主要依据:使用了多少服务、启动时需要加载多少资源以及耦合度等等。
Spring的优势:

  • 方便解耦,简化开发:Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理。什么是耦合度?对象之间的关系,通常当一个模块更改时也需要更改其他模块,这就是耦合
  • AOP 编程的支持:Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
  • 声明式事物支持:通过配置完成事物的管理,无序手动编程
  • 方便测试,降低Java EE API的使用:Spring对Junit4支持,可以使用注解测试
  • 方便集成各种优秀框架

image.png
IOC 控制反转:是一种设计思想。

  • 控制:在Java中指的是对象的控制权限(创建、销毁)
  • 反转:指的是对象控制权由原来的开发者在类中手动创建,变成了由Spring容器进行创建

例如:

  1. 传统方式:之前需要一个userDao的实例,就需要我们手动new UserDaoImpl()
  2. IOC 方式:需要一个userDao实例,直接从Spring 的 IOC容器中获得,对象的创建权交给了Spring

自定义IOC容器实现

通过自定义IOC容器实现:service与dao层的代码解耦合

  1. 创建maven项目,引入依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>dom4j</groupId>
    4. <artifactId>dom4j</artifactId>
    5. <version>1.6.1</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>jaxen</groupId>
    9. <artifactId>jaxen</artifactId>
    10. <version>1.1.6</version>
    11. </dependency>
    12. <dependency>
    13. <groupId>junit</groupId>
    14. <artifactId>junit</artifactId>
    15. <version>4.13</version>
    16. </dependency>
    17. </dependencies>
  2. 编写dao与service层,传统方式开发,耦合度太高了,如果UserDao删掉了,那么在编译器UserService就会报错 ```java public interface IUserDao { void save(); }

public class UserDaoImpl implements IUserDao { @Override public void save() { System.out.println(“保存成功”); } }

public interface IUserService { void save(); }

public class UserServiceImpl implements IUserService {

IUserDao userDao = new UserDaoImpl();

@Override
public void save() {
    userDao.save();
}

} @org.junit.Test public void testCustom() { IUserService service = new UserServiceImpl(); service.save(); }


3. 改造使用反射,获取userDao实例,去掉new关键字
```java
public class UserServiceImpl implements IUserService {

//    IUserDao userDao = new UserDaoImpl();

    @Override
    public void save() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//        userDao.save();
        IUserDao userDao = (IUserDao) Class.forName("com.example.ioc.dao.impl.UserDaoImpl").newInstance();
        userDao.save();
    }
}
  1. 上述还是存在硬编码的问题:”com.example.ioc.dao.impl.UserDaoImpl“,硬编码的问题使用配置文件去解决

    1. 准备一个配置文件:beans.xml

      <?xml version="1.0" encoding="UTF-8" ?>
      <beans>
      <!-- id:表示  class:要生成的实例类的全路径 -->
      <bean id="userDao" class="com.example.ioc.dao.impl.UserDaoImpl">
      
      </bean>
      </beans>
      
    2. 编写一个工厂类,使用dom4j解析配置文件,存储配置信息

      public class BeanFactory {
      
      private static Map<String, Object> iocMap = new HashMap<>();
      
      //程序启动时 初始化对象实例
      static {
        //1. 读取配置文件
        ClassLoader classLoader = BeanFactory.class.getClassLoader();
        //2. 加载配置文件
        InputStream stream = classLoader.getResourceAsStream("beans.xml");
        //3. 解析xml
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(stream);
            //4. 编写xpath表达式
            String xpath = "//bean";//获取bean标签
            //根据xpath获取到所有bean标签
            List<Element> list = document.selectNodes(xpath);
            //5. 遍历并反射对象实例
            for (Element element : list) {
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                //使用反射生成实例对象
                Object o = Class.forName(className).newInstance();
                //保存到Map - IOC 容器
                iocMap.put(id, o);
            }
        } catch (DocumentException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
      }
      
      public static Object getBean(String beanId) {
        return iocMap.get(beanId);
      }
      }
      
    3. 使用反射,创建实例,存到map中(map就是IOC容器) ```java public class UserServiceImpl implements IUserService {

// IUserDao userDao = new UserDaoImpl();

@Override
public void save() throws ClassNotFoundException, IllegalAccessException, InstantiationException {

// userDao.save(); // IUserDao userDao = (IUserDao) Class.forName(“com.example.ioc.dao.impl.UserDaoImpl”).newInstance(); IUserDao userDao = (IUserDao) BeanFactory.getBean(“userDao”); userDao.save(); } }

> BeanFactory其实就是一个简单的Spring的IOC容器所具备的功能,需要实例直接从IOC容器获取即可。
> 解耦的思路:反射+配置文件

<a name="DI3ur"></a>
## Spring 基础
首先,先实现Spring的简单实现,借助springIOC 实现service层和dao层解耦合

1. 引入spring IOC 容器依赖 : spring-context
```xml
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  1. 配置applicationContext.xml 文件其实就是上述的beans.xml的一样的功能 ```xml <?xml version=”1.0” encoding=”UTF-8” ?>


3. service层调用dao层
```java
        //1. 获取到spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
        // 这句代码其实就是:
        // - 加载配置文件
        // - 解析配置文件
        // - 创建bean对象
        // - 在IOC容器中存储
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        //传递id值
        //直接从IOC容器中取出bean对象
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.save();

Spring 相关API

接口
抽象类
实现类
image.png

BeanFactory

BeanFactory 是IOC容器的核心接口,它定义了IOC的基本功能 特点:在第一次调用getBean()方法时,创建指定对象的实例

        //BeanFactory 是核心接口
        //这句代码执行的时候:不会创建bean对象 存到IOC容器中
        BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        //在调用getBean的才真正创建bean对象 存到容器中,再根据key取出bean对象
        UserDao userDao = (UserDao) xmlBeanFactory.getBean("userDao");
        userDao.save();

ApplicationContext

代表应用上下文对象,可以获得spring IOC容器中的bean对象 特点:在Spring容器启动时,加载并创建所有对象实例

常见的实现类有:

  • ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐使用
  • FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
  • AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类创建Spring容器。用它来读取注解
    //1. 获取到spring上下文对象,借助上下文对象可以获取到IOC容器中的bean对象
          // 这句代码其实就是:
          // - 加载配置文件
          // - 解析配置文件
          // - 创建bean对象
          // - 在IOC容器中存储
          ApplicationContext applicationContext =
                  new ClassPathXmlApplicationContext("applicationContext.xml");
    
    常用的几个方法如下:
  1. 通过bean id 获取bean

         //根据bean id 获取bean
         //直接从IOC容器中取出bean对象
         UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    
  2. 通过类型获取bean

    //根据类型 获取bean
         UserDao userDao = applicationContext.getBean(UserDao.class);
    

    但是这种方式存在着一个问题 :例如在上述中我们实现了UserDaoImpl,这时候又创建了一个UserDao的实现了UserDaoImpl2,在applicationContext.xml

     <bean id="userDao" class="com.prim.dao.impl.UserDaoImpl"></bean>
     <!-- 共同实现了相同的接口 -->
     <bean id="userDao2" class="com.prim.dao.impl.UserDaoImpl"></bean>
    

    这是在使用类型获取bean的话spring就会抛出一个错误
    image.png

  3. 通过bean id 和 class 一起查找bean

    
         //根据bean id 和 class 一起获取bean
         UserDao userDao = applicationContext.getBean("userDao", UserDao.class);
    

    Spring 配置文件

    bean 标签的配置

     <!--
         id:唯一标识
         class:类全路径
         bean 必须有无参构造函数,如果没有无参构造函数 那么bean对象是无法创建成功的
         scope:对象的作用范围
             singleton: 默认值 单例的 每次从容器中取出的是一个对象
             prototype: 多例的 每次从容器中取出该对象,那么该对象就会重新创建
             request:在web 项目中,对象存储到request域中
             session:在web 项目中,对象会存到session域中
             global session:在web项目中,应用在Portlet环境,如果没有该环境就会存储在session域中
    
     -->
     <bean id="userDao" class="com.prim.dao.impl.UserDaoImpl" scope="singleton"></bean>
    

    bean的生命周期:

  4. 当scope:singleton 时

Bean的实例化个数:1个
Bean实例化的时机:当spring核心文件被加载时,实例化配置的bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直存活
对象销毁:当应用卸载,销毁容器时,对象就被销毁了

  1. 当scope:prototype 时

Bean的实例化个数:多个
Bean实例化的时机:当调用getBean()方法时,实例化Bean
Bean的生命周期:
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直存活
对象销毁:当对象长时间不用时,被java的垃圾回收器销毁

bean 生命周期配置

创建 bean生命周期调用的方法:

public class UserDaoImpl implements UserDao {

    public void init(){
        System.out.println("初始化方法执行了");
    }

    @Override
    public void save() {
        System.out.println("保存成功");
    }

    public void destroy(){
        System.out.println("销毁方法执行了");
    }
}

在Bean配置文件中,配置init-methoddestroy-method 创建实例和销毁实例分别调用的方法。

        init-method: 创建实例时,调用的方法
        destroy-method:销毁实例时,调用的方法

    -->
    <bean id="userDao" class="com.prim.dao.impl.UserDaoImpl" scope="singleton"
          init-method="init" destroy-method="destroy">

    </bean>

Bean实例化的三种方式

  1. 无参构造方式实例化,其实就是上述的配置
  2. 工厂静态方法实例化

应用场景:依赖的jar包有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁的使用B对象,此时我们可以将B对象的创建权交个IOC容器,以后我们在使用B对象的时候,无需从A类中的m1方法获取,直接从IOC容器获得。
常见静态工厂:

public class StaticFactoryBean {
    public static UserDao createUserDao() {
        //假设:这个对象在jar包中的一个对象
        return new UserDaoImpl();
    }
}

Beans文件配置:

    <bean id="factoryUserDao"
          class="com.prim.factory.StaticFactoryBean"
          factory-method="createUserDao">

    </bean>
  1. 普通工厂方法实例化

应用场景:和静态工厂方法实例化一样
Beans文件配置:

    <!-- 普通工厂实例化 -->
    <!-- 先生成工厂类的实例 - 存到IOC容器中 -->
    <bean id="dynamicFactory" class="com.prim.factory.DynamicFactoryBean"/>

    <!-- 再生产UserDao的实例 - 存到IOC容器中 -->
    <bean id="dynamicUserDao" factory-method="createUserDao" factory-bean="dynamicFactory"/>

重点Bean 依赖注入

依赖注入 DI (Dependency Injection):它是Spring框架核心IOC的具体实现。

在我们编写程序时,通过控制反转,把对象的创建交给Spring,但是代码中不可能出现没有依赖的情况。IOC解耦知识降低他们的依赖关系,但不会消除。
例如:业务层仍会调用持久层方法,那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。通过框架把持久层对象传入业务层,而不用我们自己去获取。

如下例子:业务层来调用持久层例子

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        //根据之前将的IOC容器中获取 dao的实例类
        //将下面的关系,其实可以由Spring框架来维护 持久层和业务层的依赖关系 而不需要我们手动去维护
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.save();
    }
}

然后,在Beans的配置文件中,添加业务层由IOC来创建业务层的实例

    <bean id="userDao"
          class="com.prim.dao.impl.UserDaoImpl">
    </bean>


    <!-- 配置userService -->
    <bean id="userService" class="com.prim.service.impl.UserServiceImpl"/>

模拟Web层调用业务层的代码:

    @Test
    public void testService() {
//        UserService service = new UserServiceImpl();
        //
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = (UserService) applicationContext.getBean("userService");
        service.save();
    }

上述的代码其实别没有彻底的解耦,UserServiceImpl中还需要从IOC容器中获取DAO层的实例,业务层和持久层的依赖关系并没有彻底消除,IOC容器只是帮助我们去创建实例。

业务层和持久层的依赖关系,其实可以由Spring框架来进行维护。通过Spring框架,将持久层对象自动注入到业务层,只需要在配置文件配置持久层对象注入到哪个业务层对象。

  1. 构造方法

image.png
service层的修改

public class UserServiceImpl implements UserService {

    //注入UserDao对象
    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        //根据之前将的IOC容器中获取 dao的实例类
        //将下面的关系,其实可以由Spring框架来维护 持久层和业务层的依赖关系 而不需要我们手动去维护
//        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.save();
    }
}

beans配置文件的修改:

    <!-- userDao 对象 -->
    <bean id="userDao"
          class="com.prim.dao.impl.UserDaoImpl">
    </bean>


    <!-- 配置userService 有参构造实例对象 -->
    <bean id="userService" class="com.prim.service.impl.UserServiceImpl">
        <!-- index: UserServiceImpl的构造参数第一个参数进行赋值
             type: 参数的类型
             ref:  引用IOC容器中的对象,其实就是和bean标签配置的id值一样
         -->
<!--        <constructor-arg index="0" ref="userDao" type="com.prim.dao.UserDao"></constructor-arg>-->

        <!--
            第二种写法
            name: 构造参数的参数名
         -->
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>

测试代码:通过IOC容器获取到UserService实例对象,调用save方法。

    @Test
    public void testService2() {
        //取出Service的实例
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = (UserService) applicationContext.getBean("userService");
        service.save();
    }
  1. set方法

service层的代码修改:

public class UserServiceImpl implements UserService {

    //注入UserDao对象
    private UserDao userDao;

//    public UserServiceImpl(UserDao userDao) {
//        this.userDao = userDao;
//    }


    //创建setter方法 注入UserDao对象
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        //根据之前将的IOC容器中获取 dao的实例类
        //将下面的关系,其实可以由Spring框架来维护 持久层和业务层的依赖关系 而不需要我们手动去维护
//        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        userDao.save();
    }
}

beans配置文件修改代码:通过property标签,设置name:就是setter方法的属性名,ref:就是引用对象,也就是要注入的对象

    <!-- userDao 对象 -->
    <bean id="userDao"
          class="com.prim.dao.impl.UserDaoImpl">
    </bean>

    <!-- 配置userService set方法构造实例对象 -->
    <bean id="userService" class="com.prim.service.impl.UserServiceImpl">
        <!--
            name: set的属性名称
            ref: 引用对象
            value: 普通类型的数据
         -->
        <property name="userDao" ref="userDao"/>
    </bean>
  1. P命名空间注入

    P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件。 使用的不多,不推荐使用。

带入如下:
首先引入P命名空间

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

service的配置

    <!-- 配置userService set方法构造实例对象 -->
    <bean id="userService" class="com.prim.service.impl.UserServiceImpl" p:userDao-ref="userDao">
    </bean>

:::tips 依赖注入主要就有两种:构造方法注入、set方法注入。最常用的就是set方法注入 :::

Bean 依赖注入的数据类型

注入的数据的三种数据类型:

  • 普通数据类型
  • 引用数据类型
  • 集合数据类型

引用数据类型,就是上述的操作都是引用数据类型,下面看一下普通数据类型和集合数据类型

  1. 普通数据类型

注入username和age的普通数据类型

public class UserDaoImpl implements UserDao {
    private String username;
    private Integer age;

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

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public void save() {
        System.out.println(username + ":" + age);
        System.out.println("保存成功");
    }
}
  1. 配置文件

     <bean id="userDao"
           class="com.prim.dao.impl.UserDaoImpl">
         <property name="username" value="jakeprim"/>
         <property name="age" value="18"/>
     </bean>
    
  2. 注入集合数据类型

  • List集合注入

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

    配置文件:

      <!-- user 对象 -->
      <bean id="user" class="com.prim.domin.User">
          <property name="username" value="jakeprim"/>
          <property name="age" value="28"/>
      </bean>
    
      <!-- userDao 对象 -->
      <bean id="userDao"
            class="com.prim.dao.impl.UserDaoImpl">
          <property name="username" value="jakeprim"/>
          <property name="age" value="18"/>
          <property name="list">
              <list>
                  <value>aa</value>
                  <!-- 将user对象注入到list集合中 -->
                  <ref bean="user"></ref>
              </list>
          </property>
      </bean>
    

    image.png

  • Set集合注入

Set集合其实和List集合的配置差不多是一样的。

        <!-- set集合注入 -->
        <property name="set">
            <set>
                <value>aa</value>
                <ref bean="user"></ref>
            </set>
        </property>
  • Array数组注入 ```xml private Object[] array;

    public void setArray(Object[] array) {

      this.array = array;
    

    }

    <!-- 数组注入 -->
    <property name="array">
        <array>
            <value>aa</value>
            <ref bean="user"></ref>
        </array>
    </property>

- Map集合注入
```xml
<!-- Map集合注入 -->
        <property name="map">
            <map>
                <entry key="k1" value="kkk"></entry>
                <entry key="k2" value-ref="user"></entry>
            </map>
        </property>
  • Properties配置注入

properties都是由String字符串的key和value组成的. {p2=v2, p1=v1}

        <!-- properties注入 -->
        <property name="properties">
            <props>
                <prop key="p1">v1</prop>
                <prop key="p2">v2</prop>
            </props>
        </property>

配置文件模块化

在实际开发中,spring的配置内容非常多,这就导致spring配置很繁杂并且体积很大。可以将部分配置拆解到其他配置文件中,就是配置文件模块化

  1. 并列多个配置文件

将配置文件按层分,service层配置,dao层配置

    public void testService2() {
        //取出Service的实例
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml",
                        "applicationContext-service.xml","applicationContext-dao.xml");
        UserService service = (UserService) applicationContext.getBean("userService");
        service.save();
    }
  1. 主从配置文件

applicationContext的主配置文件,引入子配置文件

    <!-- import标签 设置加载的配置文件 -->
    <import resource="classpath:applicationContext-user.xml"/>
    <import resource="classpath:applicationContext-product.xml"/>

:::tips 注意多个配置文件,出现相同id的bean不会报错,但是后加载的bean会覆盖前面加载的bean :::

  • <bean> 标签:创建对象并放到spring的IOC容器

id: 在容器中Bean实例的唯一标识,不允许重复
class:要实例化的Bean的全限定名
scope:Bean的作用范围,常用:singleton(默认)和prototype

  • <constructor-arg>标签 :属性注入

name:属性名称
value: 注入的普通属性值
ref: 注入的对象引用值

  • <property> 标签:属性注入

name : 属性名称
value: 注入的普通属性值
ref: 注入的对象引用值

  • <import> 标签:导入其他的spring的配置分文件

IOC 实战

Spring-context整合DbUtils来实现对数据库的操作。
1.引入依赖:

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>

        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
  1. 创建DAO层和Service层,这里代码还是比较简单的

DAO 层代码实现如下:通过Spring注入QueryRunner对象

public class AccountDaoImpl implements AccountDao {
    //通过Spring 注入QueryRunner对象
    private QueryRunner queryRunner;

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    @Override
    public List<Account> findAll() {
        List<Account> accounts = null;
        try {
            accounts = queryRunner.query("select * from account", new BeanListHandler<>(Account.class));
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return accounts;
    }

    @Override
    public Account findById(Integer id) {
        String sql = "select * from account where id = ?";
        Account account = null;
        try {
            account = queryRunner.query(sql, new BeanHandler<>(Account.class), id);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return account;
    }

    @Override
    public void save(Account account) {
        String sql = "insert into account(name,money) values(?,?)";
        try {
            int update = queryRunner.update(sql, account.getName(), account.getMoney());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void update(Account account) {
        String sql = "update account set name=?,money=? where id = ?";
        try {
            int update = queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void delete(Integer id) {
        String sql = "delete from account where id = ?";
        try {
            int update = queryRunner.update(sql, id);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

Service层代码实现如下:
将DAO的实例注入

public class AccountServiceImpl implements AccountService {


    //通过Spring 注入AccountDao实例
    private AccountDao accountDao;

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

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }
}
  1. 配置数据库数据properties文件

    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring_db?characterEncoding=utf-8
    jdbc.username=root
    jdbc.password=123456
    
  2. 配置applicationContext.xml Spring配置

  • 加载数据库配置文件
  • 创建数据源对象
  • 创建QueryRunner对象
  • 创建DAO对象
  • 创建Service对象 ```xml <?xml version=”1.0” encoding=”UTF-8” ?>

<!-- 创建 DataSource 数据源对象 -->
<!-- 抽取数据库配置文件 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- 创建 QueryRunner 对象 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
    <!-- 数据源对象DataSource 通过构造函数的方式注入 -->
    <constructor-arg name="ds" ref="dataSource"/>
</bean>

<!-- DAO层bean实例化 -->
<bean id="accountDao" class="com.example.dao.impl.AccountDaoImpl">
    <!-- set方式 注入QueryRunner对象 -->
    <property name="queryRunner" ref="queryRunner"/>
</bean>

<!-- service层bean实例化 -->
<bean id="accountService" class="com.example.service.impl.AccountServiceImpl">
    <!-- set 方式 注入AccountDao对象 -->
    <property name="accountDao" ref="accountDao"/>
</bean>

> 上述的代码,就将DbUtils和Spring IOC 整合了在一起,这样确实大大简化了我们的开发

测试:通过`ClassPathXmlApplicationContext` 获取IOC容器,拿到Service层的实例,进行操作
```java
    @org.junit.Test
    public void test() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService service = (AccountService) context.getBean("accountService");
        List<Account> accounts = service.findAll();
        System.out.println(accounts);
    }

Spring 注解开发

Spring是轻代码而重配置的框架,配置比较繁重同样也会影响开发效率。所以spring 注解开发来代替xml配置文件简化配置,提高开发效率

Spring 常用注解

spring常用的注解主要替代<bean>的配置。

注解 说明
@Component 使用在类上用于实例化bean
@Controller 使用在web层类上用于实例化bean
@Service 使用在service层类上用于实例化bean
@Repository 使用在dao层类上用于实例化bean
@Autowired 使用在字段上用于根据类型依赖注入,如果使用类型匹配到多个实例会根据变量名去匹配实例,如果没有该实例就会报错。
@Qualifier 结合@Autowired一起使用,根据名称ID进行依赖注入。注意这个注解是不能单独使用的
@Resource 相当于@Autowired+@Qualifier,按照名称进行注入。注意在JDK11以后完全移除了javax扩展导致不能使用@Resource注解。如果要使用需要maven引入依赖:<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
@Value 注入普通属性
@Scope 标注bean的作用范围
@PostConstruct 使用在方法上标注该方法是bean的初始化方法
@PreDestroy 使用在方法上标注该方法是bean的销毁方法

image.png :::tips 注意:使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,指定哪个包及其子包下的bean需要进行扫描以识别使用注解配置的类、字段和方法。 :::

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

常用注解的示例代码:
我们将上述的Service层的代码,配置文件移除配置通过注解来进行配置

//@Service == <bean> 标签的配置,默认的id  class = AccountServiceImpl
@Service("accountService")//value == id 配置就是id
@Scope("singleton")//作用域配置为单例
public class AccountServiceImpl implements AccountService {
    //通过Spring 注入AccountDao实例
//    @Autowired //根据类型进行注入
//    @Qualifier("accountDao")//根据类型匹配到多个实例时,会根据设置的值进行二次匹配
    @Resource(name = "accountDao") // 根据name值,直接从IOC容器中进行匹配
    private AccountDao accountDao;

    @Value("注入普通属性")
    private String str1;

    @Value("${jdbc.driverClassName}") //注入properties文件中的配置,因为我们已经在applicationContext.xml中加载了该文件可以直接使用
    private String driver;

    @PostConstruct
    private void init() {
        System.out.println("方法初始化");
    }

    @PreDestroy
    private void destroy() {
        System.out.println("方法销毁");
    }

    @Override
    public List<Account> findAll() {
        System.out.println(str1);//注入普通属性
        System.out.println(driver);//com.mysql.jdbc.Driver
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }
}

DAO层的代码配置,从applicationContext.xml中移除DAO的层的配置,通过注解实现

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    //通过Spring 注入QueryRunner对象
    @Autowired
    private QueryRunner queryRunner;

    @Override
    public List<Account> findAll() {
        List<Account> accounts = null;
        try {
            accounts = queryRunner.query("select * from account", new BeanListHandler<>(Account.class));
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return accounts;
    }

    @Override
    public Account findById(Integer id) {
        String sql = "select * from account where id = ?";
        Account account = null;
        try {
            account = queryRunner.query(sql, new BeanHandler<>(Account.class), id);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return account;
    }

    @Override
    public void save(Account account) {
        String sql = "insert into account(name,money) values(?,?)";
        try {
            int update = queryRunner.update(sql, account.getName(), account.getMoney());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void update(Account account) {
        String sql = "update account set name=?,money=? where id = ?";
        try {
            int update = queryRunner.update(sql, account.getName(), account.getMoney(), account.getId());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void delete(Integer id) {
        String sql = "delete from account where id = ?";
        try {
            int update = queryRunner.update(sql, id);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

spring核心配置文件修改如下:

DAO层和Service层的配置就不需要了,通过注解可以大大的减少配置文件的配置。 注意:一定要开启包注解扫描。

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

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.example"></context:component-scan>

    <!-- 引入jdbc.properties
        注意:如果要引入外部的文件都需要加上classpath:这个前缀
    -->
    <context:property-placeholder location="classpath:jdbc.properties"/>


    <!-- 创建 DataSource 数据源对象 -->
    <!-- 抽取数据库配置文件 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 创建 QueryRunner 对象 -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <!-- 数据源对象DataSource 通过构造函数的方式注入 -->
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

    <!-- DAO层bean实例化 -->
<!--    <bean id="accountDao" class="com.example.dao.impl.AccountDaoImpl">-->
<!--        &lt;!&ndash; set方式 注入QueryRunner对象 &ndash;&gt;-->
<!--        <property name="queryRunner" ref="queryRunner"/>-->
<!--    </bean>-->

<!--    <bean id="accountDao2" class="com.example.dao.impl.AccountDaoImpl"></bean>-->


    <!-- service层bean实例化 -->
<!--    <bean id="accountService" class="com.example.service.impl.AccountServiceImpl">-->
<!--        &lt;!&ndash; set 方式 注入AccountDao对象 &ndash;&gt;-->
<!--        <property name="accountDao" ref="accountDao"/>-->
<!--    </bean>-->
</beans>

思考:在spring的核心配置文件中,还存在着一些配置,并没有全部用注解代替,那么能否基于纯注解的方式呢?

使用上面的注解还不能全替代xml配置文件,还需要使用注解替代的配置:

  • 非自定义的Bean的配置
  • 加载properties文件的配置
  • 组件扫描的配置
  • 引入其他文件
注解 说明
@Configuration 用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解
@Bean 使用在方法上,标注将该方法的返回值存储到Spring容器中
@PropertySource 用于加载properties文件中的配置
@CommponentScan 用于指定Spring在初始化容器时要扫描的包
@import 用于导入其他配置类

创建配置类:

@Configuration //标记该类为核心配置类
@ComponentScan("com.example") // 开启扫描包及子包注解
@PropertySource("classpath:jdbc.properties")// 加载properties文件
public class SpringConfig {

    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean(name = "dataSource") //如果不写id 默认为方法名
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean(name = "queryRunner")
    public QueryRunner getQueryRunner(@Autowired DataSource dataSource) {
        return new QueryRunner(dataSource);
    }
}

测试:AnnotationConfigApplicationContext 加载注解方式的核心配置类

        //纯注解的方式 加载核心配置类
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService service = (AccountService) context.getBean("accountService");
        List<Account> accounts = service.findAll();
        System.out.println(accounts);
        context.close();

配置类拆分,在上述代码中,我们的核心配置类寄存在其他配置和数据库的配置,那么如何将数据库的配置进行拆分呢?和xml方法的配置文件模块化类似。
如下代码:拆分一个数据库配置类

@PropertySource("classpath:jdbc.properties")// 加载properties文件
public class DataSourceConfig {
    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean(name = "dataSource") //如果不写id 默认为方法名
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

核心配置类,通过@Import引入其他的子配置类

@Configuration //标记该类为核心配置类
@ComponentScan("com.example") // 开启扫描包及子包注解
@Import({DataSourceConfig.class}) // 引入子配置类
public class SpringConfig {
    @Bean(name = "queryRunner")
    public QueryRunner getQueryRunner(@Autowired DataSource dataSource) {
        return new QueryRunner(dataSource);
    }
}

Spring 整合Junit

在之前的代码中,通过Junit进行编写测试代码,都需要创建spring的API对象,那么有什么简洁的方法,来方便测试spring的相关代码。

  1. 导入spring-test依赖

spring5及以上版本要求Junit版本在4.12及以上

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
  1. 使用@Runwith注解替换原来的运行器
  2. 使用@ContextConfiguration 指定配置文件或配置类
  3. 使用@Autowired 注入需要测试的对象

    //SpringJUnit4ClassRunner是spring提供的作为junit运行环境的类
    @RunWith(SpringJUnit4ClassRunner.class)
    //设置核心配置类
    @ContextConfiguration(classes = {SpringConfig.class})
    //设置核心配置文件
    //@ContextConfiguration({"classpath:applicationContext.xml"})
    public class Test {
     @Autowired //注入依赖
     AccountService accountService;
    
     @org.junit.Test
     public void test() {
         //XML的方式 加载核心配置文件
    //        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-anno.xml");
    
         //纯注解的方式 加载核心配置类
    //        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    //        AccountService service = (AccountService) context.getBean("accountService");
         List<Account> accounts = accountService.findAll();
         System.out.println(accounts);
     }
    }
    

    知识总结和复习:

  • 需要掌握IOC容器的实现原理
  • Spring相关的API以及标签的相关配置以及bean的生命周期配置
  • Bean的依赖注入 xml方式需要知道
  • Spring核心配置文件常用的标签以及配置文件模块化
  • Spring 注解开发中,常用的注解一定要掌握以及核心配置类中使用到的注解了解即可
  • 实际开发中Spring IOC 通常会采用XML+注解的方式