第一部分:多配置文件方式
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 方法来实现定制逻辑。