Spring 框架:多配置文件、开发方式与 IOC 注解

第一部分:多配置文件方式

1.1 多配置文件的加载方式

在 src 目录下又创建了一个配置文件,现在是两个核心配置文件。加载这两个配置文件的方式有两种:

方式 写法 说明
主配置文件包含其他配置文件 <import resource="applicationContext2.xml"/> 读取主配置文件即可,两者互补
工厂创建时直接加载多个配置文件 new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml") 用逗号分隔多个文件名

重点:多个配置文件应是互补关系,不要在两个文件中配置同样内容,避免配置冲突。

易错点:把两个配置文件配成重复内容,导致后加载的覆盖先加载的,或产生冲突。


第二部分:Spring 框架开发程序的方式

2.1 需求与技术选型

  • 需求:编写 Service 和 Dao 的类,演示代码。
  • 技术选择:持久层使用原始 JDBC 程序,连接池选择 Druid 连接池。
  • 不再像以前那样在 Controller 里 new Service、Service 里 new Dao,而是用 IOC 和 DI 注解,配合数据库连接池与 JdbcTemplate 实现持久层操作。

重点:连接池管连接资源,Template 管 SQL 执行,二者分工不同。

易错点:以为 JDBC Template 和连接池是一回事;实际连接池负责连接复用,Template 负责封装 SQL 执行。

2.2 创建 Maven 工程,导入依赖

核心依赖:

复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- 连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!-- mysql驱动包 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
</dependencies>

重点:

  • 核心依赖是 Spring 本身,另需日志两个、测试一个。
  • 还差德鲁伊连接池(Druid)和 MySQL 驱动。
  • MySQL 驱动版本按个人情况选,常用 5.1.8 或 5.1.63,用 8 版就引 8 版。

易错点:漏引德鲁伊或驱动导致连库失败;MySQL 版本须和实际匹配。

2.3 包结构约定

  • 基础包:com.qcby 两级目录下建细包。
  • 细包:controller、model、service、impl、dao/mapper、util/utils。
  • 测试包放 com.qcby 下第三层,叫 demo 或 test 均可。
  • 资源目录:resources(拼写注意)。

易错点:非 Spring Boot 项目测试包层级较随意,但基础包名与分层约定要遵守。

2.4 创建数据库、表结构

复制代码
create database spring_db;
use spring_db;
create table account(
    id int primary key auto_increment,
    name varchar(40),
    money double
) character set utf8 collate utf8_general_ci;

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

2.5 编写 JavaBean

复制代码
package com.qcbyjy.domain;

import java.io.Serializable;

public class Account implements Serializable {

    private static final long serialVersionUID = 7355810572012650248L;

    private Integer id;
    private String name;
    private Double money;

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

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

    public Double getMoney() { return money; }
    public void setMoney(Double money) { this.money = money; }

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

重点:

  • 实体类实现 Serializable 接口,便于序列化与 IO 流对象传递。
  • serialVersionUID 用于标识序列化版本,显式声明可避免自动生成带来的兼容隐患。
  • 包装类 Integer、Double 默认值为 null,基本类型 int、double 默认值为 0;业务字段用包装类可用 null 表示"未赋值"。

易错点:

