Spring Framework-IoC详解

IoC的概念和作用

在介绍Ioc之前,我们首先先了解一下以下内容

什么是程序的耦合

耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关 系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立 性)。

在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个 准则就是高内聚低耦合。

分类:

  • 内容耦合: 当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
  • 公共耦合: 两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
  • 外部耦合: 一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
  • 控制耦合: 一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
  • 标记耦合: 若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
  • 数据耦合: 模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
  • 非直接耦合: 两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实 现的。

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:

如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

程序的耦合指的是程序之间的依赖关系。 主要包括:类之间的依赖和方法间的依赖。 而解耦指的就是降低程序间的依赖关系。

案例-早期JDBC连接耦合及解除耦合方式

创建JDBC连接操作数据库时,通过DriverManager及反射的形式注入驱动,展示代码的耦合性。

SQL

CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id主键',
  `account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `createDate` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`, `account`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `account` VALUES (1, 'zhangsan', '张三', '2024-04-26 22:57:14');
INSERT INTO `account` VALUES (2, 'lisi', '李四', '2024-04-26 22:57:32');

maven

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>

测试案例

OldJdbcClientTest.java

import java.sql.*;

public class OldJdbcClientTest {
    public static void main(String[] args) throws Exception {
        //1.注册驱动
        DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb","root","root");
        //3.获取预处理 sql 语句对象
        PreparedStatement preparedStatement = connection.prepareStatement("select * from account;");
        //4.获取结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //5.遍历结果集
        while(resultSet.next()){
            System.out.printf("id:%s-account:%s-name:%s-createDate:%s%n",
                    resultSet.getInt("id"),
                    resultSet.getString("account"),
                    resultSet.getString("name"),
                    resultSet.getDate("createDate"));
        }
        //6.释放资源
        preparedStatement.close();
        connection.close();
    }
}

执行结果与分析

在这里插入图片描述

分析

上述案例,我们描述了程序的耦合,
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
驱动的注入,依赖于com.mysql.cj.jdbc.Driver()这个类,,当我们需要使用其他数据库(例如:oracle)时,则需要修改java代码选择使用Oracle注册。
如果删除mysql依赖则代码在编译器就会报错,找不到com.mysql.cj.jdbc.Driver这个类。而实际开发中,应尽量做到编译期不依赖,运行期才依赖。

解耦思路

  • 使用反射来创建对象,而避免使用new关键字
  • 通过读取配置文件来获取要创建的兑现全路径类名

因此,在实际开发中,我们通常选择使用Class.forName(反射的方式进行注册),将内容耦合转化为数据耦合从而降低代码的耦合性。

示例

我们修改
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());

Class.forName("com.mysql.cj.jdbc.Driver");
后进行测试,可以得到同样的结果

通过反射的方式,我们可以将代码内容的耦合转化为数据耦合,从而降低代码的耦合性

案例2-模拟业务层操作数据层实现保存数据

通过模拟业务层操作数据层,实现数据保存,进一步展示代码的耦合性及解决方案。

实现代码

IAccountDao.java

// DAO 接口
public interface IAccountDao {
    void saveAccount();
}

AccountDaoImpl.java

import main.java.demo1.dao.IAccountDao;

// DAO 接口实现类
public class AccountDaoImpl implements IAccountDao {
    @Override
    public void saveAccount() {
        System.out.println("Account 保存成功");
    }
}

IAccountService.java

// SERVICE 接口
public interface IAccountService {
    void saveAccount();
}

AccountServiceImpl.java

import main.java.demo1.dao.IAccountDao;
import main.java.demo1.dao.impl.AccountDaoImpl;
import main.java.demo1.service.IAccountService;

// SERVICE 接口实现类
public class AccountServiceImpl implements IAccountService {
    IAccountDao iAccountDao = new AccountDaoImpl();
    @Override
    public void saveAccount() {
        iAccountDao.saveAccount();
    }
}

TestClient.java

import main.java.demo1.service.IAccountService;
import main.java.demo1.service.impl.AccountServiceImpl;

public class TestClient {
    public static void main(String[] args) {
        // 创建Service
        IAccountService accountService = new AccountServiceImpl();
        accountService.saveAccount();
    }
}

执行结果

在这里插入图片描述

分析

代码中主要同使用new关键字进行示例的创建,这样大大的增加了类之间的耦合性。

使用反射替代new关键字进行优化

实现步骤:

  • 通过配置文件来配置service和dao,这里我们使用Properties文件进行配置,配置内容:唯一标识() = 全路径类名
  • 通过读取配置文件中配置内容,反射创建对象。
优化代码

beans.properties

accountDao = demo2.dao.impl.AccountDaoImpl
accountService = demo2.service.impl.AccountServiceImpl

BeanFactory.java

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

// 创建Bean对象
public class BeanFactory {
    // 定义一个静态Properties
    private static Properties prop;

    static {
        // 实例化对象
        prop = new Properties();
        // 获取文件流
        try (InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("beans.properties");){
            // 加载prop
            prop.load(is);
        } catch (IOException e) {
            System.err.println("初始化Properties失败");
        }
    }

