Spring核心-控制反转(IOC)

发布于:2022-11-13 ⋅ 阅读:(939) ⋅ 点赞:(0)

文章目录

一、IOC介绍

控制反转(IoC,Inversion of Control)是一个概念,一种思想。它的目的是指导我们设计出更加松耦合的程序,更加有利于程序维护。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。

控制与反转:

  • 控制(Control):对象的创建

    手动创建对象每次使用都需要 new,可能导致重复创建耗费资源且不方便切换实现类(耦合度高)。

  • 反转(Inversion):对象的创建由开发者在类中手动 new 反转到由 Spring 容器创建

    所有创建出来的实例都交给 Spring 统一管理,它是面向切面编程(AOP)的一个前提。

二、开发前准备

1.下载Apache Maven(MAC篇)

Windows & Linux 请参考:https://www.runoob.com/maven/maven-setup.html

官方下载地址:https://maven.apache.org/download.cgi

① 官网下载 Maven 到自定义位置并解压

解压后我们会得到一个这样的文件 ⬇️

② 解压后配置环境变量

打开终端输入命令 vim ~/.bash_profile ,然后增加以下内容 ,此处的maven安装路径改为自己的即可

export MAVEN_HOME=/Users/quokka/maven/apache-maven-3.8.6
export PATH=$PATH:$MAVEN_HOME/bin

③ 执行命令刷新环境变量

source ~/.bash_profile

④ 安装后的检查

mvn -version

.zshrc 的配置

如果每次重新打开终端都需要重新 source ~/.bash_profile 才能让环境变量生效,则需要在 .zshrc 中进行配置。

编辑.zshrc:

vim ~/.zshrc

添加命令:

source ~/.bash_profile

2.设置Maven仓库

实际开发中我们一般都使用自己下载的 Maven ,不使用 IDEA 工具自带的 Maven ,这就需要将我们上面下载的 Maven 配置到 IDEA 工具中。

1)在IDEA上设置

① 设置运行参数(不做该操作会导致 Maven 骨架生成速度慢到可怕😨)

将如下参数填入对应位置即可

-DarchetypeCatalog=internal

② 全局配置 Maven 基础信息

这里我们需要在自己的 Maven 安装目录同级下创建一个 repository 文件夹作为 Maven 仓库,所有我们需要的依赖都会存放在这个文件夹之中。

完成后依次点击【Apply】、【OK】即可。

2)本地Maven配置文件设置

打开本地 Maven 安装路径中如下路径文件:

apache-maven-3.8.6/conf/settings.xml

① 设置本地仓库路径到我们之前创建好的本地repository仓库路径位置

<localRepository>你的本地仓库路径</localRepository>

② 从国外仓库下载很慢,我们设置为国内镜像

<mirror>
  <id>aliyunmaven</id>
  <mirrorOf>*</mirrorOf>
  <name>阿里云公共仓库</name>
  <url>https://maven.aliyun.com/repository/public</url>
</mirror>

3.创建Maven项目

① 新建项目

② 新建一个 Maven 项目(以Maven版web项目为例)

完事儿点击【Create】等待 Maven 骨架形成即可。

上面我选择的是 Web 项目的 Maven 模版,根据开发项目的不同我们可以选择不同的模版 ⬇️

