今天开始,Spring 笔记开始更新,主要还是作为阅读《Spring 实战》(第4版)的学习笔记,该系列的内容大多也是引用自该书中。
虽然工作中大量接触 Spring,但回顾发现自己只是知道怎么做,而并没有去深究,导致有时候遇到问题不知从何下手,所以想要系统学习 Spring。
Spring 的使命便是如标题所说:简化 Java 开发。具体怎么简化的呢?分为以下四个方面:
- 基于 POJO 的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
这四点现在也不用花太多时间去深究,就像面向对象
一样,有个大概的了解,在实际中我们会慢慢体会到的。
接下来就简单了解下:
POJO
POJO:全程 Plain Old Java Object,直译就是简单老式 Java 对象。
更详细的一点的解释就是:一个简单的Java类,这个类没有实现/继承任何特殊的java接口或者类,不遵循任何主要java模型,约定或者框架的java对象。在理想情况下,POJO不应该有注解。
public class MyPOJO {
public String sayHello() {
return "Hello";
}
}
这就是一个 POJO,很简单,但 Spring 可以使用 DI 来装配它们,让它们变得强大。接下来就讲一下 DI,也就是依赖注入。
依赖注入
目的:解耦
注:以下代码只为表现本文中心理念,不能作实际编译用
/**
* 拯救少女的骑士
*/
public class DamselRescuingKnight implements Knight {
//拯救少女的任务
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest() {
quest.embark();
}
}
以上代码就是按正常逻辑编写的,DamselRescuingKnight
在他的构造函数中 new 了一个RescueDamselQuest
,于是这两者便紧紧耦合在一起了,这个骑士只能去拯救少女,而别的任务就完成不了了。
step1:如何实现 DI
public class BraveKnight implements Knight{
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
quest.embark();
}
}
这段代码便一定程度上降低了耦合性,骑士没有自己创建任务,而且在构造的时候把任务作为参数传递进来,这便是依赖注入的方式之一,即构造器注入 constructor injection
。
而且上述代码中构造器传入的是一个Quest
接口,这样骑士便能随心所欲的完成继承了Quest
接口的任何任务。这样BraveKnight
没有和任何Quest
发生耦合,这就是 DI 带来的最大收益——松耦合。
step2:如何注入 Quest
首先需要一个Quest
的实现类,如下,屠龙勇士再次出现了:
import java.io.PrintStream;
public class SlayDragonQuest implements Quest {
private PrintStream stream;
public SlayDragonQuest(PrintStream stream) {
this.stream = stream;
}
@Override
public void embark() {
stream.println("Embarking on quest to slay the dragon");
}
}
接下来便有两个问题,如何把SlayDragonQuest
交给BraveKnight
、如何把PrintStream
交给SlayDragonQuest
。这个过程称为装配 wiring
,Spring 提供了很多种装配 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/scheme/beans
http://www.springframework.org/scheme/beans/spring-beans.xsd">
<!-- 注入 Quest bean-->
<bean id="knight" class="BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<!-- 创建 SlayDragonQuest-->
<bean id="quest" class="SlayDragonQuest">
<constructor-arg value="#{T(System.out)}"/>
</bean>
</beans>
这样就能把BraveKnight
和SlayDragonQuest
声明为 spring 中的 bean 了,这里只是展示下大概怎么配置,具体在书中后面的章节会讲,我到时候也会整理成笔记的。
虽然他们之间会想依赖,但并不知道会有什么样的 Quest 传给他,只有通过 spring 的配置文件才能知道他们之间是如何装配起来的。而且我们可以通过修改 xml 直接修改依赖关系。
step3:如何工作的
Spring 通过应用上下文 Application Context
装载 bean 并把他们组装起来。
xml 对应的上下文是 ClassPathXmlApplicationContext
,示例代码如下:
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
// 获取 Knight bean
Knight knight = context.getBean(Knight.class);
// 使用 knight
knight.embarkOnQuest();
context.close();
}
}
这里通过knights.xml
创建了 Spring 应用上下文,然后从该应用上下文中获取一个 ID 为 knight 的 bean,然后就可以调用他的方法。这个类完全不知道那种类接受了哪种任务,只有 xml 知道。
应用切面
DI 是为了让相互协作的组件保持松耦合,而面向切面编程 aspect-oriented-programming
允许你把那些遍布应用各处的功能分离出来形成可重用的组件。
最常用的需要切面的便是日志、事务管理、安全。
还是骑士的例子,屠龙骑士应该只需要专注于如何去屠龙,而屠龙后怎么把他的事迹记录下来不应该需要他操心。而是有专门的记录历史的。用代码来看一下,我们把记录屠龙勇士的人称为吟游诗人。
首先需要创建一个吟游诗人的类:
import java.io.PrintStream;
/**
* 吟游诗人
*/
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {
stream.println("这个骑士要上了");
}
public void singAfterQuest() {
stream.println("骑士探险结束,真牛逼");
}
}
代码很简单,就是需要在骑士执行任务前后分别输出两句话。
似乎我们把 braveKnight
稍加改造就能实现记录了,如下
public class BraveKnight implements Knight{
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest,Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
但就如同一开始所说,我们应该让骑士自己去调用吟游诗人然后记录吗?答案肯定是不,骑士不应该考虑这些,他只要专心完成他的任务就行了。
这时候我们需要把 Minstrel
抽象为一个切面,并在 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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- 注入 Quest bean-->
<bean id="knight" class="BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<!-- 创建 SlayDragonQuest-->
<bean id="quest" class="SlayDragonQuest">
<constructor-arg value="#{T(System.out)}"/>
</bean>
<bean id="minstrel" class="Minstrel">
<constructor-arg value="#{T(System.out)}"/>
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<!-- 定义切点-->
<aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
<!-- 声明前置通知-->
<aop:before method="singBeforeQuest" pointcut-ref="embark"/>
<!-- 声明后置通知-->
<aop:after method="singAfterQuest" pointcut-ref="embark"/>
</aop:aspect>
</aop:config>
</beans>
首先和之前一样把 Minstrel 声明 为一个 bean,然后在 aop:config
中定义切面,并定义什么时候调用方法。具体配置也是后面再详细介绍,这里先大致看懂意思就行。
这样的话,BraveKnight
不需要显示的调用它,甚至不知道它的存在,这就是 AOP 的妙处。
使用模板消除样板式代码
简单的说就是减少重复代码,很常见的一个例子就是用 JDBC 访问数据库,正常我们可能需要写如下代码:
public static void queryTeacher() {
String driverName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/zyc";
String username = "root";
String password = "123456";
Connection connection = null;
Statement statement = null;
try {
Class.forName(driverName);
connection = (Connection) DriverManager.getConnection(url,username,password);
statement = connection.createStatement();
ResultSet rs = statement.executeQuery("SELECT tno,tname from teacher");
while (rs.next()) {
String no = rs.getString("tno");
String name = rs.getString("tname");
System.out.println(no + ":" + name);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
相似代码会在同一个项目中出现很多遍,这时候可以使用 Spring 中 JDBCTemplate 来简化代码
public static void insertTeacher() {
return jdbcTemplate.queryForObject("SELECT tno,tname from teacher",
...,
...);
// 代码省略,意思很简单,就是把模板中变的因素作为参数传进来
这种思想在代码中很常见,不管是 Java 还是别的编程语言。
网友评论