Spring实战-第1章Spring之旅

摘要

本章内容:
1.Spring的bean容器
2.介绍Spring的核心模块
3.更为强大的Spring生态系统
4.Spring的新功能

1.1 简化Java开发

Spring是为了解决企业级应用开发的复杂性而创建的,使用 Spring 可以让简单的 JavaBean 实现之前只有 EJB 才能完成的事情。但 Spring 不仅仅局限于服务器端开发,任何 Java 应用都能在简单性、可测试性和松耦合等方面从 Spring 中获益。

为了降低Java开发的复杂性,Spring采取了以下4种关键策略:

1.基于POJO的轻量级和最小侵入性编程;
2.通过依赖注入和面向接口实现松耦合;
3.基于切面和惯例进行声明式编程;
4.通过切面和模板减少样板式代码。

Spring竭力避免因自身的API而弄乱你的应用代码。Spring不会强迫你实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。

程序清单1.1Spring不会在HelloWorldBean上有任何不合理的要求

package com.habuma.spring;
public class HelloWorldBean{
    public String sayHello(){
        return "Hello World";
    }   
}

1.1.2 依赖注入

任何一个有实际意义的应用(肯定比Hello World示例更复杂)都会由两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务逻
辑。按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。

通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。

看下面这个例子:

package com.springination.knights;
public class BraveKnight implements Knight{
    private Quest quest;
    public BraveKnight(Quest quest){ //Quest 被注入进来
        this.quest = quest;
    }
    
    public void embarkOnQuest(){
        quest.embark();
    }
}

BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造器参数传入。这是依赖注入的方式之一,即构造器注入。

为了测试BraveKnight,需要注入一个mock Quest:

package com.springination.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;

public class BraveKnightTest{
    @Test
    public void knightShouldEmbarkOnQuest(){
        Quest mockQuest = mock(Quest.class);//创建 mock Quest
        BraveKnight knight = new BraveKnight(mockQuest); //注入 mock Quest
        knight.embarkOnQuest();
        verify(mockQuest,times(1)).embark();
    }
}

你可以使用mock框架Mockito去创建一个Quest接口的mock实现。通过这个mock对象,就可以创建一个新的BraveKnight实例,并通过构造器注入这个mock Quest。

创建应用组件之间协作的行为通常称为装配。Spring有多种装配bean的方式,采用XML是很常见的一种装配方式。

使用Spring将SlayDragonQuest注入到BraveKnight中:



    
        
    
    
        
    

Spring还支持使用Java来描述配置:

package com.springination.knights.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.springinaction.Knights.BraveKnight;
import com.springinaction.Knights.Knight;
import com.springinaction.Knights.Quest;
import com.springinaction.Knights.SlayDragonQuest;

public class KnightConfig {
    @Configuration
    public class KnightConfig{
        @Bean
        public Knight knight() {
            return new BraveKnight(quest());
        }
        @Bean
        public Quest quest() {
            return new SlayDragonQuest(System.out);
        }
    }
}

Spring通过应用上下文(Application Context)装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带
了多种应用上下文的实现,它们之间主要的区别仅仅在于如何加载配置。

KnightMain.java加载包含Knight的Spring上下文:

package com.springinaction.knights;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class KnightMain{
    public static void main(String[] args) throws Exception{
        ClassPathXmlApplicationContext context = 
                new ClassPathXmlApplicationContext("META-INF/spring/knights.xml");
        Knight knight = context.getBean(Knight.class);
        knight.embarkOnQuest();
        context.close();
    }
}

1.1.3 应用切面

DI能够让相互协作的软件组件保持松散耦合,而面向切面编程(AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。

先来看下面这张图:



AOP能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来复杂性。总之,AOP能够确保POJO的简单性。


AOP 应用:
下面程序展示了,吟游诗人吟唱骑士的英勇事迹:

package com.springinaction.knights;

import java.io.PrintStream;

public class Minstrel{
    private PrintStream stream;
    
    public Minstrel(PrintStream stream) {
        this.stream = stream;
    }
    
    public void singBeforeQuest() {
        stream.println("Fa la la, the knight is so brave!");
    }
    
    public void singAfterQuest() {
        stream.println("Tee hee hee, the brave knight" + "did embark on a quest!");
    }
}
package com.springinaction.knights;

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() throws QuestException{
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

这样应该能达到效果,在骑士行动前后,诗人歌颂骑士。但是诗人应该是独立的个体,但是这个程序复杂化了,这个骑士居然去管理诗人,其实跟诗人应该是没有关系的。
所以下面要将诗人声明为一个切面:



    
        
    

    
        
    

     //声明 Minstrel bean
        
    

    
        
             //定义切点
            
            
        
    

使用模板消除样板式代码
许多Java API,例如JDBC,会涉及编写大量的样板式代码。

    public Employee getEmployeeById(long id){
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try{
            conn = dataSource.getConnection();
            stmt = conn.prepareStatement("select id, firstname,lastname,salary 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.setFirstName(rs.getString("firstname"));
                employee.setLastName(rs.getString("lastname"));
                employee.setSalary(rs.getBigDecimal("salary"));
            }
            return employee;
        }catch (SQLException e){
        }finally {
            if(rs != null){
                try{
                    stmt.close();
                }catch(SQLException e){}
            }
            if(conn != null){
                try{
                    conn.close();
                }catch(SQLException e){}
            }
        }
        return null;
    }

这段代码很繁琐,就是一个查询数据库的功能要写很多重复的代码,很多重复的异常处理。

使用Spring的JdbcTemplate(利用了 Java 5特性的JdbcTemplate实现)重写的getEmployeeById()方法仅仅关注于获取员工数据的核心逻辑,而不需要迎合JDBC API的需求。

    public Employee getEmployeeById(long id){
        return jdbcTemplate.queryForObject("select id, firstname, lastname, salary" +
                "from employee where id=?",
                new RowMapper(){
                    public Employee mapRow(ResultSet rs, int rowNum) throws SQLException{
                        Employee employee = new Employee();
                        employee.setId(rs.getLong("id"));
                        employee.setFirstName(rs.getString("firstname"));
                        employee.setLastName(rs.getString("lastname"));
                        employee.setSalary(rs.getBigDecimal("salary"));
                        return employee;
                    }
                },
                id);
    }

1.2 容纳你的Bean

在基于Spring的应用中,你的应用对象生存于Spring容器中。Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(在这里,可能就是new到finalize())。

Spring自带了多个容器实现,可以归为两种不同的类型。bean工厂(由org.springframework. beans.factory.eanFactory接口定义)是最简单的容器,提供基本的DI支持。应用上下文(由org.springframework.context.ApplicationContext接口定义)基于BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。

Spring自带了多种类型的应用上下文。下面罗列的几个是你最有可能
遇到的。

1.AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
2.AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
3.ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
4.FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
5.XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。

bean的生命周期

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。

相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。


在bean准备就绪之前,bean工厂执行了若干启动步骤:

1.Spring对bean进行实例化;
2.Spring将值和bean的引用注入到bean对应的属性中;
3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
5.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
7.如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

Spring模块

总结

Spring致力于简化企业级Java开发,促进代码的松散耦合。成功的关键在于依赖注入和AOP。

你可能感兴趣的:(Spring实战-第1章Spring之旅)