Maven 模版(internal) 描述
appfuse-basic-spring 创建一个基于Hibernate,Spring和Spring MVC的Web应用程序的原型
appfuse-basic-struts 创建一个基于Hibernate,Spring和Struts 2的Web应用程序的原型
appfuse-basic-tapestry 创建一个基于Hibernate, Spring 和 Tapestry 4的Web应用程序的原型
appfuse-core 创建一个基于 Hibernate and Spring 和 XFire的jar应用程序的原型
appfuse-modular-jsf 创建一个基于 Hibernate,Spring和JSF的模块化应用原型
appfuse-modular-spring 创建一个基于 Hibernate, Spring 和 Spring MVC 的模块化应用原型
appfuse-modular-struts 创建一个基于 Hibernate, Spring 和 Struts 2 的模块化应用原型
appfuse-modular-tapestry 创建一个基于 Hibernate, Spring 和 Tapestry 4 的模块化应用原型
maven-archetype-j2ee-simple 一个简单的J2EE的Java应用程序
maven-archetype-marmalade-mojo 一个Maven的 插件开发项目 using marmalade
maven-archetype-mojo 一个Maven的Java插件开发项目
maven-archetype-portlet 一个简单的portlet应用程序
maven-archetype-quickstart javaSE项目
maven-archetype-site-simple 简单的网站生成项目
maven-archetype-site 更复杂的网站项目一般用来做父工程
maven-archetype-webapp 一个简单的Java Web应用程序
jini-service-archetype Archetype for Jini service project creation
softeu-archetype-seam JSF+Facelets+Seam Archetype
softeu-archetype-seam-simple JSF+Facelets+Seam (无残留) 原型
softeu-archetype-jsf JSF+Facelets 原型
jpa-maven-archetype JPA 应用程序
spring-osgi-bundle-archetype Spring-OSGi 原型
confluence-plugin-archetype Atlassian 聚合插件原型
jira-plugin-archetype Atlassian JIRA 插件原型
maven-archetype-har Hibernate 存档
maven-archetype-sar JBoss 服务存档
wicket-archetype-quickstart 一个简单的Apache Wicket的项目
scala-archetype-simple 一个简单的scala的项目
lift-archetype-blank 一个 blank/empty liftweb 项目
lift-archetype-basic 基本(liftweb)项目
cocoon-22-archetype-block-plain http://cocoapacorg2/maven-plugins/
cocoon-22-archetype-block http://cocoapacorg2/maven-plugins/
cocoon-22-archetype-webapp http://cocoapacorg2/maven-plugins/
myfaces-archetype-helloworld 使用MyFaces的一个简单的原型
myfaces-archetype-helloworld-facelets 一个使用MyFaces和Facelets的简单原型
myfaces-archetype-trinidad 一个使用MyFaces和Trinidad的简单原型
myfaces-archetype-jsfcomponents 一种使用MyFaces创建定制JSF组件的简单的原型
gmaven-archetype-basic Groovy的基本原型
gmaven-archetype-mojo Groovy mojo 原型

③ 此时的目录结构不全,我们需要自行添加。

④ 添加 src/main/java 文件夹:在main目录上右击New -> Directory -> 选择出现的 java 文件夹即可

⚠️ 旧版本的 IDEA可能没有自动提示,我们就需要手动创建一个普通 java 文件夹,然后选中创建好的 java 文件右击进行关联设置即可。

⑤ 添加 src/main/resources 文件夹:(已存在就跳过此步骤) 在main目录上右击New -> Directory -> 选择出现的 java 文件夹即可

⚠️ 旧版本的 IDEA可能没有自动提示,我们就需要手动创建一个普通 resources 文件夹,然后同上面的关联方式选中创建好的 resources 文件右击进行关联为 Resources Root 即可。

⑥ 添加 src/test/java 文件夹:在src目录上右击New -> Directory -> 选择出现的 test/java 文件夹即可

⚠️ 旧版本的 IDEA可能没有自动提示,我们就需要手动创建一个普通 test/java 文件夹,然后同上面的关联方式选中创建好的 test/java 文件右击进行关联为 Test Sources Root 即可。

⑦ 添加 src/test/resources 文件夹:在src目录上右击New -> Directory -> 选择出现的 test/resources 文件夹即可

⚠️ 旧版本的 IDEA可能没有自动提示,我们就需要手动创建一个普通 test/resources 文件夹,然后同上面的关联方式选中创建好的 test/resources 文件右击进行关联为 Test Resources Root 即可。

⑧ 最终目录结构

4.引入Maven依赖pom.xml

1)导入Spring依赖

打开 pom.xml 导入 spring 的依赖(spring-context)

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

2)导入junit测试包

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>

5.创建Spring配置文件

Dao层(持久层)中的对象我们都在 applicationContext.xml 文件中进行配置,交由 Spring 管理。

src/main/resources/ 目录下创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml

创建好的xml文件如下

三、XML方式实现IoC

1.具体演示

① 定义Dao层接口与实现类

测试接口:

public interface TestDao {
    void save();
}

测试实现类:

public class TestDaoImpl implements TestDao {
    public TestDaoImpl() {
        super();
        System.out.println("TestDaoImpl无参构造");
    }