    // 根据bean名称获取实例对象
    public static Object getBean(String beanName){
        String beanPath= prop.getProperty(beanName);
        Object obj = null;
        try {
            Class<?> aClass = Class.forName(beanPath);
            obj = aClass.getDeclaredConstructor(null).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}

AccountServiceImpl.java

// IAccountDao accountDao = new AccountDaoImpl();
IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

TestClient.java

// IAccountService accountService = new AccountServiceImpl();
IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");

问题:

每次通过BeanFactory获取对象都是通过反射创建的新对象。然而我们更希望的是单例的。

进一步优化

BeanFactory.java

import java.io.InputStream;
import java.util.*;

// 创建Bean对象 
// * bean 在计算机英语中有可重用组件的含义
public class BeanFactory {
    // 定义一个静态Properties
    private static Properties prop;
    // 定义Map存储反射实例
    private static Map<String, Object> beans = new HashMap<String, Object>();

    static {
        // 实例化对象
        prop = new Properties();
        // 获取文件流
        try (InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("beans3.properties");) {
            // 加载prop
            prop.load(is);
            Enumeration<Object> keys = prop.keys();
            while(keys.hasMoreElements()){
                String key =  keys.nextElement().toString();
                // 单例情况下,存在依赖时,必须先初始化依赖
                if("accountService".equals(key)){
                    beans.put("accountDao", Class.forName(prop.getProperty("accountDao")).getDeclaredConstructor().newInstance());
                }
                String path = prop.getProperty(key);
                Object obj = null;
                Class<?> aClass = Class.forName(path);
                obj = aClass.getDeclaredConstructor(null).newInstance();
                beans.put(key, obj);
            }
            System.out.println(beans);
        } catch (Exception e) {
            System.err.println("初始化Properties失败");
        }
    }

    // 根据bean名称获取实例对象
    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }
}

通过以上优化,我们将new关键字创建对象,转换成通过工厂反射创建对象。

什么是IoC

IoC,即Inversion of Control,中文翻译为“控制反转”,是面向对象编程中的一种设计原则和编程思想。其核心思想是将组件之间的依赖关系反转,由外部容器(通常是一个框架或容器)来负责创建和管理这些依赖关系,而不是由组件自身来控制。

正如上述案例,将new关键字创建对象,转换成通过工厂反射创建对象。这种将控制创建对象的权限交给工厂进行统一创建,管理的行为。我们称之为控制反转。

主要作用:

  • 提高代码的松耦合性和可维护性: 在IoC中,组件之间的依赖关系被解耦,组件不再直接创建或引用其他组件,而是由IoC容器来负责这些关系的创建和管理。这使得代码更加灵活,更易于维护和测试。
  • 实现单一职责原则: 每个组件只关注自己的业务逻辑,而不需要关心如何创建或获取其他组件。这有助于简化代码,提高代码的可读性和可重用性。
  • 降低组件之间的耦合度: 由于IoC将依赖关系从组件中解耦,组件之间的耦合度降低,使得代码更加灵活和可测试。

消减(不是消除,而是降低)程序的耦合(解除我们代码中的依赖关系)

IoC的最常见实现方式是依赖注入(Dependency Injection,简称DI)。在依赖注入中,对象在被创建时,其依赖的对象引用由外部实体(如IoC容器)注入到该对象中,而不是由对象自身去创建或查找这些依赖。

案例3-spring创建管理bean-基于XML配合

将上述案例,通过模拟业务层操作数据层,实现数据保存,创建bean的方式交给spring进行创建及管理(基于XML配置)

引入maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.5</version>
</dependency>

实现代码

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来管理 -->
    <bean id="accountDao" class="demo1.dao.impl.AccountDaoImpl"></bean>
    <bean id="accountService" class="demo1.service.impl.AccountServiceImpl">
    </bean>
</beans>

TestClient.java

import demo1.dao.IAccountDao;
import demo1.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 获取通过 ClassPathXmlApplicationContext(默认在resources目录下查找文件) 获取 ApplicationContext
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        // 创建Service
        IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
        IAccountDao accountDao = (IAccountDao) applicationContext.getBean("accountDao");
        System.out.println("accountService = " + accountService);
        System.out.println("accountDao = " + accountDao);
    }
}

执行结果:

在这里插入图片描述

通过以上配置,我们已经可以通过加载spring来获取我们配置交给spring创建和管理的bean的实例

spring中BeanFactory 和 ApplicationContext

UML

在这里插入图片描述

从图中我们可以看出,BeanFactory 才是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。

区别: 创建对象的时间点不一样 。

  • ApplicationContext:在构件核心容器时,创建对象采取的策略是立即加载的方式。也就是说,只要已读取完配置文件马上就创建配置文件中的对象。
  • BeanFactory:在构件核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候使用,什么时候才真正的创建对象。

测试代码

根据反射创建对象时,会默认调用对象无参构造函数这一特性展开测试。
AccountServiceImpl.java

public AccountServiceImpl() {
    System.out.println("AccountServiceImpl 被创建了");
}

AccountServiceImpl添加无参构造函数,被调用是输出内容

ApplicationContext方式

TestClient.java

import demo2.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 通过ApplicationContext获取spring bean创建并管理的对象
        System.out.println("=======1. 通过ApplicationContext获取spring bean创建并管理的对象 =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean2.xml");
        System.out.println("=======2. 获取AccountService =======");
        // 获取AccountDao
        IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
        System.out.println("accountService = " + accountService);
    }
}

执行结果:
在这里插入图片描述

从执行结果可以看出,ApplicationContext读取完配置后立即创建了对象

BeanFactory方式

TestClient2.java

import demo2.service.IAccountService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;

public class TestClient2 {
    public static void main(String[] args) {
        // 获取BeanFactory获取获取spring bean创建并管理的对象
        System.out.println("=======1. 通过BeanFactory获取获取spring bean创建并管理的对象 =======");
        // 该方式已不推荐使用,这里只为展示区别使用
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean2.xml"));
        System.out.println("=======2. 获取AccountService =======");
        // 获取AccountDao
        IAccountService accountService = (IAccountService) beanFactory.getBean("accountService");
        System.out.println("accountService = " + accountService);
    }
}

执行结果:

在这里插入图片描述

从执行结果可以看出,BeanFactory在使用时才会真正的创建对象

ApplicationContext三个常用实现类

  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件 推荐使用这种
  • FileSystemXmlApplicationContext: 从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
  • AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

