JavaWeb(4):Spring简介

参考书籍:《Pro Java for Web Apps》

Maven依赖

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.4.RELEASE</version>
    <scope>compile</scope>
</dependency>

        Spring Framework是一个Java应用程序容器,它提供了许多有用的特性,如反转控制(IoC)、依赖注入(DI)、抽象数据访问、事务管理等。
        IoC是一个软件设计模式,即组装器(Spring Framework)在运行时才会绑定对象,也就是运行时才会实例化依赖的组件。这使得开发者可以针对接口进行编程,无需重新编译代码。DIIoC的一种实现方式,通过DI,一个类可以通过声明其依赖的接口类型,然后利用组装器在运行时注入其依赖的实例。
        Spring Framework提供了一个MVC(模型-视图-控制)模式框架,利用它可以简化创建Web应用程序的过程,不用手动处理复杂的ServletHttpServletRequest等。控制器类的每个方法都被映射到了一个不同的请求URL、方法或请求的其他属性上,模型可以通过 $Map$<$String, Object$>的形式从控制器传递到视图,控制器返回的视图或视图名称将把模型转发到合适的JSP视图。通过这些特性,Spring Framework极大地简化了Servlet容器的工作内容。
        使用MVC框架时,控制器类中的每个方法都可以拥有一个指向特定URL、请求方法、参数存在性、头的值、内容类型或期望响应类型的唯一映射。
        Spring Framework容器以一个或多个应用上下文的形式存在,由 $org.springframework.context.ApplicationContext$ 接口表示。一个应用上下文管理着一组bean、执行业务逻辑的Java对象、执行任务等。由Spring管理的bean可以自动进行DI、消息通知、bean验证等服务。一个Spring程序至少需要一个应用上下文,也可以使用多个应用上下文组成层次结构。在层次结构中应用上下文间有着类似于树的父子兄弟关系,一个子应用上下文的bean可以访问父应用上下文的bean,但无法访问兄弟应用上下文的bean。许多类都继承了 $ApplicationContext$ ,也有许多实现了它:

描述
$ConfigurableApplication$ 可配置的应用上下文
$WebApplicationContext$ 用于Java EE Web应用程序,提供了对 $ServletContext$ 和 $ServletConfig$ 的访问
$ConfigurableWebApplicationContext$ 可配置的 $WebApplicationContext$
$XmlApplicationContext$ 用于在Java EE Web应用程序中从XML文件加载Spring配置
$AnnotationConfigWebApplicationContext$ 用于在Java EE Web应用程序中以编程方式配置Spring

        Spring使用 $DispatcherServlet$ 处理Web请求,该Servlet将请求委托给合适的控制器,并按需求对请求和响应实体进行转换。在Web应用程序中,我们可以使用任意数量的 $DispatcherServlet$ 。每个 $DispatcherServlet$ 类都有自己的应用上下文,包含了对Web应用程序的 $ServletContext$ 和自己的 $ServletConfig$ 的引用。


启动Spring Framework

        配置和启动Spring framework是不同的,且相互独立,都可以用多种方式实现。在Java SE中只能通过在应用程序的 $main$ 方法中以编程方式启动。而在Java EE中,可以使用XML创建部署描述符( $web.xml$ )或者在 $javax.servlet.ServletContainerInitializer$ 中通过编程方式启动。

部署描述符方式

        传统的Spring Framework应用程序总是通过部署描述符启动。以部署描述符方式启动要求在配置文件中创建 $DispatcherServlet$ 实例,然后以 $contextConfigLocation$ 启动参数的形式为它提供配置文件,并指示Spring在启动时加载。

<context_param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/rootContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>springDispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/servletContext.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springDispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

        $ContextLoaderListener$ 将在Web应用程序启动时初始化,然后从 $contextConfigLocation$ 中指定的 $rootContext.xml$ 中加载根应用上下文并启动。同样的,$springDispatcher$ 也从指定的 $servletContext.xml$ 中加载应用上下文。

编程方式

        Java EE 6提供了一个新接口 $ServletContainerInitializer$ ,实现其的类将在应用程序开始时启动,并在所有监听器启动之前调用它们的 $onStartup$ 方法。要使用该接口,需要在 $/META-INF/services/javax.servlet.ServletContainerInitializer$ 文件中列出实现它的类。Spring Framework提供了一个桥接口,简化了这一步:$org.springframework.web.SpringServletContainerInitializer$ 类实现了 $ServletContainerInitializer$ 接口,然后该类会扫描程序并寻找 $org.framework.web.WebApplicationInitializer$ 接口的实现,调用其 $onStartup$ 方法。因此我们可以在 $WebApplicationInitializer$ 实现类中以编程方式配置监听器、Servlet和过滤器。

public class Bootstrap implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootContextConfiguration.class);
        container.addListener(new ContextLoaderListener(rootContext));

        AnnotationConfigWebApplicationContext servletContext = new AnnotationConfigWebApplicationContext();
        servletContext.register(ServletContextConfiguration.class);
        ServletRegistration.Dynamic dispatcher = container.addServlet("springDispatcher", new DispatcherServlet(servletContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }
}

        上面的代码实现了与之前的XML文件一样的配置,其中 $RootContextConfiguration$ 和 $ServletContextConfiguration$ 是自定义的配置应用上下文的类。在此要注意的是,不要把 $DispatcherServlet$ 映射到 $/\star$ ,因为映射到 $/\star$ 也会把内部JSP请求发送到该Servlet
        我们也不必只使用一种方式配置,可以混合使用,如下所示:

public class Bootstrap implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
        rootContext.setConfigLocation("/WEB-INF/rootContext.xml");
        container.addListener(new ContextLoaderListener(rootContext));

        XmlWebApplicationContext servletContext = new XmlWebApplicationContext();
        servletContext.setConfigLocation("/WEB-INF/servletContext.xml");
        ServletRegistration.Dynamic dispatcher = container.addServlet("springDispatcher", new DispatcherServlet(servletContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }
}

配置Spring Framework

        与启动相同,配置Spring Framework同样可以通过XML和编程方式。配置Spring Framework的主要步骤就是配置beanSpring Framework本身提供了默认bean,在此基础上,我们需要自己再编写一些bean

XML配置方式

        通过在上面的引用的 $servletContext.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"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <mvc:annotation-driven />

    <bean name="fooImpl" class="FooImpl"/>

    <bean name="barController" class="BarController">
        <property name="foo" ref="fooImpl"/>
    </bean>

</beans>

        我们使用 <$beans$> 命名空间配置bean。上述XML文件将实例化 $FooImpl$ 和 $BarController$ ,并将 $FooImpl$ 注入 $BarController$ 的 $foo$ 属性中。<$mvc:annotation-driven$> 指示Spring使用 $@RequestsMapping$, $@RequestBody$, $@RequestParam$, $@PathParam$, $@ResponseBody$ 这样的注解将请求映射到控制器方法上。

混合配置方式

        在XML的基础上,我们可以通过使用组件扫描和注解进行配置。组件扫描将扫描所有标注了 $org.springframework.stereotype.Component$ 的类,并将他们变成bean交由Spring进行管理。任何标注了 $@Component$ 的注解都将变成组件注解,因此 $@Controller$, $@Repository$, $@Service$ 也都是组件注解。当然,也可以创建自己的组件注解。$@org.springframework.beans.factory.annotation.Autowired$ 注解,以及它的同义词 $@javax.inject.Inject$ 和 $@javax.annotation.Resource$ 用于声明Spring应该注入的依赖,可以用于标注字段、方法、方法参数或者构造器。通常bean应该具有无参构造器,但对于一个标注了 $@Autowired$ 的构造器的类,Spring将注入所有构造器参数并使用该构造器构造实例。当存在多个匹配的bean时,可以通过使用 $@org.springframework.beans.factory.annotation.Qualifier$ 或者 $@javax.inject.Named$ 指定使用的bean的名字,或者使用 $@org.springframework.context.annotation.Primary$ 标记组件指定优先使用的bean的名字。除了上述注解之外,还有两个特殊的用于bean的注解,$@javax.annotation.PostConstruct$ 注解和 $@javax.annotation.PreDestroy$ 注解,用于标注一个 $public\ \ void$ 的无参方法,前者会在bean构造完成后调用,后者会在bean关闭前调用。
        通过使用注解,我们可以简化我们的Spring配置。在 $rootContext.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"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="org.example">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

</beans>

        <$context:annotation-config$> 启用注解配置,<$context:component-scan$> 启用组件扫描,$base-package$ 特性指定要扫描的包,在其命名空间内,我们使用了 <$context:exclude-filter$> 标签排除了对 $@Controller$ 组件的扫描。因为在之前的 $servletContext.xml$ 中我们已经配置了 $barController$ ,如果不排除的话,它将会被实例化两次。或者,我们可以修改 $servletContext.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"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <mvc:annotation-driven/>

    <context:annotation-config/>
    <context:component-scan base-package="org.example" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

</beans>

        使用 <$context:include-filter$> 设置应该扫描的包,注意这里使用了 $use-default-filters$ 该属性表示是否使用标准扫描模式,默认为 $true$ 。

编程配置方式

        使用编程方式配置时,我们要创建自定义的配置类,然后为这些类标上 $@org.springframework.context.annotation.Configuration$ 注解。在自定义的配置类内,我们还可以通过使用 $@org.springframework.context.annotation.Bean$ 注解标注无参方法注册bean。不仅如此,我们在混合配置中所提到的所有注解都能在配置类内使用,而且由于 $@Configuration$ 是标注了 $@Component$ 的注解,因此也能被组件扫描所扫描到,但这可能产生意料之外的行为,如bean被实例化两次。使用配置类时,可以通过结合使用 $@org.springframework.context.annotation.Import$ 注解导入其他配置类中的配置,或者使用 $@org.springframework.context.annotation.ImportResource$ 注解导入XML配置来实现将配置分布在多个地方。
        还记得我们在混合配置中保留的命名空间吗?现在我们可以使用注解来代替它们了。

注解 命名空间 作用
$@ComponentScan$ <$context:component-scan$> 指定启用组件扫描的包
$@EnableAsync$ <$task:*$> 启用Spring的 $@Aysnc$ 异步方法执行
$@EnableScheduling$ <$task:*$> 启用Spring的 $@Scheduled$ 计划方法的执行
$@EnableCaching$ <$cache:*$> 启用Spring的注解驱动缓存管理特性
$@EnableTransactionManagement$ <$tx:annotation-driven$> 为 $@Transactional$ 方法启用事务管理
$@EnableWebMvc$ <$mvc:annotation-driven$> 通常需要对该注解进行自定义配置,通过继承 $WebMvcConfigurerAdapter$ 来自定义需要的部分
@Configuration
@EnableWebMvc
@ComponentScan(
    basePackage = "org.example",
    useDefaultFilters = false,
    includeFilters = @ComponentScan.Filter(Controller.class)
)
public class ServletContextConfiguration {}

        上例是一个简单的配置类,它没有包含任何bean,一切都通过组件扫描和 $@EnableWebMvc$ 进行配置。

JavaWeb(4):Spring简介