1. 背景介绍
你可能在各种地方、各种程序中见过Spring的身影,比如:开发web项目用到的SpringMVC。那么Spring到底是一个什么东西呢?能做什么呢?先不着急,可能一两句话还说不清楚Spring能做什么,但是我们只需要知道:Spring是为了简化Java开发而诞生的,所以,一定不要抱着去学习一种框架的方式去学习Spring,而应该多想想平时不用Spring编程时候,遇到过哪些问题,根据问题来学习Spring,你才能真正体会到Spring的强大,做到学以致用。
Spring是如何简化Java开发的呢?总的来说有以下四条策略,Spring做的任何事情,都可以追溯到下面这四条策略中:
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
下面我们将逐一进行介绍。
2. 基于POJO的轻量级和最小侵入性编程
如果你之前用过很多框架,你会发现很多框架强制要求你继承或实现他们的类或接口,导致你的应用和框架绑死,代码耦合性非常高,要做到迁移非常困难。
Spring竭力减少框架本身对你代码的污染,不会强制你实现它的接口或者继承它的类,使用Spring时候,你的代码中甚至看不到Spring的身影。
我们看下面一段代码:
public class School {
private String name;
private String city;
public School(String name, String city) {
this.name = name;
this.city = city;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"name\":\"")
.append(name).append('\"');
sb.append(",\"city\":\"")
.append(city).append('\"');
sb.append('}');
return sb.toString();
}
}
这就是一个普通的POJO[Plain Ordinary Java Object简单的Java对象,实际就是普通JavaBeans],但是有了Spring,我们可以赋予它很大的魔力。我们接着往下看Spring如何做的。
3. 通过依赖注入和面向接口实现松耦合
在我们现实编程世界里,任何一个简单的应用都可能由多个类组成,这些类相互协作完成程序逻辑,按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象) 的引用,这将会导致高度耦合和难以测试的代码。比如下面的代码:
public class Person {
private Task task;
public Person() {
this.task = new WorkTask();
}
public void doTask(){
task.doIt();
}
}
在上面的代码中,Person类的构造器中直接创建了WorkTask任务,work任务和Person是紧耦合,极大限制了该Person执行其他任务的能力,比如学习、吃饭、睡觉。同时对doTask方法进行单元测试时候也非常困难,因为要调用task对象的doIt方法。
当然我们说的耦合性也得一分为二的看,一定程度的耦合性在程序中是必须的,没有耦合的代码什么也做不了,但是耦合性太高使得代码难以测试、维护、复用。
通过依赖注入,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系。减少了代码之间的紧耦合性。
那么,我们可以修改Person类,让其本身不负责去创建task:
public class Person {
private Task task;
public Person(Task task) {
this.task = task;
}
public void doTask(){
task.doIt();
}
}
现在的Person类就可以执行任何任务了,只要任务实现了Task接口即可,如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。我们在后面的章节中会具体讲解如何使用Spring进行依赖注入。现在你只需要知道,我们可以通过Person类的构造器完成各种类型的Task的注入。
4. 基于切面和惯例进行声明式编程
依赖注入能够让相互协作的软件组件保持松散耦合,而面向切面编程(aspect-orien-ted programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
面向切面编程往往被定义为促使软件系统实现关注点的分离一项技术。系统 由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核 心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和 安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系 统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。
如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性。
实现系统关注点功能的代码将会重复出现在多个组件中。这意味着 如果你要改变这些关注点的逻辑,必须修改各个模块中的相关实现。即使你把这些关注点抽象为一个独立的模块,其他模块只是调用它的方法,但方法的调用还是会重复出现在各个模块中。
组件会因为那些与自身核心业务无关的代码而变得混乱。一个向地址簿增加地址条目的方法应该只关注如何添加地址,而不应该关注它是不是安全的或者是否需要支持事务。
AOP能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的 组件中去。所造成的结果就是这些组件会具有更高的内聚性并且会更加关注 自身的业务,完全不需要了解涉及系统服务所带来复杂性。总之,AOP能够确保POJO的简单性。
5. 通过切面和模板减少样板式代码
如果现在有一个需求,根据员工ID去数据库中查询员工信息。在刚开始学习了java那会儿,我们会写出这样的代码:
public Employee getEmployeeById(long id){
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try{
conn = dataSource.getConnection();
stmt = conn.prepareStatement("select * from employee where id = ?");
stmt.setLong(1,id);
rs = stmt.executeQuery();
Employee employee = null;
if(rs.next()){
employee = new Employee();
employee.setId(rs.getLong("id"));
employee.setName(rs.getString("name"));
...
}
return employee;
} catch (SQLException e){
...
} finally {
if(rs != null){
try{
rs.close();
} catch (SQLException e){
...
}
}
if(stmt != null){
try{
stmt.close();
} catch (SQLException e){
...
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException e){
...
}
}
}
return null;
}
我们已经发现了问题,少量查询员工的代码淹没在一堆JDBC的样板式代码中,同时包含各种异常处理,那么,我们能不能去消除样板代码,仅仅去编写那些有差异化的代码呢?答案是肯定的,比如Spring的jdbcTemplate就是对这种样板代码的封装,让编程人员只需要关注那些操作数据库的核心逻辑[查询、更新、删除]。代码更加简洁明了且易于维护。
6. 小结
至此向你展示了Spring通过面向POJO编程、DI、切面和模板技术来简化Java开发中的复杂性。在后面的章节中我们会细化每个版块,让大家对Spring有个全面清晰的了解。