测试代码

TestClient.java

import demo3.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过ClassPathXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml");
        System.out.println("=======2. 获取AccountService =======");
        // 获取AccountDao
        IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
        System.out.println("accountService = " + accountService);
        System.out.println("");
        System.out.println("================================================================================");
        System.out.println("");
        System.out.println("=======1. 通过FileSystemXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("D:/temp/bean3.xml");
        System.out.println("=======2. 获取AccountService =======");
        // 获取AccountDao
        IAccountService accountService2 = (IAccountService) applicationContext.getBean("accountService");
        System.out.println("accountService2 = " + accountService2);
    }
}

执行结果:
在这里插入图片描述

这里暂时只演示ClassPathXmlApplicationContext,FileSystemXmlApplicationContext方式,AnnotationConfigApplicationContext(讲注解是说明)

IOC 中 bean 标签和管理对象细节

什么是bean标签

作用:
  • 用于配置对象让 spring 来创建的。
  • 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
测试没有无参构造函数则不能创建成功

UserDo.java

// UserDo
// * 提供有参构造方法,但是不重新无参构造方法
public class UserDo {
    private String name;
    public UserDo(String name) {
        this.name = name;
    }
}

bean4-01.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来管理 -->
    <bean id="userDo" class="demo4.entity.UserDo"></bean>
</beans>

TestClient.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过ClassPathXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean4-01.xml");
    }
}

执行结果:
在这里插入图片描述

属性:
属性描述可选值
id给对象在容器中提供一个唯一标识。用于获取对象。默认情况下调用无参构造函数。-
class指定类的全路径类名,用于反射创建对象。默认情况下调用无参构造函数。-
scope指定对象的作用范围singleton :默认值,单例的.
--prototype :多例的.
--request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
--session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
--global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.
init-method指定类中的初始化方法名称。-
destroy-method指定类中销毁方法名称。-

创建 Bean 的三种方式

基础对象如下:
AccountDo.java

package demo11.entity;
public class AccountDo {
    private String name;
    public AccountDo(String name) {
        this.name = name;
        System.out.println("使用有参构造函数注入-name = " + name);
    }
}

IAccountDao.java

package demo11.dao;

public interface IAccountDao {
    void saveAccount();
}

AccountDaoImpl.java

package demo11.dao.impl;

import demo11.dao.IAccountDao;
import org.springframework.stereotype.Repository;

public class AccountDaoImpl implements IAccountDao {

  public AccountDaoImpl() {
    System.out.println(" 调用 AccountDaoImpl 默认构造函数");
  }
  @Override
  public void saveAccount() {
    System.out.println("AccountDaoImpl Account 保存成功");
  }
}
使用构造函数创建Bean

bean11-01.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-->
    <!-- 1 使用默认构造函数创建 (注:当创建有参构造器,但没有重写默认构造函数时,将会报错)-->
    <bean id="accountDaoImpl" class="demo11.dao.impl.AccountDaoImpl"></bean>
    <!-- 2 使用有参构造函数创建 -->
    <bean id="accountDo" class="demo11.entity.AccountDo">
        <constructor-arg name="name" value="张三"></constructor-arg>
    </bean>
</beans>

TestClient.java

package demo11;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean11-01.xml");
    }
}

执行结果:

在这里插入图片描述

使用简单工厂中的方法创建Bean

InstanceFactory.java

package demo11.factory;

import demo11.dao.IAccountDao;
import demo11.dao.impl.AccountDaoImpl;

public class InstanceFactory {

    public IAccountDao getAccountService(){
        System.out.println("使用类中方法创建Bean");
        return new AccountDaoImpl();
    }
}

bean11-02.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="instanceFactory" class="demo11.factory.InstanceFactory"></bean>
    <bean id="accountDao" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
</beans>

TestClient2.java

package demo11;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient2 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean11-02.xml");
    }
}

执行结果:

在这里插入图片描述

使用工厂中的静态方法创建Bean

StaticFactory.java

package demo11.factory;


import demo11.dao.IAccountDao;
import demo11.dao.impl.AccountDaoImpl;

public class StaticFactory {

    public static IAccountDao getAccountService(){
        System.out.println("使用类中的静态方法创建bean");
        return new AccountDaoImpl();
    }
}

bean11-03.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="accountDao" class="demo11.factory.StaticFactory" factory-method="getAccountService"></bean>
</beans>

TestClient3.java

package demo11;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient3 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean11-03.xml");
    }
}

执行结果:

在这里插入图片描述

bean 的作用范围和生命周期

  • 单例对象
    • 出生:当容器创建时,对象出生,并且会调用init()方法
    • 活着:只要容器还在,对象就一直存在
    • 死亡:容器销毁时,对象消亡,并且会调用destory()方法

      单例对象的生命周期和容器相同

  • 多例对象
    • 出生:当我们使用对象时spring框架为我们创建,并且会调用init()方法
    • 活着:对象只要是在使用过程中就一直活着
    • 死亡:当对象长时间不使用,且没有别的对象引用时,由Java的垃圾回收器回收
案例-测试spring bean的生命周期

bean4-02.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:给对象在容器中提供一个唯一标识。用于获取对象。默认情况下调用无参构造函数。
        class:指定类的全路径类名,用于反射创建对象
        scope:指定对象的作用范围
            singleton : 默认值,单例的.
            prototype : 多例的.
            request : WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
            session : WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
            global session : WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.
        init-method: 指定类中的初始化方法名称
        destroy-method: 指定类中销毁方法名称
     -->
    <bean id="accountDo" class="demo4.entity.AccountDo" init-method="init" destroy-method="destroy"></bean>
    <bean id="accountDo2" class="demo4.entity.AccountDo2" scope="prototype" init-method="init" destroy-method="destroy"></bean>