  • 把 serialVersionUID 当普通业务 ID;它是 JVM 判断类版本是否匹配的校验标记。
  • 业务字段用基本类型,前端没传值时默认 0 被误判为有效值。

2.6 编写 AccountDao 接口和实现类

接口:

复制代码
package com.qcbyjy.dao;

import com.qcbyjy.domain.Account;
import java.util.List;

public interface AccountDao {
    public List<Account> findAll();
}

实现类:

复制代码
package com.qcbyjy.dao;

import com.qcbyjy.domain.Account;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class AccountDaoImpl implements AccountDao {

    // 注入连接池对象
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public List<Account> findAll() {
        List<Account> list = new ArrayList<>();
        Connection connection = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            // 获取连接
            connection = dataSource.getConnection();
            // 编写 SQL 语句
            String sql = "select * from account";
            // 预编译
            stmt = connection.prepareStatement(sql);
            // 查询
            rs = stmt.executeQuery();
            // 遍历,封装数据
            while (rs.next()) {
                Account account = new Account();
                account.setId(rs.getInt("id"));
                account.setName(rs.getString("name"));
                account.setMoney(rs.getDouble("money"));
                list.add(account);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try { connection.close(); } catch (SQLException e) { e.printStackTrace(); }
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
            try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        return list;
    }
}

重点:

  • 用 DataSource 接口接收连接池对象,DataSource 是 java.sql 包下的最上层接口。
  • 未赋值时,除直接 new 德鲁伊连接池外,更推荐用 IOC/DI 注入。
  • 第三方类(如德鲁伊连接池)也能交给框架管理。

易错点:

  • 以为第三方类不能注入;实际可以。
  • 混淆直接 new 对象和 set 注入的优先场景;此处优先用 IOC/DI 注入而非硬编码 new。
  • 用 Statement 而不是 PreparedStatement;后者用占位符防 SQL 注入,前者不安全。

2.7 编写 AccountService 接口和实现类

接口:

复制代码
package com.qcbyjy.service;

import com.qcbyjy.domain.Account;
import java.util.List;

public interface AccountService {
    public List<Account> findAll();
}

实现类:

复制代码
package com.qcbyjy.service;

import com.qcbyjy.dao.AccountDao;
import com.qcbyjy.domain.Account;
import java.util.List;

public class AccountServiceImpl implements AccountService {

    // 依赖注入
    private AccountDao accountDao;

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

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

重点:业务层调持久层,以前靠 new,现在直接声明接口类型的 AccountDao,借助 Spring 注入即可。

易错点:混淆"实现序列化接口"和"实现业务接口";AccountServiceImpl 实现的是 AccountService 业务接口。

2.8 编写配置文件

复制代码
<?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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql:///spring_db" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>

    <!-- 管理 bean -->
    <bean id="accountService" class="com.qcbyjy.service.AccountServiceImpl">
        <property name="accountDao" ref="accountDao" />
    </bean>
    <bean id="accountDao" class="com.qcbyjy.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

重点:

  • 交给框架管理的对象必须明确标识,如 accountService、accountDao、dataSource。
  • 框架提供的类(如德鲁伊)不用自己写类,直接写全路径;自己写的类必须显式加 id 交框架管。
  • JDBC URL 格式:jdbc:mysql:///数据库名,本机可省略主机端口;连其他服务器需写明 IP 和端口。

易错点:ref 用于引用类型,value 用于基本类型;dataSource 是对象,必须用 ref。

2.9 编写测试程序

复制代码
package com.qcbyjy.test;

import com.qcbyjy.domain.Account;
import com.qcbyjy.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;

public class Demo1 {

    @Test
    public void run1() {
        ApplicationContext ac =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = (AccountService) ac.getBean("accountService");
        List<Account> list = accountService.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
    }
}

重点:

  • 核心配置文件必须在类路径下加载 ApplicationContext。
  • 用 getBean 获取对象后调用方法,不能套用 new 的点属性赋值逻辑。

第三部分:IOC 注解的方式

3.1 IOC 注解方式的快速入门

IOC 注解方式依赖没有变化。

编写接口和实现类:

复制代码
package com.qcbyjy.demo2;

public interface UserService {
    public void hello();
}

package com.qcbyjy.demo2;

import org.springframework.stereotype.Component;

/**
 * <bean id="us" class="com.qcbyjy.demo2.UserServiceImpl"/>
 */
// 组件,作用:把当前类使用 IOC 容器进行管理
// 如果没有指定名称,默认使用类名,首字母小写:userServiceImpl
@Component(value = "us")
public class UserServiceImpl implements UserService {
    @Override
    public void hello() {
        System.out.println("Hello IOC 注解...");
    }
}

编写配置文件,重点是开启注解扫描:

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

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

</beans>

测试方法:

复制代码
package com.qcbyjy.test;

import com.qcbyjy.demo2.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo2 {

    @Test
    public void run1() {
        ApplicationContext ac =
                new ClassPathXmlApplicationContext("applicationContext_anno.xml");
        UserService userService = (UserService) ac.getBean("us");
        userService.hello();
    }
}

重点:

  • @Component 等价于之前手动配的 bean 对象。
  • 不声明注解内名字时,默认 bean 名是类名首字母小写;声明了则按声明名。
  • 扫描时只要识别注解就会接管该类。

易错点:注解属性写法------若注解上只有一个 value 属性,双引号可省略;但有多个属性时 value 不能省,一写逗号就说明有多个属性。

3.2 bean 管理类常用的 4 个注解

注解 分层
@Component 普通的类
@Controller 表现层
@Service 业务层
@Repository 持久层

重点:四者作用相同,推荐使用在不同分层上。

易错点:把通用注解和分层注解混为一谈;@Component 是通用写法,其余三个分别针对特定场景。

3.3 依赖注入常用的注解

注解 作用
@Value 用于注入普通类型(String、int、double 等)
@Autowired 默认按类型进行自动装配(引用类型)
@Qualifier 和 @Autowired 一起使用,强制使用名称注入
@Resource Java 提供的注解,也被支持;使用 name 属性,按名称注入

重点:

  • 基本类型用 @Value 注入值,引用数据类型用 @Autowired 按类型装配。
  • @Autowired 按类型注入,若容器里同类型有多个 bean 就有类型冲突风险。
  • @Qualifier 只有 value 属性,配合按类型注入即可明确指定用哪个实现类。
  • @Resource 不是 Spring 框架注解,却也能实现 DI;指定名称时用 name,不是 value。
  • @Autowired 和 @Value 不仅能加在属性上,也可作用在 set 方法上。

易错点:

  • 以为接口能直接注入;实际注入的是实现类,当实现类有两个时,仅靠类型无法选定就会出问题。
  • 只把对象交给框架管理还不够,Service 里还依赖 Dao,Dao 里还依赖 DataSource,这些依赖都得靠注解注入补齐。
  • 混淆"加注解"和"交由框架管理":前者解决依赖装配,后者解决对象托管。

3.4 对象生命周期(作用范围)注解

复制代码
@Scope // 生命周期注解,取值 singleton(默认值,单实例)和 prototype(多例)

初始化方法和销毁方法注解(了解):

复制代码
@PostConstruct // 相当于 init-method
@PreDestroy    // 相当于 destroy-method

销毁时机:

  • 单例对象销毁时机跟随容器,容器销毁对象销毁。
  • 多例对象销毁时机由 Java 垃圾回收机制控制。

3.5 注解方式代码示例

复制代码
package com.qcbyjy.demo3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

// 默认当前类名就是 ID 名称,首字母小写
@Component(value = "c")
// @Controller
// @Service(value = "c")
// @Repository(value = "c")
// @Scope(value = "singleton") // 默认值,单例的
// @Scope(value = "prototype") // 多例的
public class Car {

    // 注解注入值,属性 set 方法可以省略不写
    // 只有一个属性,属性名称是 value,value 可以省略不写
    @Value("大奔2")
    private String cname;

    @Value(value = "400000")
    private Double money;

    // 按类型自动装配的注解,和 id 名称没有关系
    @Autowired
    // 按 id 的名称注入,Qualifier 不能单独使用,需要 Autowired 一起使用
    // @Qualifier(value = "person")
    // @Resource Java 提供的注解,按名称注入对象,属性名称是 name
    // @Resource(name = "person")
    private Person person;

    /**
     * Car 对象创建完成后,调用 init 方法进行初始化操作
     */
    @PostConstruct
    public void init() {
        System.out.println("操作...");
    }

    @Override
    public String toString() {
        return "Car{" +
                "cname=" + cname +
                ", money=" + money +
                ", person=" + person +
                '}';
    }
}

package com.qcbyjy.demo3;

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

@Component(value = "person")
public class Person {

    @Value("张三")
    private String pname;

    @Override
    public String toString() {
        return "Person{" +
                "pname=" + pname +
                '}';
    }
}

3.6 IOC 纯注解的方式

纯注解方式是微服务架构开发的主要方式,目的是替换掉所有配置文件,但需要编写配置类。

编写实体类:

复制代码
package com.qcbyjy.demo4;

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

@Component
public class Order {

    @Value("北京")
    private String address;

    @Override
    public String toString() {
        return "Order{" +
                "address=" + address +
                '}';
    }
}

编写配置类,替换掉 applicationContext.xml:

复制代码
package com.qcbyjy.demo4;

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

/**
 * Spring 的配置类,替换掉 applicationContext.xml
 */
// 声明当前类是配置类
@Configuration
// 扫描指定的包结构
@ComponentScan(value = "com.qcbyjy.demo4")
public class SpringConfig {
}

测试方法:

复制代码
package com.qcbyjy.test;

import com.qcbyjy.demo4.Order;
import com.qcbyjy.demo4.SpringConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo4 {

    @Test
    public void run1() {
        // 创建工厂,加载配置类
        ApplicationContext ac =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        // 获取到对象
        Order order = (Order) ac.getBean("order");
        System.out.println(order);
    }
}

3.7 纯注解常用注解总结

注解 作用
@Configuration 声明是配置类
@ComponentScan 扫描具体包结构
@Import 导入其他配置类
@Bean 只能写在方法上,使用此方法创建一个对象,对象创建完成保存到 IOC 容器中

@Import 示例:

复制代码
@Configuration
public class SpringConfig2 {
}

@Configuration
@ComponentScan(value = "com.qcbyjy.demo4")
// 引入新的配置类
@Import(value = {SpringConfig2.class})
public class SpringConfig {
}

@Bean 示例:

复制代码
@Configuration
@ComponentScan(value = "com.qcbyjy.demo4")
@Import(value = {SpringConfig2.class})
public class SpringConfig {

    /**
     * 创建连接池对象,返回对象,把该方法创建后的对象存入到 IOC 容器中,使用 @Bean 注解解决
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
}

重点:@Bean 用于把方法返回的对象交给 IOC 容器管理,适合创建第三方类对象。


第四部分

4.1 Properties 文件与 Properties 类

  • Properties 文件是 key=value 格式的 K-V 数据格式,类似 JSON 但用等号连接而非冒号。
  • Properties 类是 JDK 源码中 java.util 下的类,可通过加载本地 resource 文件、读成流来解析配置。
  • 常见用法:定义 db.properties 四个参数,写工具类时用 new Properties() 读取。

重点:Properties 文件本质是结构化数据格式,靠流加载解析,和 JSON 定位相似但语法不同。

易错点:把 Properties 文件当普通文本;它需要靠流加载解析。

4.2 日志级别

常见级别:

级别 触发时机
debug 测试编译时打印
info 运行时打印
warning 仅警告时打印
error 仅错误时打印

重点:日志配置可控制打印条件、输出位置、生成时间;配置可网上拷贝,通常只需改输出位置和时间等少数值。

易错点:面试会被问专业术语和原理,不能只懂白话,要补学学名与作用机制。

4.3 序列化 UID

  • 实现 Serializable 接口的类可获自动生成的序列化 UID,用于标识类版本。
  • serialVersionUID 是 long 类型,可用 IDEA 的 Alt+回车 快速生成。
  • 反序列化时 JVM 比对两端 UID 是否一致,不一致会抛异常,故显式声明可避免自动生成带来的兼容隐患。

重点:序列化 UID 是 Java 序列化机制的控制提示符,在反序列化时用来验证序列化类与当前类的兼容性。

易错点:

  • 把序列化 UID 当成普通业务 ID;它不用于寻址,而是 JVM 判断类版本是否匹配的校验标记。
  • IDEA 中勾错提示项:应勾选 Serializable class without serialVersionUID,不是上方相近的提示。

4.4 包装类与基本类型

类型 默认值
基本类型 int 0
包装类 Integer null

重点:包装类默认值为 null,基本类型默认值为 0。业务字段用包装类可利用 null 表达"未赋值",和基本类型零值做明确区分。

易错点:以为基本类型不能放进集合;实际能放只是自动装箱,但用包装类更统一。

4.5 权限修饰符

修饰符 可见性
private 只有本类内能访问,换类就用不了,必须提供 set/get 方法
public 任何人可访问,日常最常用
default 包内可见
protected 同包或子类可见,比 private 宽、比 public 窄

重点:private 成员本类内部可以访问;无父子关系的类无法访问;有父子关系的子类也受私有规则约束。

易错点:混淆 private 与 protected;private 严格类内隔离,protected 还涉及同包或子类可见。

4.6 内部类

  • 内部类含普通内部类、匿名内部类。
  • 匿名内部类常重写 Object 方法来实现定制逻辑。
  • 需通过两个包演示权限修饰符修饰方法/类/属性的特点与流程。

4.7 Object 类核心方法

方法 说明
hashCode 返回哈希码值
toString 返回对象的字符串表示
equals 判断对象是否相等
finalize 垃圾回收前调用

重点:内部类与 Object 类无直接继承关系,但匿名内部类常重写 Object 方法来实现定制逻辑。

相关推荐
努力努力再努力wz2 小时前
【Lua入门系列】从 Table 到元表与元方法:一文理解 Lua 的行为扩展机制
开发语言·junit·lua
博风4 小时前
eclipse中配置maven,以及在本地使用maven。
java·eclipse·maven
麻瓜code1 天前
秒杀优化三连:Lua + 阻塞队列 + Redis Stream,响应从 3.4 秒到毫秒级
redis·junit·lua
Android小码家1 天前
framework&framework-minus-apex&android.jar的区别
android·python·jar
少年的范儿1 天前
IntelliJ IDEA 配置 JDK 21 与 Maven 教程(Windows):开发环境配置与验证
java·jdk·maven·intellij idea
peach3 天前
同样是装依赖,为什么 npm 长成 node_modules,Maven 长成 .m2
npm·node.js·maven
屈冠成3 天前
ShopXO商城系统测试报告
开发语言·spring·maven·lua
努力努力再努力wz3 天前
【Lua 入门系列】从顺序执行到分支与循环:一文理解 Lua 控制结构、作用域与跳转规则
junit·单元测试·lua
努力努力再努力wz4 天前
【Lua入门系列】从动态类型到变量作用域:彻底理解 local、全局变量与跨文件访问
开发语言·junit·lua