    @Override
    public void save(){
        System.out.println("执行save()方法");
    }
}

② 在配置文件中将dao配置进去

<bean id="TestDaoImpl" class="com.quokka.dao.impl.TestDaoImpl"/>

  • <bean/>:用于定义一个实例对象,一个实例对应一个 bean 标签。
  • id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。
  • class:用类的全路径名指定该 Bean 所属的类。(⚠️ 注意这里只能是类,不能是接口)

③ 定义测试类

public class AppTest {
    @Test
    public void test01(){
        //1 指定spring配置文件的位置和名称
        String resource = "classpath:applicationContext.xml";
        
        //2 读取配置文件,创建spring容器对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(resource);
        
        //3 使用id从spring容器中获取对象
        TestDaoImpl testDaoImpl = (TestDaoImpl) applicationContext.getBean("TestDaoImpl");
        
        //执行对象的业务方法
        testDaoImpl.save();
    }
}

当然我们也可以根据类型获取 ⬇️

public class AppTest {
    @Test
    public void test01(){
        //1 指定spring配置文件的位置和名称
        String resource = "applicationContext.xml";

        //2 读取配置文件,创建spring容器对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(resource);

        //3 根据类型获取
        TestDaoImpl testDaoImpl = applicationContext.getBean(TestDaoImpl.class);

        //执行对象的业务方法
        testDaoImpl.save();
    }
}

测试结果:

TestDaoImpl无参构造
执行save()方法

这样我们就使用 Spring 的 IoC 实现了解耦合。

getBean方法:

//通过指定 id 获取对象的实例,需要手动强转
public Object getBean(String name) throws BeansException;

//通过指定类型获取对象的实例,不需要强转
public <T> T getBean(Class<T> requiredType);

⚠️ 注意:同一个类型下只能有一个对象实例。

2.ApplicationContext接口

1)配置文件在不同路径的获取容器对象方式

ApplicationContext 接口用于加载 Spring 的配置文件,在程序中充当 “容器” 的角色,其常用实现类有两个。

  • 若 Spring 配置文件存放在项目的类路径下(建议统一放在 resoueces 文件夹下),即该配置在 src 中。则使用 ClassPathXmlApplicationContext 实现类进行加载配置文件。(推荐)
  • 若 Spring 配置文件存放在项目的根路径下,即该配置文件与 src 目录同级。则使用 FileSystemXmlApplicationContext 实现类来加载配置文件。

2)ApplicationContext容器中对象的装配时机

ApplicationContext 容器会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高,但占用内存。

即下面的语句会将配置文件中的所有对象一次性创建出来,存储在 ApplicationContext 容器中⬇️

//2 读取配置文件,创建spring容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(resource);

3.Bean的装配

容器根据代码要求创建 Bean 对象后再传递给代码的过程,称为 Bean 的装配。

1)默认装配方式

Spring 调用 Bean 类的无参构造器,创建空值的实例对象。即 Spring 框架是默认调用类的无参构造方法来创建对象,如果定义了有参构造,就需要在类中显示的定义无参构造方法。

2)容器中Bean的作用域

当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以在<bean>标签中通过 scope 属性,为 Bean 指定特定的作用域,Spring 支持多种作用域。⬇️

scope属性值 描述
singleton 单例模式。即在整个 Spring 容器中使用 singleton 定义的 Bean 将是单例的,叫这个名称的对象只有一个实例。(默认就是单例的)
prototype 原型模式。即每次使用 getBean 方法获取的同一个<bean />的实例都是一个新的实例。
request 对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
session 对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
  1. 对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
  2. 对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了。
  3. 对于 scope 为 prototype 的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行装配的。

四、基于XML的DI

1.依赖注入介绍

① 什么是依赖?

如 classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,则 classA 对 classB 有依赖。

public class ClassA {
    private ClassB classB;

    public void wayA(){
        classB.wayB();
    }
}

class ClassB{
    public void wayB(){
        System.out.println("ClaccB中的way()方法");
    }
}

② 什么是注入?

Bean 实例在调用无参构造器创建对象后,就要对 Bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。注入方式的有多种,其中常用的有两类:set方法注入、构造方法注入。