</beans>

AccountDo.java

public class AccountDo {
    public AccountDo() {
        System.out.println("执行 AccountDo 无参构造方法");
    }
    public void init(){
        System.out.println("执行 AccountDo - init()方法");
    }
    public void destroy(){
        System.out.println("执行 AccountDo - destroy()方法");
    }
}

AccountDo2.java

public class AccountDo2 {
    public AccountDo2() {
        System.out.println("执行 AccountDo2 无参构造方法");
    }
    public void init(){
        System.out.println("执行 AccountDo2 - init()方法");
    }
    public void destroy(){
        System.out.println("执行 AccountDo2 - destroy()方法");
    }
}

TestClient2.java

import demo4.entity.AccountDo;
import demo4.entity.AccountDo2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient2 {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======获取ApplicationContext=======");
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean4-02.xml");
        System.out.println("======scope:singleton单例=========");
        AccountDo accountDo1 = (AccountDo) ac.getBean("accountDo");
        AccountDo accountDo2 = (AccountDo) ac.getBean("accountDo");
        System.out.printf("对比获取bean是否是同一对象:%s%n",accountDo1 == accountDo2);
        System.out.printf("手动关闭容器-触发销毁方法%n");
        ac.destroy();
        System.out.println("");
        System.out.println("=======获取ApplicationContext =======");
        ClassPathXmlApplicationContext ac2 = new ClassPathXmlApplicationContext("bean4-02.xml");
        System.out.println("======scope:prototype多例=========");
        AccountDo2 accountDo3 = (AccountDo2) ac2.getBean("accountDo2");
        AccountDo2 accountDo4 = (AccountDo2) ac2.getBean("accountDo2");
        System.out.printf("对比获取bean是否是同一对象:%s%n",accountDo3 == accountDo4);
        System.out.printf("手动关闭容器-触发销毁方法%n");
        ac2.destroy();
    }
}

执行结果:

在这里插入图片描述

单例对象:容器在创建时创建对象,并调用init()方法,容器销毁时调用destory()方法,且每次获取的都是同一对象。

多例对象:容器在创建时并不会创建对象,在获取时会创建对象,并调用init()方法,容器销毁时,不会执行destory()方法,多例对象的销毁由GC管理,并且每次获取的对象不是同一个对象。

spring中的依赖注入

什么是依赖注入

在当前类需要用到其他类的对象,有spring为我们提供,我们只需要在配置文件中说明依赖关系的维护就称之为依赖注入(Dependency Injection,简称DI)。

依赖注入是控制反转(IoC)的一种具体方式。其主要目的是降低代码之间的耦合度,提高系统的可维护性和可测试性。

能注入的数据,可分为三类:

  • 基本数据类型及String
  • 其他bean类型(在配置文件中或者注解配置过的bean)
  • 复杂类型/集合类型

注入的方式,可分为三类:

  • 使用构造函注入
  • 使用set方法注入
  • 使用注解注入(注解时说明)
构造函数注入

AccountDo.java

import java.util.Date;

public class AccountDo {
    private Integer id;
    private String name;
    private Date createDate;
    public AccountDo(Integer id, String name, Date createDate) {
        System.out.println("使用构造函数注入");
        this.id = id;
        this.name = name;
        this.createDate = createDate;
    }
    @Override
    public String toString() {
        return "AccountDo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", createDate=" + createDate +
                '}';
    }
}

bean5-01.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">

    <!-- 构造函数注入:
        使用的标签:constructor-arg
        标签出现的位置:bean标签内部
        标签中的属性:
            type: 用于指定要注入的数据类型,该数据类型也是构造函数中的某个/某些参数的数类型。
            index: 用于指定需要注入的数据给构造函数中指定索引的参数赋值。索引的位置从0开始。
            name: 用于指定给构造函数中指定名称的参数赋值
            value: 用于提供基本类型和String类型的数据
            ref: 用于指定其他的bean类型数据。它指的就是在Spring的Ioc核心容器中出现过的bean对象
     -->
    <!-- 构造函数注入 - type -->
    <bean id="accountDo" class="demo5.entity.AccountDo">
        <constructor-arg type="java.lang.Integer" value="1"></constructor-arg>
        <constructor-arg type="java.lang.String" value="张三"></constructor-arg>
        <constructor-arg type="java.util.Date" ref="now"></constructor-arg>
    </bean>

    <!-- 构造函数注入 - index -->
    <bean id="accountDo1" class="demo5.entity.AccountDo">
        <constructor-arg index="0" value="2"></constructor-arg>
        <constructor-arg index="1" value="李四"></constructor-arg>
        <constructor-arg index="2" ref="now"></constructor-arg>
    </bean>

    <!-- 构造函数注入 - name -->
    <bean id="accountDo2" class="demo5.entity.AccountDo">
        <constructor-arg name="id" value="3"></constructor-arg>
        <constructor-arg name="name" value="王五"></constructor-arg>
        <constructor-arg name="createDate" ref="now"></constructor-arg>
    </bean>

    <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>
</beans>

TestClient.java

import demo5.entity.AccountDo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过ClassPathXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean5-01.xml");
        // 输出AccountDo、
        System.out.println("=======构造函数注入 - type =======");
        AccountDo accountDo = (AccountDo) applicationContext.getBean("accountDo");
        System.out.println(accountDo);

        System.out.println("=======构造函数注入 - index =======");
        AccountDo accountDo1 = (AccountDo) applicationContext.getBean("accountDo1");
        System.out.println(accountDo1);

        System.out.println("=======构造函数注入 - name =======");
        AccountDo accountDo2 = (AccountDo) applicationContext.getBean("accountDo2");
        System.out.println(accountDo2);
    }
}

执行结果:

在这里插入图片描述

优势:在获取bean时,注入数据是必须的操作,否则对象无法创建成功

弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供

set方法注入

AccountDo2.java

import java.util.Date;

public class AccountDo2 {
    private Integer id;
    private String name;
    private Date createDate;
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
    @Override
    public String toString() {
        return "AccountDo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", createDate=" + createDate +
                '}';
    }
}

bean5-02.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">

    <!-- set方法注入:
        使用的标签:property
        标签出现的位置:bean标签内部
        标签中的属性:
            name: 用于指定注入时所调用的set方法名称
            value: 用于提供基本类型和String类型的数据
            ref: 用于指定其他的bean类型数据。它指的就是在Spring的Ioc核心容器中出现过的bean对象
     -->
    <!-- 构造函数注入 - type -->
    <bean id="accountDo2" class="demo5.entity.AccountDo2">
        <property name="id" value="1" />
        <property name="name" value="张三" />
        <property name="createDate" ref="now" />
    </bean>

    <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>
</beans>

TestClient2.java

import demo5.entity.AccountDo2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient2 {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过ClassPathXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean5-02.xml");
        // 输出AccountDo2
        System.out.println("=======set方法注入 =======");
        AccountDo2 accountDo2 = (AccountDo2) applicationContext.getBean("accountDo2");
        System.out.println(accountDo2);

    }
}

执行结果:

在这里插入图片描述

优势: 创建对象时没有明确的限制,可以直接使用默认构造函数

弊端: 如果有某个成员必须有值,如果没有配置该属性的set注入,则该值会是空值

使用 p 名称空间注入数据

此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的
set 方法实现注入功能。本质还是set方法注入

AccountDo.java

import java.util.Date;

public class AccountDo {
    private Integer id;
    private String name;
    private Date createDate;
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
    @Override
    public String toString() {
        return "AccountDo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", createDate=" + createDate +
                '}';
    }
}

bean6-01.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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDo" class="demo6.entity.AccountDo"
        p:id="1" p:name="张三" p:createDate-ref="now"
    ></bean>
    <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>
</beans>

引入p命名空间xmlns:p="http://www.springframework.org/schema/p"

TestClient.java

import demo6.entity.AccountDo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过ClassPathXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean6-01.xml");
        AccountDo accountDo = (AccountDo) applicationContext.getBean("accountDo");
        System.out.println(accountDo);
    }
}

执行结果:

在这里插入图片描述

IOC中的注解

注解的分类

注解按作用可以分为以下几类:

  • 用于创建对象: 与在XML配置文件中编写一个标签实现的功能是一样的
  • 用于注入数据: 与在XML配置文件中写一个标签的作用是一样的
  • 用于改变作用范围: 与在bean标签中使用scope属性实现的功能是一样的
  • 和生命周偶器相关: 与在bean标签中使用init-method和destory-method的作用是一样的
分类注解作用属性
用于创建对象---
-@Component用于把当前类对象存入spring容器中value:用于指定bean的ID(唯一ID)。当我们不写时,它的默认值就是当前类名,且首字母小写
-@Controller同@Component同@Component,一般用于表现层的注解。
-@Service同@Component同@Component,一般用于业务层的注解。
-@Repository同@Component同@Component,一般用于持久层的注解。
-@Configuration用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)。value:用于指定配置类的字节
-@ComponentScan用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="com.itheima"/>是一样的。basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
-@Bean该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
-@Import用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。value[]:用于指定其他配置类的字节码。
用于注入数据---
-@Autowired自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错-
-@Qualifier在按照类型注入的基础上再加上按照名称注入。它在给类成员注入是不能单独使用,但是在给方法参数注入时可以单独使用value:用于指定注入的bean的id
-@Resource直接按照bean的id注入。它可以独立使用name:用于指定bean的id
-@Value用于注入基本类型和String类型的数据value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)SpEL的写法:${表达式}
-@PropertySource用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
用于改变作用范围---
-@Scope相当于:用于指定 bean 的作用范围。value:指定范围的值。取值:singleton(默认值) prototype request session globalsession
和生命周偶器相关---
-@PostConstruct用于指定初始化方法。与在bean标签中使用init-method的作用一样-
-@PreDestroy用于指定初始化方法。与在bean标签中使用destory-method的作用一样-

当我们在使用注解时,需要告知spring在创建容器时需要扫描的包,配置所需的标签不是在beans的约束,而是一个名称为context名称空间和约束中。
当然我们可以通过XML配置文件的方式告知,也可以通过注解额的形式告知。

@Controller @Service @Repository:他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。

如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。例如:@Component(value = "account2") 等价于 @Component("account2")

@Autowired @Qualifier @Resource 都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。 另外,集合类型的注入只能通过XML来实现。

测试案例-@Component注解

AccountDo.java

import org.springframework.stereotype.Component;

@Component
public class AccountDo {
}

AccountDo2.java

import org.springframework.stereotype.Component;

@Component(value = "account2")
public class AccountDo2 {

}

bean7-01.xml

import demo7.entity.AccountDo;
import demo7.entity.AccountDo2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        // 通过ClassPathXmlApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过ClassPathXmlApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean7-01.xml");
        AccountDo accountDo = (AccountDo) applicationContext.getBean("accountDo");
        System.out.println(accountDo);
        AccountDo2 accountDo2 = (AccountDo2) applicationContext.getBean("account2");
        System.out.println(accountDo2);
    }
}

执行结果:
在这里插入图片描述

上述案例通过,xml形式告知扫描包,当然我们也可以直接通过AnnotationConfigApplicationContext获取,当然也可以通过注解形式配置扫描路径(后续讲解)

TestClient2.java

