Spring 相关学习笔记Ⅰ
一、Spring
- Spring 是一个轻量级的Ioc容器,并支持AOP,关注点在于组件之间的解耦。
- Spring是一个开源的Java EE应用程序框架,发起人是Rod Johnson,2003年兴起,为解决企业应用开发的复杂性而创建的,提供了强大的Ioc、AoP等功能。
1.1 Spring 包含
- Spring FrameWork
- Spring 生态系统
1.1.1 Spring FrameWork 4个基本模块
- Core 模块,核心,定义了容器,IoC/DI
- Bean 模块,定义了工厂,自动装配
- AOP 模块,面向切面编程
- Context 模块,上下文
1.1.2 Spring FrameWork 的两个重要点
① IoC 控制反转
IoC:把 程序中 创建组件(Java Bean、POJO)、装配组件依赖的资源的操作(权限),移交 给运行程序的 容器(Spring、Tomcat、WebLogic、WebSphere、JBoss),由容器负责 自动化完成。
组件:使用 注解标记的普通Java类(POJO)。
由于上述的描述不太好理解,《重构》作者Matin Flower 重命名为 DI(依赖注入)。
注入的方式:
- 构造注入
- set方法注入
- 接口注入
企业级应用开发 分层:
- Web、移动端、桌面、第三方接口
- 控制层 Controller
- 业务层 Service
- 数据持久层 Repository
- 数据库
组件要用 @Component 注解标记,容器才会去自动管理,组件可划分3层架构,可以给组件起名字 @Component("name")
- @Controller 控制器 :view层
- @Service 业务逻辑 :service层
- @Repository 持久层 :dao层
组件 自动加载 @Autowired
组件 要加载的组件 @Qualifier("name")
组件 延迟加载 @Lazy
组件 同类型时首先加载 @Primary
组件 作用域 @Scope (4个)
- singlenton 单例(默认)
- prototype 原型
- request 请求(web 环境中)
- session 会话(web 环境中)
生命周期相关的注解:
- PostConstruct 相当于init-method
- PreDestroy 相当于destroy-method
Spring MVC/Data 依赖关系:
@Controller --依赖--> @Service --依赖--> @Repository|@Mapper|Dao --依赖--> Entity 和 Table
singlenton 单例模式的介绍请参考我另一篇文章 设计模式之单例模式
IoC的简单实现请参考我的另一篇文章 简单实现IoC
注入方式 简介
class A{
void run(){
}
}
class B{
// B依赖A:例如人 生活 依赖空气、阳光、水、食物
A a1;
void work(){
a1.run();
}
}
// 非注入的方式
// B构造时,在内部创建A new()
// 需要管理 A 的生命周期
// 缺陷:B 销毁了,a1 也将不复存在,它们的生命周期同步
class B{
// 在构造函数中初始化也一样
A a1 = new A();
B(){
//a1 = new A();
}
void work(){
a1.run();
}
}
// 注入方式
// 方案1 构造方法注入
// A 的创建及生命周期与 B 分离
// 缺陷:出生时就指定好了,因为 构造方法 只执行一次
Class B{
// 例如:伴侣、爱人
A a1;
B(A a){
a1 = a;
}
// 例如:结婚
void work(){
a1.run();
}
}
// 方案2 set方法注入
// A 的创建及生命周期与 B 分离
// 缺陷:可以动态改变依赖的对象,set方法可多次执行
class {
A a1;
void setA(A a){
a1 = a;
}
void work(){
a1.run();
}
}
// 方案3 接口注入
// 早期 EJB 中的资源注入方式
// 侵入式的,让 B 必须实现某个特定的接口,类似棍棒销售,例如想购买某个东西,必须充值办卡
// 尽量避免使用 侵入式
class B implements Xxx{
A a1;
@Override
void other(A a){
a1 = a;
}
void work(){
a1.run();
}
}
② AOP 面向切面编程
AOP:把方法中与 核心业务、核心逻辑 无关的 流程化、模板化 的代码 提取 出来定义为 可复用的组件(切面),在 编译时 或 运行时 加入到 方法(切入点) 的 特定位置(通知)。
目的:解耦、提高可复用性、提高开发效率!
AOP 是 基于 动态 代理模式 实现的!
- 切面:把方法中与核心业务、核心逻辑无关的流程化、模板化代码提取出来定义成可复用的组件
- 切入点:基于注解定义的
- 通知 [5种特定位置]
- @Before 前置
- @After 后置
- @Around 环绕
- @AfterReturning 方法返回后
- @AfterThrowing 异常抛出后
Java代理模式的介绍请参考我的另一篇文章 Java 三种代理模式
1.2 Java EE 发展
Java EE 规范 - 负责大型的分布式应用开发。
Java EE 是一套使用Java进行企业级Web应用开发的工业标准,以前称为J2EE。
其中的核心技术规范有:
-
JDBC:Java数据库连接的API,为访问不同数据库提供统一的途径。
-
JSP:JSP页面由HTML和Java代码组成,当客户端向服务器发出页面请求后,对Java代码进行处理,然后将生成的HTML页面返回给客户端浏览器,将界面表现与业务逻辑分离开。
-
Servlet:小型的Java程序,拓展了Web服务器的功能,类似于JSP,但实现方式有所区别,JSP通常是大多数HTML代码中嵌入少量的Java代码,而Servlet全是Java代码并生成HTML。
-
EJB :一个可复用的类,包含三种类型:
- 实体 Bean :类似MyBatis/Hibernate,但较复杂 - 会话 Bean - 消息驱动 Bean:EJB容器的调用
-
…
1.2.1 Java EE 不足
- Java EE 设计完备,有前瞻性,但缺陷是开发复杂、性能差、成本高,是一套重量级的解决方案。
- Java1.8之后,官网已不再推动维护JavaEE了,逐渐由社区接手管理。
1.2.2 社区 繁荣发展
2001年左右,社区很繁荣,社区开始推动技术的进化:
-
由于 JSP/Servlet 没有规范的流程,缺少最佳实践的标准,所以出现了Struts、WebWork
-
由于 JDBC、EJB 中的实体 Bean 繁琐、低效,所以出现了 MyBatis、Hibernate
-
EJB 容器被Spring Framework 代替了
-
Weblogic、Webshpere、Jboss 服务器(容器),也被 Spring 中的某些特性代替了
-
SSH 集成框架
- Struts 负责 MVC 视图相关的
- Spring 负责 业务 解耦
- Hibernate 负责 数据 存储
-
SSM 集成框架
- Spring MVC 视图相关
- Spring 业务相关
- MyBatis 数据存储
-
Spring Boot [Java服务端开发的一站式框架]
- Spring MVC
- Spring Code
- Spring Data
- JAP Hibernate
- MyBatis
- Spring Security
- 第三方框架技术
- …
二、Spring Boot
2.1 Spring Boot 概述
Spring Boot 是一个以Spring框架为核心,集成了Spring MVC、Spring Data、Spring Security等Spring全家桶,并自动整合了其他第三方框架、进行Java服务端开发的一站式解决方案。
2.2 Spring & Spring Boot
属性 | Spring | Spring Boot |
---|---|---|
时间 | 约20年 | 约7年 |
定位 | IoC 容器 | 一站式框架 |
范围 | IoC & AOP | 以 Spring 为核心 |
基于Spring Boot 可以开发
- 单体应用(一个大应用)
- 微服务(多个服务)
三、Spring Cloud
基于 Spring Boot 的分布式微服务开发架构。
微服务把 单体应用 按 业务 切分 为多个独立开发、部署、维护的 小应用。
其主要组件有:
- 注册和发现中心(基础设施)
- 负载均衡
- 路由 和 API 网关
- 分布式配置中心
- 断路器
- 日志追踪
- ...
Spring Cloud 将一个业务划分为若干个小的服务,每一个小的服务都是一个小的 Spring Boot 服务,对外提供一个网关统一的接口。
四、整合 MyBatis
ORM 对象关系映射 - 规范
简单来说就是为了不用 JDBC 那一套原始方法操作数据库。
ORM 框架:
- Hibernate 框架 [社区提供的]
- JPA 规范 :基于Hibernate 定义的规范 [官方提供的] Java Persistent API
- MyBatis :Java的 SQL 映射框架
对象模型 <-- 映射 --> 关系模型
说到框架,要清楚其两大功能:
- 繁琐操作 自动化
- 通用流程 规范化
MyBatis 概述
- SQL 映射框架
- 早期 基于 xml 映射
- 后期 基于 注解 映射 [在Java 5.0 注解出现后]
测试 MyBatis
- 新建 Spring Starter Project
- 选择 MySQL 驱动、MyBatis FrameWork 以及 Spring Boot 开发工具,开发工具可以加快程序的部署和更新。
- 可以在 java 包中新建 一个 实体类 Account 和 一个接口 AccountMapper,一个实体对应一个映射。
import java.math.BigDecimal;
// 实体类Account
public class Account {
// 持久化时,数据库中主键字段,无需传入构造方法的参数列表
int id;
String name;
BigDecimal balance;
// 无参构造方法
public Account() {
}
// 构造方法
public Account(String name, BigDecimal balance) {
super();
this.name = name;
this.balance = balance;
}
// getter and setter
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", balance=" + balance + "]";
}
}
接口记得加上注解 @Mapper,说明该接口是被 MyBatis 标记的接口,做包扫描时会被找到,其有效期是 RUNTIME。
- 插入 @Insert("")
- 查询 @Select("")
- 更新 @Update("")
- 删除 @Delete("")
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
// iBatis 原名
// MyBatis 现名
// 一个实体一个映射
@Mapper
public interface AccountMapper {
// 基于反射获得参数所属类型中的字段 - 读取方式 #{ }
/**
* 把 对象 持久化 为 表中的 一条记录
* @param account 要持久化的实体对象
*/
@Insert("insert into account(name,balance) values(#{name},#{balance})")
void save(Account account);
// 基于反射 会创建一个返回值类型的对象,使用查询获得的结果集自动去填充对象
@Select("select id,name,balance from account where id=#{id} order by balance desc")
Account findById(int id);
@Select("select id,name,balance from account order by balance desc")
List<Account> findAll();
@Update("update account set balance=#{balance} where id=#{id}")
void update(Account account);
@Delete("delete from account where id=#{id}")
void deleteById(int id);
}
4.同理,再新建一个实体类 Dept 和 一个映射接口 DeptMapper。
/**
* 实体类
*
+-------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | char(16) | YES | | NULL | |
| loc | char(16) | YES | | NULL | |
+-------+----------+------+-----+---------+----------------+
*/
public class Dept {
int id;
String title; // 对应表中字段 name
String city; // 对应表中字段 loc
public Dept() {
}
public Dept(String title, String city) {
super();
this.title = title;
this.city = city;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Dept [id=" + id + ", title=" + title + ", city=" + city + "]";
}
}
如果表中的列名和类中的属性名不一致时,需要进行结果集映射 @Results,用{ }包起多个结果映射 @Rusult,多个方法需要用到映射时,可以将映射提取出来,起个名 id,需要用到映射的地方加上注解@ResultMap(" id ") 即可。
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface DeptMapper {
@Insert("insert into dept(name,loc) values(#{title},#{city})")
void save(Dept dept);
// 表的列名和类中的属性名不一致,需要通过注解进行映射
// 映射表中的 列column 与类中的 字段property
// 重复用到时,可以提取出来,id 就是它的名字
@Results(
id = "deptResult",
value = {
@Result(column = "name", property = "title"),
@Result(column = "loc", property = "city"),
@Result(column = "id", property = "id")
})
@Select("select id,name,loc from dept")
List<Dept> findAll();
@Select("select id,name,loc from dept where id=#{id}")
@ResultMap("deptResult")
Dept findById(int id);
}
- 在 resources 包下,打开application配置文件,配置数据源。
# 数据源 - 数据库连接
# URL
spring.datasource.url=jdbc:mysql://ip地址:端口号/数据库?serverTimezone=UTC
# 用户名
spring.datasource.username=用户名
# 密码
spring.datasource.password=密码
# 驱动类的名字,java 8 以上的版本使用 cj
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- 在 Application 类中新建一个返回命令行运行CommandLineRunner的方法,记得加注解 @Bean ,声明为一个Bean组件,方法才会被自动调用。
import java.math.BigDecimal;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
// Spring Boot
@SpringBootApplication
public class OrmApplication {
public static void main(String[] args) {
SpringApplication.run(OrmApplication.class, args);
}
// 命令行执行,加上注解Bean!!!
@Bean
public CommandLineRunner a(BeanFactory factory) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
// 从组件工厂获得组件,基于工厂模式
// 工厂模式参考我的另一篇文章 设计模式 会有介绍
DeptMapper mapper = factory.getBean(DeptMapper.class);
// mapper.save(new Dept("研发","长沙"));
// mapper.save(new Dept("测试","湘潭"));
// mapper.save(new Dept("运维","广州"));
// System.out.println("save");
List<Dept> list = mapper.findAll();
for (Dept dept : list) {
System.out.println(dept);
}
System.out.println(mapper.findById(1));
System.out.println(mapper.findById(2));
}
};
}
@Bean
public CommandLineRunner b(BeanFactory factory) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
// 从组件工厂获得一个组件
AccountMapper mapper = factory.getBean(AccountMapper.class);
// mapper.save(new Account("ORM", BigDecimal.valueOf(100000)));
// System.out.println("save");
List<Account> list = mapper.findAll();
for (Account account : list) {
System.out.println(account);
}
Account account = mapper.findById(6);
System.out.println(account);
// mapper.deleteById(9);
// System.out.println("delete");
}
};
}
}
点击运行,Run As Spring Boot App,查看控制台输出结果