Spring的根本使命是 简化Java开发,方法是引导开发者使用POJO来设计实现系统。
Spring还有样式代码的封装和复用,我认为这个不是亮点。因此,略去不表,不喜勿喷。
关于POJO的理解
POJO是一个简称,全称为plain old java object。中文翻译是简单老式的java对象。
我认为Java类具备下面两个条件时,那么这个类可以被称为POJO。
- Java类没有任何基类或Java类的继承链只有JDK标准类。
- Java类依赖或者间接依赖的只有接口或者JDK标准类。
如下SlayDragonQuest就是POJO类。
/**
* Quest.java
*/
public interface Quest {
void embark();
}
/**
* SlayDragonQuest.java
*/
public class SlayDragonQuest implements Quest{
private final PrintStream mStream;
public SlayDragonQuest(PrintStream stream) {
this.mStream = stream;
}
public void embark() {
mStream.println("embarking on quest to slay the dragon");
}
}
POJO具备的好处是什么呢?
- 易复用。
作为一个Androider,其实很容易举例子。界面相关的代码很难复用到其他系统里。界面类必须是Activity或者Fragment的子类。Activity和Fragment是AOSP定义实现的类。其他系统如果没有这两个类及对应生命周期的实现,那么就无法直接使用Android界面类代码。 可以看上面示例代码。Quest和SlayDragonQuest是POJO。他们既可以使用在Spring环境中,也可以使用在其他环境中。这种灵活性降低了复用难度。
- 可测试/易测试
POJO类不会涉及到第三方类,易于构造/虚拟POJO类的运行环境。因此,POJO就具备可测性,测试难度也比较低。例如,上例中POJO的测试代码如下。
public class SlayDragonQuestTest {
public static void main(String[] args) throws Exception{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
PrintStream stream = new PrintStream(outputStream);
SlayDragonQuest quest = new SlayDragonQuest(stream);
quest.embark();
stream.close();
try {
String log = outputStream.toString("UTF-8");
System.out.println(log);
System.out.println(log.equals("embarking on quest to slay the dragon\n")?"success!":"failed!");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
POJO对象对继承链及引用类型都有限制。Spring在简化继承链、简化引用类型两个方面都有设计和实现。
Spring使用切面思想降低继承使用率
使用第三方类作基类的一个场景是:第三方类将通用的功能都实现了,子类只要继承了第三方类,那么子类可以直接具备这些功能。这个场景可以用切面手法来替代。举一个实际的例子。
古时候骑士会作很多冒险活动。吟游诗人会用诗歌记载骑士的冒险活动。诗歌越传越广,骑士也就广味认知。
有两种方式实现这个需求。
//非POJO的方式。
//具备通用功能的类
public class LogRecord {
public void logBeforeQuest() {
...
}
public void logAfterQuest() {
...
}
public void quest() {
logBeforeQuest();
actQuest();
logAfterQuest();
}
public void actQuest() {
//子类改写
}
}
public class Quest extends LogRecord {
public void actQuest() {
System.out.println("quest");
}
}
上例中Quest不再是POJO。复用/测试这个类必须考虑LogRecord的实现。
//POJO的方式
//具备通用功能的类
public class LogRecord {
public void logBeforeQuest() {
...
}
public void logAfterQuest() {
...
}
}
public class Quest extends LogRecord {
public void quest() {
System.out.println("quest");
}
}
<aop:config>
<aop:aspect ref="logRecord">
<aop:pointcut id="watchpoint" expression="execution(* *.quest(..))"/>
<aop:before pointcut-ref="watchpoint" method="logBeforeQuest" />
<aop:after pointcut-ref="watchpoint" method="logAfterQuest" />
</aop:aspect>
</aop:config>
上例中Quest仍然是POJO类。对Quest的复用及测试都比非POJO形式简单。
Spring使用控制反转思想引导开发者面向接口开发
对象的创建及销毁不是由使用者执行的而是由其他人来执行的,这个现象就是控制反转。如下例。
public interface MediaPlayer {
void play();
}
public class MediaPlayerImpl implements MediaPlayer {
public void play() {
//...
}
}
//控制反转
public class CDPlayerTestIoC {
@Autowired
private MediaPlayer player;
public void play() {
//player在使用前不需要创建
player.play();
}
}
//非控制反转
public class CDPlayerTestNonIoC {
private MediaPlayer player;
public void play() {
player = new MediaPlayerImpl();//player在使用前必须创建
player.play();
}
}
显而易见,CDPlayerTestIoC复用/测试的难度小于CDPlayerTestNonIoC。
- 只有普通类(非接口、非抽象类)才可以实例化,接口不能实例化。因此,在非控制反转场景下,CDPlayerTestNonIoC必须依赖MediaPlayerImpl。CDPlayerTestNonIoC不是POJO。
- CDPlayerTestIoC不需要考虑player创建的问题。因此,可以不考虑具体实现,而是针对接口设计编程。CDPlayerTestIoC是POJO。
不能说IoC与面向接口是强相关。但是IoC存在的前提下,可以降低面向接口编程的难度。真实使用Spring的项目,面向接口设计开发是常态。
Spring学习重点
- 切面的使用方法
- 控制反转的使用方法
- 与切面及控制反转相关的概念理解
- 一个使用Spring的J2SE应用案例。
- 一个使用Spring的Web应用案例。
- 一个使用Spring的后台应用案例。