import demo7.entity.AccountDo;
import demo7.entity.AccountDo2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestClient2 {
    public static void main(String[] args) {
        // 通过AnnotationConfigApplicationContext获取ApplicationContext
        System.out.println("=======1. 通过AnnotationConfigApplicationContext 获取ApplicationContext =======");
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("demo7.entity");
        AccountDo accountDo = (AccountDo) applicationContext.getBean("accountDo");
        System.out.println(accountDo);
        AccountDo2 accountDo2 = (AccountDo2) applicationContext.getBean("account2");
        System.out.println(accountDo2);
    }
}

执行结果:

在这里插入图片描述

测试案例-@Controller @Service @Repository @Autowired注解使用

模拟账户保存
AccountController.java

package demo8.controller;

import demo8.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class AccountController {
    @Autowired
    private IAccountService accountService;
    public void saveAccount(){
        accountService.saveAccount();
    }
}

IAccountService.java

package demo8.service;
// SERVICE 接口
public interface IAccountService {
    void saveAccount();
}

AccountServiceImpl.java

package demo8.service.impl;

import demo8.dao.IAccountDao;
import demo8.dao.impl.AccountDaoImpl;
import demo8.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// SERVICE 接口实现类
@Service
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

IAccountDao.java

package demo8.dao;
// DAO 接口
public interface IAccountDao {
    void saveAccount();
}

AccountDaoImpl.java

package demo8.dao.impl;
import demo8.dao.IAccountDao;
import org.springframework.stereotype.Repository;
// DAO 接口实现类
@Repository
public class AccountDaoImpl implements IAccountDao {

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

TestClient.java

package demo8;

import demo8.controller.AccountController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestClient {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("demo8");
        AccountController accountController = (AccountController) context.getBean("accountController");
        accountController.saveAccount();
    }
}

执行结果:

在这里插入图片描述

测试案例-@Autowired 自动按照类型注入时,Spring核心容器中包含两个相同类型

案例

IAccountService.java

package demo9.service;

// SERVICE 接口
public interface IAccountService {
    void saveAccount();
}

AccountServiceImpl.java

package demo9.service.impl;

import demo9.dao.IAccountDao;
import demo9.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// SERVICE 接口实现类
@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

IAccountDao.java

package demo9.dao;

// DAO 接口
public interface IAccountDao {
    void saveAccount();
}

AccountDaoImpl1.java

package demo9.dao.impl;

import demo9.dao.IAccountDao;
import org.springframework.stereotype.Repository;

// DAO 接口实现类
@Repository
public class AccountDaoImpl1 implements IAccountDao {
    @Override
    public void saveAccount() {
        System.out.println("AccountDaoImpl1 Account 保存成功");
    }
}

AccountDaoImpl2.java

package demo9.dao.impl;

import demo9.dao.IAccountDao;
import org.springframework.stereotype.Repository;

// DAO 接口实现类
@Repository
public class AccountDaoImpl2 implements IAccountDao {
    @Override
    public void saveAccount() {
        System.out.println("AccountDaoImpl2 Account 保存成功");
    }
}

TestClient.java

package demo9;

import demo9.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestClient {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("demo9");
        IAccountService accountService = (IAccountService) context.getBean("accountService");
        accountService.saveAccount();
    }
}

执行结果:

在这里插入图片描述

从异常可以看出,当Spring容器同时存在两个相同数据类型时,@Autowired会根据变量名称,进一步查找容器中是否有相同名称的bean id与之匹配,如果找不到匹配数据时,则抛出异常。

方式一: 通过修改变量名称处理,Sring核心容器中存在多个相同类型,@Autowired按类型注入时异常问题

修改AccountServiceImpl中注入IAccountDao变量名称为accountDaoImpl2
AccountServiceImpl.java

import demo10.dao.IAccountDao;
import demo10.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDaoImpl2;
    @Override
    public void saveAccount() {
        accountDaoImpl2.saveAccount();
    }
}

执行结果:

在这里插入图片描述

方式二: 通过追加@Qualifier注解处理

AccountServiceImpl2.java

package demo10.service.impl;

import demo10.dao.IAccountDao;
import demo10.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl2 implements IAccountService {

    @Autowired
    @Qualifier("accountDaoImpl2")
    private IAccountDao accountDao;

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

TestClient2.java

package demo10;

import demo10.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestClient2 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("demo10");
        IAccountService accountService = (IAccountService) context.getBean("accountServiceImpl2");
        accountService.saveAccount();
    }
}

执行结果:

在这里插入图片描述

方式三:使用@Resource注解,通过名称注入

AccountServiceImpl3.java

package demo10.service.impl;

import demo10.dao.IAccountDao;
import demo10.service.IAccountService;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

// SERVICE 接口实现类
@Service("accountServiceImpl3")
public class AccountServiceImpl3 implements IAccountService {

    @Resource(name = "accountDaoImpl2")
    private IAccountDao accountDao;

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

TestClient3.java

package demo10;

import demo10.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestClient3 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("demo10");
        IAccountService accountService = (IAccountService) context.getBean("accountServiceImpl3");
        accountService.saveAccount();
    }
}

执行结果:
在这里插入图片描述

注意:

@Resource注解位于模块java.xml.ws.annotation中。因为它是一个Java EE模块,所以不推荐使用它。在Java 9中删除了,并且默认情况下不会解析,因此需要手动添加它–add-modules。
在Java 10中,模块将完全消失,并且–add-modules会失败,因为java.xml.ws.annotation不再存在。最好的解决方案是立即用第三方依赖替换它。使用Java Commons Annotations可以在Maven Central上找到它:

<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
  <version>1.3.1</version>
</dependency>

Spring注解综合运用

使用dbutils连接数据库mybatis,对Account表做查询

maven
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.22</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.3.22</version>
</dependency>
<dependency>
  <groupId>commons-dbutils</groupId>
  <artifactId>commons-dbutils</artifactId>
  <version>1.4</version>