③ IoC的实现:

依赖注入(Dependency Injection,简称DI)是指程序运行过程中若需要调用另一个对象协助时,无须在代码中创建被调用者实例。而是依赖于外部容器,由外部容器创建后传递给程序。

④ Spring使用依赖注入(DI)实现IoC:

Spring 容器是一个超级大工厂,负责创建和管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,这个关系就是 Spring 使用 “依赖注入” 的方式来实现的,从而实现 IoC 使得对象之间解耦和。

2.set方法注入

set 方法注入是指通过 Setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。

1)简单类型

这里指的简单类型是指 8个基本类型 + String。

① 定义普通类

public class Student {
    private String name;
    private int age;
    
    // Setter (必须提供两个属性的 Setter 方法)
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
  
  	@Override
   	//toString()
}

② 加入对象到配置文件中

<bean id="Student" class="com.quokka.pojo.Student">
    <!-- 简单类型的属性赋值 -->
    <property name="name" value="张三" /> 	<!-- Spring容器实例化时自动调用setName("张三") -->
    <property name="age" value="18" /> 		<!-- Spring容器实例化时自动调用setAge(18) -->
</bean>
  • property标签:注入属性值。
  • name属性:需要注入值的属性名,与类中的属性名相同。
  • value属性:值的类型为基本类型 + String。

③ 测试代码

@Test
public void test02(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
  
    Student student = applicationContext.getBean(Student.class);
  
    System.out.println(student);
}

测试结果:

Student{name='张三', age=18}

2)引用类型

当指定 Bean 的某属性值为另一 Bean 的实例时,通过 ref 指定它们间的引用关系,且 ref 的值必须为某 Bean 的 id 值

① 定义普通类

public class Student {
    private String name;
    private int age;
    private Teacher teacher;
		
  	// Setter
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

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

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

    @Override
   	//toString()
}
public class Teacher {
    private String name;
		
  	//Setter
    public void setName(String name) {
        this.name = name;
    }
  
  	@Override
   	//toString()
}

② 加入对象到配置文件中

<bean id="Student" class="com.quokka.pojo.Student">
    <!-- 简单类型的属性赋值 -->
    <property name="name" value="张三" /> <!-- setName("张三") -->
    <property name="age" value="18" /> <!-- setAge(18) -->

    <!-- 引用类型的属性赋值 -->
    <property name="teacher" ref="Teacher"/> <!-- setTeacher(Teacher) -->
</bean>

<bean id="Teacher" class="com.quokka.pojo.Teacher">
    <property name="name" value="张老师"/>    
</bean>
  • property标签:注入属性值。
  • name属性:需要注入值的属性名,与类中的属性名相同。
  • value属性:值的类型为基本类型 + String。
  • ref属性:值的类型为引用类型(不包含包装类和String),通过在 Spring 容器中已存在的 Bean 对象的 id 属性赋值。

③ 测试代码

@Test
public void test02(){
    Student student = applicationContext.getBean(Student.class);
    System.out.println(student);
}

测试结果:

Student{name='张三', age=18, teacher=Teacher{name='张老师'}}

3.构造方法注入

构造方法注入是指在构造调用者实例的同时,完成被调用者的实例化,即使用构造器设置依赖关系。

① 定义普通类

public class Student {
    private String name;
    private int age;
    private Teacher teacher;

    /**
     * 有参构造,Spring容器实例化时调用该方法
     */
    public Student(String name, int age, Teacher teacher) {
        this.name = name;
        this.age = age;
        this.teacher = teacher;
    }
    
    @Override
   	//toString()
}
public class Teacher {
    private String name;

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

    @Override
   	//toString()
}

② 加入对象到配置文件中

<bean id="Student" class="com.quokka.pojo.Student">
    <!-- 通过构造器的参数注入值 -->
    <constructor-arg name="name" value="张三"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="teacher" ref="Teacher"/>
</bean>

<bean id="Teacher" class="com.quokka.pojo.Teacher">
    <property name="name" value="张老师"/>
</bean>
  • constructor-arg标签:通过构造器的参数注入值。
  • name属性:参数名,与构造方法中的参数名相同。
  • value属性:值的类型为基本类型及其 String 类型。
  • ref属性:值的类型为引用类型(不包含包装类和String),通过在 Spring 容器中已存在的 Bean 对象的 id 属性赋值。