</dependency>

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

<dependency>
  <groupId>c3p0</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.9.1.2</version>
</dependency>

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

account.sql

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id主键',
  `account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账号',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `createDate` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`, `account`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `account` VALUES (1, 'zhangsan', '张三', '2024-04-26 22:57:14');
INSERT INTO `account` VALUES (2, 'lisi', '李四', '2024-04-26 22:57:32');

SET FOREIGN_KEY_CHECKS = 1;
实现代码

AccountDo.java

package demo12.entity;

import java.util.Date;

public class AccountDo {

    private Integer id;
    private String account;
    private String name;
    private Date createDate;
    
    // get/set ...

    @Override
    public String toString() {
        return "AccountDo{" +
                "id=" + id +
                ", account='" + account + '\'' +
                ", name='" + name + '\'' +
                ", createDate=" + createDate +
                '}';
    }
}

IAccountDao.java

package demo12.dao;

import demo12.entity.AccountDo;
import java.util.List;

public interface IAccountDao {
    // 根据id查询
    AccountDo findAccountDoById(Integer id);
    // 查询所有
    List<AccountDo> findAllAccount();
    // 保存
    void saveAccount(AccountDo account);
    // 更新
    void updateAccount(AccountDo account);
    // 删除
    void deleteAccount(Integer id);
}

AccountDaoImpl.java

package demo12.dao.impl;

import demo12.dao.IAccountDao;
import demo12.entity.AccountDo;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private QueryRunner runner;

    @Override
    public AccountDo findAccountDoById(Integer id) {
        try {
            return runner.query("select * from account where id = ? ", new BeanHandler<AccountDo>(AccountDo.class), id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public List<AccountDo> findAllAccount() {
        try{
            return runner.query("select * from account",new BeanListHandler<AccountDo>(AccountDo.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void saveAccount(AccountDo account) {
        try{
            runner.update("insert into account(account,name,createDate)values(?,?,?)",account.getAccount(),account.getName(),account.getCreateDate());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void updateAccount(AccountDo account) {
        try{
            runner.update("update account set account=?,name=? where id=?",account.getAccount(),account.getName(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void deleteAccount(Integer id) {
        try{
            runner.update("delete from account where id=?",id);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

IAccountService.java

package demo12.service;

import demo12.entity.AccountDo;

public interface IAccountService {
    AccountDo findAccountDoById(Integer id);
}

AccountServiceImpl.java

package demo12.service.impl;

import demo12.dao.IAccountDao;
import demo12.entity.AccountDo;
import demo12.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    @Override
    public AccountDo findAccountDoById(Integer id) {
        return accountDao.findAccountDoById(id);
    }
}

AccountController.java

package demo12.controller;

import demo12.entity.AccountDo;
import demo12.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class AccountController {

    @Autowired
    private IAccountService accountService;

    public AccountDo findAccountDoById(Integer id) {
        return accountService.findAccountDoById(id);
    }
}

JdbcConfig.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/testdb
jdbc.username=root
jdbc.password=root

DataSourceConfig.java

package demo12.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;

@PropertySource("classpath:JdbcConfig.properties")
public class DataSourceConfig {
    @Value("${jdbc.driver}")
    private String driver;

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

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

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

    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name="runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="dataSource")
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

这里展示@Qualifier可以单独用在方法上,可以用于指定注入的多数据源的指定bean的id

SpringConfig.java

package demo12.config;

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

@Configuration
@ComponentScan(basePackages = "demo12")
@Import(DataSourceConfig.class)
public class SpringConfig {
}
使用AnnotationConfigApplicationContext获取Spring核心容器执行测试

TestClient.java

package demo12;

import demo12.config.SpringConfig;
import demo12.controller.AccountController;
import demo12.entity.AccountDo;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestClient {

    @Test
    public void testQuery(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountController accountController = (AccountController) context.getBean("accountController");
        AccountDo accountDo = accountController.findAccountDoById(1);
        System.out.println(accountDo);
    }
}

执行结果:

在这里插入图片描述

使用junit配置spring环境进行注入

TestClient2.java

package demo12;

import demo12.config.SpringConfig;
import demo12.controller.AccountController;
import demo12.entity.AccountDo;
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.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 使用Junit单元测试:测试我们的配置
 * Spring整合junit的配置
 *      1、导入spring整合junit的jar(坐标)
 *      2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
 *             @Runwith
 *      3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
 *          @ContextConfiguration
 *                  locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
 *                  classes:指定注解类所在地位置
 *
 *   当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class TestClient2 {

    @Autowired
    private AccountController accountController;

    @Test
    public void testQuery(){
        AccountDo accountDo = accountController.findAccountDoById(1);
        System.out.println(accountDo);
    }

}

执行结果:

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/610721.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java毕业设计 基于SpringBoot vue新能源充电系统

Java毕业设计 基于SpringBoot vue新能源充电系统 SpringBoot 新能源充电系统 功能介绍 首页 图片轮播 充电桩 充电桩类型 充电桩详情 充电桩预约 新能源公告 公告详情 登录注册 个人中心 余额充值 修改密码 充电桩报修 充电桩预约订单 客服 后台管理 登录 个人中心 修改密码…

【Linux】模拟实现bash(简易版)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

redis深入理解之数据存储

1、redis为什么快 1&#xff09;Redis是单线程执行&#xff0c;在执行时顺序执行 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线…

权力集中,效率提升,中心化模式的优势与挑战

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;权力集中…

Microsoft Project使用简明教程

一.认识Microsoft Project Microsoft Project 是微软公司开发的项目管理软件&#xff0c;用于规划、协调和跟踪项目的进度、资源和预算&#xff0c;如下图所示&#xff0c;左边是任务的显示&#xff0c;右边是一个日程的显示图&#xff0c;最上方的长方形处在我们项目设定日程…

【oracle数据库安装篇三】Linux6.8单机环境oracle11g容灾ADG搭建

说明 DataGuard 是在主节点与备用节点间通过日志同步来保证数据的同步&#xff0c;可以实现数据库快速切换与灾难性恢复。用户能够在对主数据库影响很小的情况下&#xff0c;实现主备数据库的同步。 关联文章 【oracle数据库安装篇一】Linux5.6基于LVM安装oracle11gR2单机 【…

Pandas数据取值与选择

文章目录 第1关&#xff1a;Series数据选择第2关&#xff1a;DataFrame数据选择方法 第1关&#xff1a;Series数据选择 编程要求 本关的编程任务是补全右侧上部代码编辑区内的相应代码&#xff0c;要求实现如下功能&#xff1a; 添加一行数据&#xff0c;时间戳2019-01-29值为…

vue开发网站—①调用$notify弹窗、②$notify弹窗层级问题、③js判断两个数组是否相同等。

一、vue中如何使用vant的 $notify&#xff08;展示通知&#xff09; 在Vue中使用Vant组件库的$notify方法来展示通知&#xff0c;首先确保正确安装了Vant并在项目中引入了Notify组件。 1.安装vant npm install vant --save# 或者使用yarn yarn add vant2.引入&#xff1a;在ma…

自存angular 自定义snackbar

定义 1.自定义样式 2.自定义组件 就在要使用snackbar的组件中 在module中引入该组件&#xff08;重新写一个组件也行的 直接引入就好&#xff09; 打开这个组件 给这个自定义的组件传参 这个自定义组件接参(类似对话框接参) 使用参数 在这个自定义组件中 做了点击如何关闭s…

企业信使运营管理平台功能介绍

企业信使运营管理平台是一种为企业提供内部协同、任务管理、沟通交流、文件共享等功能的综合性管理平台。该平台旨在提高企业内部的工作效率和沟通协作能力&#xff0c;提供便捷的工作管理工具&#xff0c;促进企业的业务发展。 内部协同功能 企业信使运营管理平台首先提供一种…

Navicat Data Modeler Ess for Mac:强大的数据库建模设计软件

Navicat Data Modeler Ess for Mac是一款专为Mac用户设计的数据库建模与设计工具&#xff0c;凭借其强大的功能和直观的界面&#xff0c;帮助用户轻松构建和管理复杂的数据库模型。 Navicat Data Modeler Ess for Mac v3.3.17中文直装版下载 这款软件支持多种数据库系统&#x…

android进阶-AIDL

参考&#xff1a;Android进阶——AIDL详解_android aidl-CSDN博客 AIDL&#xff08;Android 接口定义语言&#xff09;&#xff0c;可以使用它定义客户端与服务端进程间通信&#xff08;IPC&#xff09;的编程接口&#xff0c;在 Android 中&#xff0c;进程之间无法共享内存&…

全视通助力珠海市井岸镇卫生院新院,建设智慧病房

5月6日&#xff0c;位于珠海市斗门区的井岸镇卫生院新院正式启用&#xff0c;面向市民开诊。新院各诊区就医秩序井然&#xff0c;总体情况良好。据统计&#xff0c;截至开诊当天11点30分&#xff0c;新院门诊共接诊347人次&#xff0c;预防接种81人次&#xff0c;儿童体检33人次…

Docker快速搭建NAS服务——NextCloud

Docker快速搭建NAS服务——NextCloud 文章目录 前言NextCloud的搭建docker-compose文件编写运行及访问 总结 前言 本文主要讲解如何使用docker在本地快速搭建NAS服务&#xff0c;这里主要写如下两种&#xff1a; FileBrowser1&#xff1a;是一个开源的Web文件管理器&#xff…

effective python学习笔记_类与接口

用组合类实现多层结构而不用内置类型 例子&#xff1a;成绩单&#xff0c;存储学生各科成绩多个然后加权重&#xff0c;如果用字典类型会导致字典有多层嵌套结构 思想 当用内置类型如字典元组等结构出现超过二层的多层嵌套结构时&#xff0c;读起来会比较难懂&#xff0c;此时…

新能源 锂电池行业创业的财富方案,锂电池回收实战攻略课(36节课)

实战攻略 12年锂电池回收行业经验与坑全收录 课程内容&#xff1a; 001-课程介绍.mp4 002-锂电池的全种类认识.mp4 003-废品锂电池到级片粉末价值估算,mp4 004-锂电池的生产应用回收,mp4 005-梯次回收到粉未提纯全流程,mp4 006-锂电池行业术语,mp4 007-回收所需必备工具…

汉诺塔问题和爬楼梯(递归)

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 c语言基础_LaNzikinh篮子的博客-CSDN博客 文章目录 一.爬楼梯问题二.汉诺塔问题总结 一.爬楼梯问题 假设你正…

Ansys界面设计:ACT入门

来自官方文档Getting Started with ACT&#xff0c;机翻。 Ansys 提供一流的现成仿真技术。为了最有效地部署普遍模拟&#xff0c;您可能需要更精心策划的体验&#xff0c;以使我们的模拟专业知识与您的用户、公司或行业需求相匹配。 Ansys ACT 使您能够自定义和扩展 Ansys 体验…

java注解全网最细

引言 在java编程中&#xff0c;注解&#xff08;Annotation&#xff09;是一种元数据&#xff0c;它提供了关于程序代码的额外信息。注解不直接影响程序的执行&#xff0c;但可以在运行时提供有关程序的信息&#xff0c;或者让编译器执行额外的检查。 下面笔者通过循序渐进的…

快速上手文心一言指令

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…
最新文章