③ 测试代码

@Test
public void test02(){
    Student student = applicationContext.getBean(Student.class);
    System.out.println(student);
}

测试结果:

Student{name='张三', age=18, teacher=Teacher{name='张老师'}}

五、连接池

1.连接池介绍

⓵ 传统数据库连接对象的使用:

每次都与数据库建立网络连接,并提供用户名和密码,然后底层通过一系列复杂的处理,才能最终得到连接对象。我们在使用完连接之后,要进行资源的释放(关闭连接)。关闭连接的原因是数据库能够提供的连接数是有限的,如果不将连接关闭,当连接数用完之后,就无法再提供连接对象了。在获取连接到关闭连接这个过程是很耗费系统资源和时间的,对于访问比较密集的程序而言,极大的影响了程序的执行效率。

⓶ 连接池思想:(优化连接对象的使用)

一次性创建一定数量的连接对象,然后将这些对象放到一个容器中(连接池对象)。使用连接时,从该容器中随机获取一个连接对象完成操作。释放资源时,将连接对象返回到这个容器中,而不是真正的关闭连接。在整个过程中,连接对象自从创建之后就不再关闭,起到对连接的复用的目的,从而提高了程序的执行效率(单位时间内所能处理的请求数)。

⓷ 常见的连接池:

  • 阿里巴巴的德鲁伊(druid)
  • c3p0

2.配置连接池Bean

在 applicationContext.xml 配置文件中配置数据源 ⬇️

<!-- 配置数据源(连接池对象) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/quokka_mysql"/>
    <property name="username" value="root"/>
    <property name="password" value="quokka123456"/>
</bean>

当然我们为了能够动态更换数据库,也可以使用从外部配置文件引入的方式,然后使用 spEL 语法引入即可 ⬇️

① 准备外部文件(以db.properties为例)

该文件放在 src/main/resources/ 目录下即可

db.properties 文件内容如下⬇️

# 数据库驱动
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库链接url
jdbc.url=jdbc:mysql://localhost:3306/你的数据库名
# 数据库用户名
jdbc.username=root
# 数据库密码
jdbc.password=你的数据库密码

② 引入外部文件并配置数据源

<!-- 引入外部配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置数据源(连接池对象) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

外部导入静态资源记得刷新 Maven ⬇️

六、DBUtils

1.DBUtils概述

DBUtils 是 Apache 的一款用于简化数据访问层(Dao层)代码的工具类,是对 JDBC 的封装。

  • 核心对象

    • QueryRunner runner = new QueryRunner(DataSource ds)
    • DataSource 实现类是连接池对象
  • 核心方法

    • int update(Connection conn,String sql语句,Object ... ?对应的参数值):执行增删改操作。
    • query(String sql语句, ResultSetHandler hl,Object ... ?对应的参数值):执行查询操作。

2.Spring使用XML方式整合DBUtils

下面演示基于 Spring 的 xml 配置,实现 student 表的 CRUD 案例。

① 准备数据库表

CREATE TABLE `student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

② pom.xml导入相关依赖

<!-- mysql驱动 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.31</version>
</dependency>

<!-- 德鲁伊连接池 -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.14</version>
</dependency>

<!-- DBUtils -->
<dependency>
  <groupId>commons-dbutils</groupId>
  <artifactId>commons-dbutils</artifactId>
  <version>1.7</version>
</dependency>

③ 准备数据库连接的properties文件

src/main/resources 下创建 db.properties 文件,内容如下

# 数据库驱动
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库链接url
jdbc.url=jdbc:mysql://localhost:3306/你自己的数据库
# 数据库用户名
jdbc.username=root
# 数据库密码
jdbc.password=你自己的数据库密码

④ 在applicationContext.xml配置文件中配置数据源

<!-- 引入外部配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置数据源(连接池对象) -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- (第三方类)DBUtils的QueryRunner对象 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
    <!-- 构造方法注入 -->
    <constructor-arg name="ds" ref="dataSource"/>
</bean>

<bean id="studentDao" class="com.quokka.dao.impl.StudentDaoImpl">
    <!-- setter方法依赖注入 -->
    <property name="queryRunner" ref="queryRunner"/>
</bean>

<bean id="studentService" class="com.quokka.service.impl.StudentServiceImpl">
    <!-- setter方法依赖注入 -->
    <property name="studentDao" ref="studentDao"/>
</bean>

依赖关系如下 ⬇️

⑤ 创建Student类

public class Student {
    private String id;
    private String name;
    private String age;

    //Getter and Setter

    @Override
    //toString()
}

⑥ 创建StudentDao及其实现类

  • 需要注入 QueryRunner 对象
  • QueryRunner 对象创建的时候需要注入 DataSource
public class StudentDaoImpl implements StudentDao {
    //依赖DBUtils的QueryRunner对象
    private QueryRunner queryRunner;

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

    @Override
    public void save(Student student) {
        try{
            String sql = "insert into student values(null,?,?)";
            queryRunner.update(sql,student.getName(),student.getAge());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("保存失败!");
        }
    }

    @Override
    public Student getById(int id){
        try{
            String sql = "select * from student where id=?";
            return queryRunner.query(sql,new BeanHandler<Student>(Student.class),id);
        }catch (SQLException e){
            e.printStackTrace();
            throw new RuntimeException("获取失败!");
        }
    }

    @Override
    public List<Student> getAll() {
        try{
            String sql = "select * from student";
            return queryRunner.query(sql, new BeanListHandler<>(Student.class));
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("查询所有失败!");
        }
    }

    @Override
    public void delete(int id) {
        try{
            String sql = "delete from student where id=?";
            queryRunner.update(sql,id);
        }catch (SQLException e){
            e.printStackTrace();
            throw new RuntimeException("删除失败!");
        }
    }

    @Override
    public void update(Student student) {
        try{
            String sql = "update student set age=? where name=?";
            queryRunner.update(sql,student.getAge(),student.getName());
        }catch (SQLException e){
            e.printStackTrace();
            throw new RuntimeException("修改失败!");
        }
    }
}

⑦ 创建StudentService及其实现类

  • 需要注入 StudentDao
public class StudentServiceImpl implements StudentService {
    private StudentDao studentDao;

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public void save(Student student) {
        studentDao.save(student);
    }

    @Override
    public Student getById(int id) {
        return studentDao.getById(id);
    }

    @Override
    public List<Student> getAll() {
        return studentDao.getAll();
    }

    @Override
    public void delete(int id) {
        studentDao.delete(id);
    }

    @Override
    public void update(Student student) {
        studentDao.update(student);
    }
}

⑦ 编写测试类

public class AppTest {

    @Test
    public void testServiceSave(){
        //获取工厂对象
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象实例
        StudentService studentService = beanFactory.getBean(StudentService.class);

        Student student = new Student();
        student.setName("王武");
        student.setAge("22");

        //增加记录
        studentService.save(student);
    }

    @Test
    public void testServiceGet(){
        //获取工厂对象
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象实例
        StudentService studentService = beanFactory.getBean(StudentService.class);
        //查询字段
        Student student = studentService.getById(2);
        System.out.println(student);
    }

    @Test
    public void testServiceGetAll(){
        //获取工厂对象
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象实例
        StudentService studentService = beanFactory.getBean(StudentService.class);
        //查询所有字段
        List<Student> studentList = studentService.getAll();
        for (Student student : studentList) {
            System.out.println(student);
        }
    }

    @Test
    public void testServiceUpdate(){
        //获取工厂对象
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象实例
        StudentService studentService = beanFactory.getBean(StudentService.class);
        //修改记录
        Student student = new Student();
        student.setName("王武");
        student.setAge("28");
        studentService.update(student);
    }

    @Test
    public void testServiceDelete(){
        //获取工厂对象
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取对象实例
        StudentService studentService = beanFactory.getBean(StudentService.class);
        //修改记录
        studentService.delete(3);
    }
}

⑧ 最终目录结构

七、Spring的注解开发

使用 Spring 的纯 xml 配置方式时,每添加一个 Dao 或者是 Service,都需要将其交给 Spring 进行管理。即需要在 Spring 的配置文件中配置一个 bean 标签,影响开发效率,同时会导致配置文件臃肿。因此 Spring 提供了一些注解用来代替<bean>的配置,可以大大提高开发效率。注解开发是一种趋势,注解代替 xml 配置文件 ,简化配置。

但是也不是使用纯注解的方式进行开发,一般情况下都是注解 + xml配置的方式结合使用。

1.Spring常用注解

注解 说明
@Component 使用在类上用于实例化 Bean
@Controller 使用在 web 层类上用于实例化 Bean
@Service 使用在 service 层类上用于实例化 Bean
@Repository 使用在 dao 层类上用于实例化 Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualifiler 结合@Autowired一起使用,根据名称进行依赖注入
@Value 注入简单类型(基本类型 + String)属性

⓵ @Component

@Component 注解用于替代之前的 bean 标签,使用在类上用于实例化 Bean。它有三个别名注解分别如下:

  • @Controller:使用在 web 层类上用于实例化 Bean
  • @Service:使用在 service 层类上用于实例化 Bean
  • @Repository:使用在 dao 层类上用于实例化 Bean

使用示范:

方式一:自定义唯一标识名

@Component("accountDao") //accountDao是实例在IoC容器中的唯一标识
public class AccountDaoImpl implements AccountDao {
    @Override
    public void save() {
        System.out.println("Dao中的save()方法");
    }
}

方式二:使用默认唯一标识名

@Component //若不指定值,id就是类名的首字母小写,即accountDaoImpl
public class AccountDaoImpl implements AccountDao {
    @Override
    public void save() {
        System.out.println("Dao中的save()方法");
    }
}

方式三:使用别名注解(推荐)

@Repository
public class AccountDaoImpl implements AccountDao {
    @Override
    public void save() {
        System.out.println("Dao中的save()方法");
    }
}

⓶ @Autowired/@Qualifiler/@Value

使用注解的方式进行开发不需要提供 Setter 方法。

  • @Autowired:使用在字段上用于根据类型依赖注入(已在 Spring 容器中的引用类型)
  • @Qualifiler:结合@Autowired一起使用,根据名称进行依赖注入
  • @Value:注入简单类型(基本类型 + String)属性

使用示范:

示例一:引用类型依赖注入

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired //默认按类型匹配,如果匹配到多个结果,则将字段名作为ID进行唯一匹配
    @Qualifier("accountDaoImpl") //搭配@Qualifier注解就可以按照指定的value属性值匹配唯一的结果
    private AccountDao accountDao;

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

示例二:简单类型注入

@Service
public class AccountServiceImpl implements AccountService {
    @Value("张三")
    private String name;
    @Value("18")
    private int age;

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

可以看出,简单类型的注入显得很多余,还不如直接赋值来得方便,一般情况下不使用。当然,需要通过外部文件动态引入的时候可以考虑使用。

⓷ @Resource

@Resource注解不是 Spring 的注解,是 JDK 提供的注解,只不过是 Spring 框架兼容了而已,如果在字段上添加该注解,Spring 也会解析。其作用就是将 @Autowired 和 @Qualifier 注解的作用合二为一。

使用示范:

方式一:不指定唯一id

@Service
public class AccountServiceImpl implements AccountService {
    //默认以字段名作为ID去IoC容器中匹配,如果不存在则按类型匹配,匹配不到或匹配多个相同类型就报异常
    @Resource
    private AccountDao accountDao;

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

方式二:指定唯一id

@Service
public class AccountServiceImpl implements AccountService {
    //根据类型和ID进行匹配,相当于@Autowired 和 @Qulifiler
    @Resource(name = "accountDaoImpl")
    private AccountDao accountDao;

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

⓸ @Autowired注解和@Resource注解有什么区别?

  1. 提供方不同

    @Autowired是 spring 提供的注解,@Resource是 JDK 提供的注解。

  2. 匹配机制不同

    • @Autowired注解在没有@Qualifier注解的前提下,首先根据类型自动匹配,如果在 Spring 的容器中存在多个该类型的实例,则以属性名作为 id 进行匹配,如果没有匹配成功,则抛出异常。有@Qualifier(“xxx”)注解会将将 id 为 “xxx” 的实例注入到该属性中,需要结合@Autowired来使用。
    • @Resource注解有 name 属性就以 name 属性的值作为 id 进行唯一匹配,匹配不到则抛出异常。没有 name 属性以属性名作为 id 进行唯一匹配,如果没有匹配到,则根据类型进行自动匹配,如果匹配不到或匹配到多个相同类型的实例,则抛出异常。

2.配置注解扫描

需要在 Spring 的 applicationContext.xml 主配置文件中配置注解扫描,扫描指定包下使用了 Spring 注解的类,以上注解才能被解析。

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

    <!--  开启组件扫描:扫描指定包下使用了Spring注解的类  -->
    <context:component-scan base-package="com.quokka"/>
</beans>

3.Spring整合Junit

Spring 中一个类继承了 junit 中的类,并对类中运行的方法进行增强操作,让代码运行的时候知道有 Spring 容器的存在,且可以从容器中拿到要注入的对象。步骤如下⬇️

⓵ 导入spring-test和junit依赖

<!--  java单元测试  -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.3.23</version>
</dependency>

⓶ 在测试类上通过注解的方式标注配置文件的位置

@ContextConfiguration("classpath:applicationContext.xml") //指定配置文件的所在路径
public class AppTest {}

⓷ 在测试类上通过注解的方式表明使用spring的测试类去运行代码

@ContextConfiguration("classpath:applicationContext.xml") //指定配置文件的所在路径
@RunWith(SpringJUnit4ClassRunner.class) //指定使用spring编写的增强的junit类运行代码
public class AppTest {}

⓸ 在测试类中通过@Autowired注入需要测试的对象

@ContextConfiguration("classpath:applicationContext.xml") //指定配置文件的所在路径
@RunWith(SpringJUnit4ClassRunner.class) //指定使用spring编写的增强的junit类运行代码
public class AppTest {
    @Autowired
    AccountService accountService;
}

4.Spring使用注解方式整合DBUtils

⓵ 新建Maven工程

详见:创建Maven项目

⓶ 导入相关依赖到pom.xml

<dependencies>
  <!--  spring依赖  -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.23</version>
  </dependency>

  <!--  java单元测试  -->
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
  </dependency>

  <!--  mysql驱动  -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.31</version>
  </dependency>

  <!--  德鲁伊连接池  -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.14</version>
  </dependency>

  <!--  DBUtils  -->
  <dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.7</version>
  </dependency>
</dependencies>

⓷ 准备resources/jdbc.properties文件

# 数据库驱动
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库链接url
jdbc.url=jdbc:mysql://localhost:3306/你的数据库
# 数据库用户名
jdbc.username=root
# 数据库密码
jdbc.password=你的数据库密码

⓸ 创建Spring的主配置文件

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

    <!--  开启组件扫描:扫描指定包下使用了Spring注解的类  -->
    <context:component-scan base-package="com.quokka"/>

    <!--  外部引入jdbc.properties  -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--  配置DataSource连接池  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      	<!-- 使用spring的el表达式获取已加载的配置文件中的数据 -->
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--  配置QueryRunner,需要注入DataSource依赖  -->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>
</beans>

QueryRunner 和 DruidDatasource 两个类是第三方编写的,因此我们没有办法在他们上面添加注解,所以只能将他们配置在 xml 文件中。

⓹ 编写Dao及其实现类

@Repository
public class AccountDaoImpl implements AccountDao {
    //依赖DBUtils的QueryRunner对象
    @Autowired
    private QueryRunner queryRunner;

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

    @Override
    public void update(String name) {
        try{
            String sql = "update account set balance=balance-100 where name=?";
            queryRunner.update(sql,name);
        }catch (SQLException e){
            e.printStackTrace();
        }
    }
}

⓺ 编写Service及其实现类

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public void update(String name) {
        accountDao.update(name);
    }
}

⓻ 测试

@ContextConfiguration("classpath:applicationContext.xml") //指定配置文件的所在路径
@RunWith(SpringJUnit4ClassRunner.class) //指定使用spring编写的增强的junit类运行代码
public class AppTest {
    @Autowired
    AccountService accountService;

    @Test
    public void accountServiceTest(){
        accountService.update("张三");
    }
}

测试前的数据库表:

测试结果:


网站公告

今日签到

点亮在社区的每一天
去签到