我的github
posts - 3243,  comments - 42,  views - 158万

Spring4.0是Spring在积蓄4年后,于2013年隆重推出的一个重大升级版本,它进一步加强了Spring作为Java领域第一开源平台的地位。

Spring4.0引入了众多Java开发者翘首以盼的新功能,如基于Groovy Bean的配置、HTML5/WebSocket支持等。全面支持Java8.0,最低要求是Java6.0。

这些新功能实用性强、易用性强,可大幅地降低Java应用,特别是Java Web应用开发的难度,同时有效地提升应用开发的优雅性。

本书是在《精通Spring3.x——企业应用开发详解》的基础上,历时一年的重大调整改版而成的,延续了上一版本“追求深度,注重原理,不停留在技术表面”的写作风格,力求使作者在熟练使用Spring的各项功能的同时透彻理解Spring的内部实现,真正做到知其然并知其所以然。此外,本书重点突出了“实战性”的主题,力求使全书内容体现“从实际项目中来,到实际项目中去”的写作原则。

 

前言

Spring从2004年发布第一个版本以来,至今已有12载。回顾Spring的光辉岁月,一路与时俱进,引领时代之潮流。

总的来说,Spring主要有三次重大的版本升级:一是,2006年从1.0升级到2.0,在Spring2.0中新增了XML命名空间、

 

第一章:对Spring框架进行了整体性的概述,可使读者快速建立起对Spring整体性的认识。

第二章:通过一个简单的例子展现开发Spring Web应用的整体过程,通过这个实例,读者可以快速跨入Spring Web应用的世界。

第三章:Spring Boot的设计目的是用来简化新Spring应用的搭建和开发过程,本章通过实例向读者讲述了Spring Boot的使用技巧。

第四章:讲解了Spring IoC容器的知识,通过具体的实例详细地讲解了IoC概念;同时,对Spring框架的三个最重要的框架级接口进行了剖析,并对Bean的生命周期进行了讲解。

 

第一章 Spring概述

Spring是分层的Java SE/EE应用一站式的轻量级开源框架,以IoC(Inverse of Control,控制反转)和AOP(Aspect Oriented Programming,切面编程)为内核,提供了展现层Spring MVC、持久层Spring JDBC及业务层事务管理等一站式的企业级应用技术。此外,Spring以海纳百川的胸怀整合了开源世界里众多著名的第三方框架和类库,逐渐成为使用最多的轻量级Java EE企业应用开源框架。github: https://github.com/spring-projects/spring-framework

Spring的特点:

  • 方便解耦,简化开发。基于IoC容器,用户可以将对象之间的依赖关系交由Spring控制,避免硬编码造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些底层的需求编写代码,可以更专注于上层的应用。
  • AOP编程的支持。通过Spring提供的AOP功能,方便进行面向切面的变成,很多不容易用传统OOP实现的功能可以通过AOP轻松应对。
  • 等等。

Spring的体系结构:

1. IoC

它将类与类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由IoC容器负责依赖类之间的创建、拼接、管理、获取等工作。BeanFactory接口是Spring框架的核心接口,它实现了容器许多核心的功能。

Context模块构建于核心模块之上,扩展了BeanFactory的功能,添加了i18n国际化、Bean生命周期控制、框架事件体系、资源加载透明化等多项功能。此外,该模块还提供了许多企业级服务的支持,如邮件服务、任务调度、JNDI获取、EJB集成、远程访问等。ApplicableContext是Context模块的核心接口。

表达式语言模块是统一表达式语言(Unified E)的一个扩展,该表达式语言用于查询和管理运行期的对象,支持设置/获取对象属性,调用对象方法,操作数组、集合等。此外,该模块还提供了逻辑表达式运算、变量定义等功能,可以方便地通过表达式串和Spring IoC容器进行交互。

2. AOP

AOP是继OOP之后,对编程设计思想影响极大的技术之一。在Spring里实现AOP编程有众多选择,在Java5.0中引入java.lang.instrument,允许在JVM启动时启用一个代理类,通过该代理类在运行期修改类的字节码,改变一个类的功能,从而实现AOP的功能。

3. 数据访问和集成

任何应用程序的核心问题是对数据的访问和操作。数据有多种表现形式,如数据表、XML、消息等,而每种数据形式又拥有不同的数据访问技术。

首先,Spring站在DAO的抽象层面,建立了一套面向DAO层的统一的异常体系,同时将各种访问数据的检查型异常转换为非检查型异常,为整合各种持久层框架提供基础。其次,Spring通过模板化技术对各种数据访问技术进行了薄层封装,将模式化的代码隐藏起来,使数据访问的程序得到大幅简化。这样,Spring就建立起了和数据形式及访问计数无关的统一的DAO层,借助AOP技术,Spring提供了声明式事务的功能。

4. Web及远程操作

该模块建立在Application Context模块之上,提供了Web应用的各种工具类,如通过Listener或Servlet初始化Spring容器,将Spring容器注册到Web容器中。该模块还提供了多项面向Web的功能,如透明化文件上传、Velocity、FreeMarker、XSLT的支持。此外,Spring可以整合Struts、WebWork等MVC框架。

5. Web及远程访问

Spring自己提供了一个完整的类似于Struts的MVC框架,称为Spring MVC。

6. WebSocket

WebSocket提供了一个在Web应用中高效、双向的通信,需要考虑到客户端和服务器之间的高频和低时延消息交换。

 

>>Spring子项目

打开Spring官方网站http://spring.io/projects,可以看到Spring众多的子项目,它们构建起一个丰富的企业级应用解决方案的生态系统。

Spring IO Platform、Spring Boot、Spring XD、Spring Cloud、Spring Data等等。

 


第二章 Spring快速入门

本章通过一个简单的例子展现开发Spring Web应用的整体过程,通过这个实例,读者可以快速跨入Spring Web应用的世界。实例应用按持久层、业务层和展现层进行组织,从底层DAO到Web展现逐层演进,一步步地搭建起一个完整的实例。通过本章的学习,读者可以独立完成一个典型的基于Spring的Web应用。

 

2.1 实例概述

登陆模块很基本,但是实际上涉及到的面很广,麻雀虽小五脏俱全。

在持久层拥有两个DAO类,分别是UserDao和LoginLogDao,在业务层对应一个业务类UserService,在展现层拥有一个LoginController类和两个JSP页面,分别是登陆页面login.jsp和欢迎页面main.jsp。

 

 过程:

(1)用户访问login.jsp,返回带用户名/密码表单的登陆页面。

(2)用户在登录页面输入用户名/密码,提交表单到服务器,Spring根据配置调用LoginController控制器响应登录请求。

(3)LoginController调用UserService#hashMatchUser()方法,根据用户名和密码查询是否存在匹配的用户,UserService内部通过调用持久层的UserDao完成具体的数据库访问操作。

(4)如果不存在匹配的用户,则重定向到login.jsp页面,并报告错误;否则进入下一步。

(5)LoginController调用UserService#findUserByUserName()方法,加载匹配的User对象,并更新用户最近一次登录时间和登录IP。

(6)LoginController调用UserService#loginSuccess()方法,进行登录成功的业务处理:首先调用UserDao@updateLoginInfo()方法为用户添加5个积分,然后创建一个LoginLog对象,并利用LoginLogDao将其插入数据库中。

(7)重定向到欢迎页面main.jsp,欢迎页面产生响应返回给用户。

2.2 环境准备

在进入实例的具体开发之前,需要做一些环境的准备工作,其中包括数据库表的创建、项目工程的创建、规划Spring配置文件等。如果还没有安装mysql,可以从http://www.mysql.org/downloads下载并安装。

提示:MySQL4.1.0之前的版本不支持事务,而Spring的各种声明式事务需要底层数据库的支持,所以最好安装5.0或更高版本的MySQL。各版本主要增加的特性如下:

           MySQL 5.0增加存储过程、视图、游标、触发器、XA事务。

           MySQL 5.1增加事件调度器、分区、可插拔的存储引擎API、行复制、全局动态查询日志修改。

2.3 持久层

持久层负责数据的访问和操作,DAO类被上层的业务类调用。Spring本身支持多种流行的ORM框架(第14章对此进行专门的讲解),这里使用Spring JDBC作为持久层的实现技术(关于Spring JDBC的详细内容,请参见第13章)。这里只是简单介绍,所以就算不了解Spring JDBC也能轻松阅读本章。

2.3.1 建立领域对象

领域对象(Domain Object)也被称为实体类,它代表了业务的状态,且贯穿展现层、业务层和持久层,并最终被持久化到数据库中。

在Spring2.5之后,可以使用注解的方式定义Bean。较之于XML配置方式,注解配置方式的简单性非常明显,已经被广泛接受。那么这里我们使用@Repository定义了一个DAO Bean。

例子1:LoginLogDao

复制代码
package com.smart.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.smart.domain.LoginLog;

@Repository
public class LoginLogDao {
    private JdbcTemplate jdbcTemplate;

    //保存登陆日志SQL
    private final static String INSERT_LOGIN_LOG_SQL= "INSERT INTO t_login_log(user_id,ip,login_datetime) VALUES(?,?,?)";
    
    public void insertLoginLog(LoginLog loginLog) {
        Object[] args = { loginLog.getUserId(), loginLog.getIp(),
                          loginLog.getLoginDate() };
        jdbcTemplate.update(INSERT_LOGIN_LOG_SQL, args);
    }

    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
复制代码

你可能有疑问,没有打开/释放Connection的代码,DAO类是如何访问数据库的呢?前面说过,重复性的操作都被JdbcTemplate封装起来了。UserDao和LoginLog都提供了一个带@Autowired注解的JdbcTemplate变量,

2.4 业务层

在论坛登录实例中,业务层仅有一个业务类,即UserService。UserService负责将持久层的UserDao和LoginLogDao组织起来,完成用户/密码认证、登录日志记录等操作。

2.4.1 UserService

UserService业务接口有3个业务方法,其中,hasMatchUser()方法用于检查用户名/密码的正确性;findUserByUserName()方法以用户名为条件加载User对象;loginSuccess()方法在用户登录成功后调用,更新用户最后登录时间和IP信息,同时记录用户登录日志。 

事务管理的代码虽然无须出现在程序代码中,但我们必须以某种方式告诉Spring哪些业务类需要工作在事务环境下及事务的规则等内容,以便Spring根据这些信息自动为目标业务类添加事务管理的功能。

复制代码
    <!-- 配置事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="dataSource" />
        
    <!-- 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="serviceMethod"
            expression="(execution(* com.smart.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" />
        <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>
复制代码

 

 

2.5 展现层 

业务层和持久层的开发任务已经完成,该是为程序提供界面的时候了。Struts MVC框架由于抢尽了天时地利,成为当下主流的展现层框架。但也有很多人认为Spring MVC相比于Struts更简单更强大更优雅。此外,由于Spring MVC出自Spring之手,因此和Spring容器没有任何阻抗,显得天衣无缝。

Spring3.0提供了REST风格的MVC,使得Spring MVC更轻便易用。Spring4.0对MVC进行了全面增强,支持跨域注解@CrossOrigin配置,Groovy Web集成,Gson、Jackson、Protobuf的HttpMessageConverter消息转换器等,使得Spring MVC的功能更加丰富和强大。(读者将在第18章学习到Spring MVC的详细内容)

2.5.1 配置Spring MVC框架

首先需要对web.xml文件进行配置,以便Web容器启动时能够自动启动Spring容器,如代码清单2-11所示。

代码清单2-11:自动启动Spring容器的配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:smart-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
......
复制代码

然后通过Web容器上下文参数指定Spring配置文件的地址。然后指定Spring所提供的ContextLoaderListener的Web容器监听器。

最后需要配置Spring MVC相关的信息。Spring MVC像Struts一样,也通过一个Servlet来截获URL请求,然后再进行相关的处理,如代码清单2-12所示。

代码清单2-12:Spring MVC地址映射

复制代码
    <servlet>
        <servlet-name>smart</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>smart</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
复制代码

请求被Spring MVC截获后,首先根据请求的URL查找到目标的处理控制器,并将请求参数封装“命令”对象一起传给控制器处理;然后,控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。

2.5.2 处理登录请求

1. POJO控制器类

首先需要编写的是LoginController,它负责处理登录请求,完成登录业务,并根据登陆成功与否转向欢迎页面或失败页面,如代码清单2-13所示。

代码清单2-13 LoginController

复制代码
package com.smart.web;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.smart.domain.User;
import com.smart.service.UserService;

@RestController
public class LoginController{
    private UserService userService;
    
    @RequestMapping(value = "/index.html")
    public String loginPage(){
        return "login";
    }
    
    @RequestMapping(value = "/loginCheck.html")
    public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){
        boolean isValidUser =  userService.hasMatchUser(loginCommand.getUserName(),
                                        loginCommand.getPassword());
        if (!isValidUser) {
            return new ModelAndView("login", "error", "用户名或密码错误。");
        } else {
            User user = userService.findUserByUserName(loginCommand
                    .getUserName());
            user.setLastIp(request.getLocalAddr());
            user.setLastVisit(new Date());
            userService.loginSuccess(user);
            request.getSession().setAttribute("user", user);
            return new ModelAndView("main");
        }
    }

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}
复制代码

 标注了@Controller的类首先会是一个Bean,所以可以使用@Autowired进行Bean的注入。

一个控制器可以拥有多个处理映射不同HTTP请求路径的方法,通过@RequestMapping指定方法如何映射请求路径。

请求参数会根据参数名称默认契约自动绑定到相应方法的入参中。例如,loginCheck(HttpServletRequest request, LoginCommand loginCommand)方法中,请求参数会按名称匹配绑定到loginCommand的入参中。

请求响应方法可以返回一个ModelAndView,或直接返回一个字符串,SpringMVC会解析之并转向目标响应页面。

2.5.3 JSP视图页面

论坛登录模块共包括两个JSP页面,分别是登录页面login.jsp和欢迎页面main.jsp,我们将在这节完成这两个页面的开发工作。

1. 登录页面login.jsp

login.jsp页面有两个用处,既作为登录页面,又作为登录失败后的响应页面。所以在1处使用JSTL标签对登录错误返回的信息进行处理。在JSTL标签中引用了error变量,该变量正是LoginController中返回的ModelAndView("login","error","用户名或密码错误")对象所声明的error参数。

login.jsp的登录表单提交到/loginController.html,如2所示。<c:url value="loginController.html">的JSTL标签会在URL前自动加上应用部署根目录。假设应用部署在网站的bbt目录下,则<c:url/>标签将输出/bbt/loginController.html。通过<c:url/>标签很好地解决了开发和应用部署目录不一致的问题。

由于login.jsp放置在WEB-INF/jsp目录下,无法直接通过URL进行调用,所以它由LoginController控制类中标注了@RequestMapping(value="/index.html")的loginPage()进行转发,如代码清单2-13所示。

2. 欢迎页面main.jsp

登录成功的欢迎页面很简单,仅使用JSTL标签显示一条欢迎信息即可,如代码清单2-18所示。

复制代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>小春论坛</title>
</head>
<body>
    ${user.userName},欢迎您进入小春论坛,您当前积分为${user.credits};
</body>
</html>
复制代码

 

2.6 运行Web应用

基于Maven工程,运行Web应用有两种方式:第一种方式是在IDE工具中配置Web应用服务器;第二种方式是在pom.xml文件中配置Web应用服务器插件。

复制代码
<build>
        <plugins>
            <!-- jetty插件 -->
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.25</version>
                <configuration>
                    <connectors>
                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>8000</port>
                            <maxIdleTime>60000</maxIdleTime>
                        </connector>
                    </connectors>
                    <contextPath>/bbs</contextPath>
                    <scanIntervalSeconds>0</scanIntervalSeconds>
                </configuration>
            </plugin>
复制代码

Jetty插件常用配置选项说明如下。

在Connectors中配置Connector对象,包含Jetty的监听端口。如果不配置连接器,则默认监听端口会被设置为8080。本示例将通过连接器port选项,将监听端口设置为8000。

contextPath可选,用于配置Web应用上下文。如果不配置此项,则默认上下文采用pom.xml中设置的<artifactId>名称。本示例将上下文设置为bbs。

注意:这里出现jetty6版本过低的问题,导致jsp编译显示出错,查看:https://www.cnblogs.com/2008nmj/p/14760746.html

 

第三章 Spring Boot

Spring Boot是由Pivotal团队设计的全新框架,其目的是用来简化Spring应用开发过程。本章使用Spring Boot重新开发第2章的实例。

本章主要内容:

Spring Boot概览

Spring Boot快速入门

Spring Boot安装配置

Spring Boot应用实战

Spring Boot运维支持

 

3.1 Spring Boot概览

该框架使用了特定的方式来进行配置,从而使得开发人员不再需要定义一系列的样板化的配置文件,而专注于核心业务的开发,项目涉及的一些基础设施则交由Spring Boot来解决。

多年来,Spring配置复杂性一直为人所诟病,Spring IO子项目试图化解这一问题,但由于其主要侧重于解决集成方面的问题,因此Spring配置复杂性并没有得到本质的改观,如何实现简化Spring配置的呼声仍然高亢,直到Spring Boot的出现。Spring Boot可让开发人员不再需要编写复杂的XML配置文件,仅通过几行代码就能实现一个可运行的Web应用。

Spring Boot不是去再造一个“轮子”,它的“革命宣言”是为Spring项目开发带来一种全新的体验,从而大大降低Spring框架的使用门槛。

Spring Boot革新Spring项目开发体验之道,其实是借助强大的Groovy动态语言实现的,如借助Groovy强大的MetaObject协议、可插拔的AST转换器及内置的依赖解决方案引擎等。在其核心的编译模型中,Spring Boot使用Groovy来构建工程文件,所以它可以轻松地利用导入模板及方法模板对类所生成的字节码进行改造,从而让开发者仅用很简洁的代码就可以完成很复杂的操作。

Spring Boot的特点:

  • 从Spring Boot的名称中的Boot可以看出,它的作用在于创建和启动基于Spring框架的项目。可以很快的启动一个Spring应用,自动把相关的类库或框架加载进来。
  • 内嵌Tomcat和Jetty容器,不需要部署War文件到Web容器即可独立运行。

Spring Boot启动器

Spring Boot是由一系列启动器组成的,这些启动器构成一个强大的、灵活的开发助手。开发人员根据项目需要,选择并组合相应的启动器,就可以快速搭建一个适合项目需要的基础运行框架。例如,要开发一个基于Maven的Web项目,通常在模块的pom.xml文件中引入spring-mvc、spring-webmvc、jackson、tomcat等依赖模块;如果使用Spring Boot启动器,则只需要引用一个spring-boot-starter-web模块即可。下面先来了解一下Spring提供了哪些有用的启动器,如表3-1所示。

表3-1:Spring Boot启动器

  • spring-boot-starter
  • spring-boot-starter-amqp
  • spring-boot-starter-aop
  • spring-boot-starter-artemis
  • spring-boot-starter-batch
  • spring-boot-starter-cache
  • ........

3.2 快速入门

上一节学习了Spring Boot相关背景、特点、启动器的基础知识,下面通过一个示例,快速体验一下Spring Boot的功效。我们以Maven方式快速创建一个Spring Web应用,首先需要在pom.xml文件中引入Spring Boot依赖,如代码清单3-1所示。

代码清单3-1:Spring Boot依赖配置pom.xml

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>chapter3</artifactId>
    <name>spring4.x第三章实例</name>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
</project>
复制代码

配置好Spring Boot相关依赖之后,接下来就可以通过几行代码,快速创建一个Web应用,如代码清单3-2所示。、

代码清单3-2:论坛应用BbsDaemon

复制代码
@Controller
public class BbsController {
    @ResponseBody
    @RequestMapping("/")
    public String hello(){
        return "欢迎光临小春论坛!";
    }

    @ResponseBody
    @RequestMapping("/getInformation")
    public String json(){
        SystemInfo s = new SystemInfo();
        return s.testGetOSInfo2();
        //return "json";
    }
}
复制代码

3.3 安装配置

 

3.4 持久层

Spring框架提供了几种可选的操作数据库方式,可以直接使用Spring内置轻量级的JdbcTemplate,也可以使用第三方持久化框架Hibernate或MyBatis。

 

第四章 IoC容器

本章开始讲解Spring IoC容器的知识。为了理解Spring的IoC容器,我们将通过具体的实例详细地讲解IoC的概念。同时,本章将对Java反射技术进行快速学习,它是Spring实现依赖注入的Java底层技术,掌握Java反射技术有助于读者深刻理解IoC的知识,做到知其然并知其所以然。此外,本章还对Spring框架的3个重要的框架级接口进行了剖析,并对Bean的生命周期进行了讲解。通过本章的学习,读者可以掌握依赖注入的设计思想、实现原理,以及几个Spring IoC容器级接口的知识。

本章主要内容:

IoC概念所包含的设计思想

Java反射技术

BeanFactory、ApplicationContext及WebApplicationContext基础接口

Bean的生命周期

 

4.1 IoC概述

IoC(Inverse of Control,控制反转)是Spring容器的内核,AOP、声明式事务等功能在此基础上开发结果。但是IoC这个重要的概念却比较晦涩难懂,不容易让人望文生义,这不能不说是一大遗憾。不过IoC确实包括很多内涵,它涉及代码解耦、设计模式、代码优化等问题的考量,我们试图通过一个小例子来说明这个概念。

假设要用Java来编写一个剧本,如代码清单4-1

public class MoAttack{
  public void cityGateAsk(){
    LiuDeHua ldh = new LiuDeHua();
    ldh.responseAsk("墨者革离!");
  }
}

我们会发现,作为具体角色饰演者的演员直接写到剧本对象MoAttack中,使得剧本和具体的某个演员密不可分。

但是明智的编剧只应该考虑角色,而不应该和演员绑定在一起。通过以上分析,我们知道需要为该剧本的主人公革离定义一个接口,如代码清单4-2所示。

public class MoAttack{
  public void cityGateAsk(){
    GeLi geli = new LiuDeHua();
    geli.responseAsk("墨者革离!");
  }
}

从上面可以看出,MoAttack同时依赖于GeLi接口和LiuDeHua类,并没有达到我们所期望的剧本仅依赖于角色的目的。但是角色最终又必须通过具体的演员才能完成,那么如何让LiuDeHua和剧本无关而又能完成GeLi的具体动作呢?当然时,在电影开拍时再将演员和角色关联。

通过引入导演,使得剧本和具体饰演者解耦。对应到软件中,导演就像一台装配器,安排演员表演具体的角色。

现在我们可以反过来讲解IoC的概念了。IoC(Inverse of Control)的字面意思是控制反转,它包括两方面的内容:

  • 其一是控制。
  • 其二是反转。

那到底是什么东西的“控制”?又是怎么被“反转”了呢?“控制”是指选择GeLi角色扮演者的控制权;“反转”是指这种控制权被从剧本中移除,转交到导演的手上。对于软件来说,即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,即由Spring容器借由Bean配置来进行控制。

因为IoC不够开门见山,Martin Fowler提出了DI(Dependency Injection,依赖注入)的概念用来代替IoC,即让调用类对某一接口实现类的依赖关系由第三方注入,以移除调用类对某一接口实现类的依赖。

IoC的类型:

  • 构造函数注入
  • 属性注入

以上,虽然MoAttack和LiuDeHua实现了解耦,MoAttack无须关注角色实现类的实例化工作,但是这些工作在代码中依然存在,只是转移到Director类而已。假设想改变这一局面,就需要通过媒体“海选”或者第三方代理机构来选择导演和演员,这样就实现了解耦。

所谓的媒体“海选”和第三方机构,在程序领域就是一个第三方的容器,它帮助完成类的初始化和装配工作,让开发者从这些底层实现类的实例化、依赖关系装配等工作中解脱出来,专注于更有意义的业务逻辑开发工作。这无疑是一件令人向往的事情。

Spring就是这样的一个容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入工作。下面是Spring配置文件对以上实例进行配置的配置文件片段:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w4.org/2001/XMLSchema-instance" 
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
  <bean id="geli" class="LiuDeHua" />
  <bean id="moAttack" class="com.smart.ioc.MoAttack" p:geli-ref="geli" />
</beans>

通过new XmlBeanFactory("beans.xml")等方式即可启动容器。在容器启动时,Spring根据配置文件的描述信息,自动实例化Bean并完成依赖关系的装配,从容器中即可返回准备就绪的Bean实例,后续可直接使用之。

Spring为什么会有这种“神奇”的力量,仅凭一个简单的配置文件,就能魔法般地实例化并装配好程序所用的Bean呢?这归功于Java语言本身的类反射功能。

4.2 相关Java基础知识

Java语言允许通过程序化的方式间接对Class进行操作。Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息,如构造函数、属性和方法等。

以一个示例了解Java反射原理。

使用传统方法:

复制代码
public class Car{
  private String brand;
  private String color;
  private int maxSpeed;
  
  //默认构造函数
  public Car(){}

  //带参构造函数
  public Car(String brand, String color, int maxSpeed){
    this.brand = brand;
    this.color = color;
    this.maxSpeed = maxSpeed;
  }

  //未带参的方法
  public void introduce(){
     System.out.println("brand:" + brand +";color:" + ";mxSpeed:" + maxSpeed);
  }
  //省略参数的getter/setter方法
  ...
}
复制代码

 一般情况下,我们会使用如下代码创建Car的实例:

Car car = new Car();

car.setBrand("红旗CA72");

或者是:

Car car = new Car("红旗CA72","黑色");

以上两种方法都是采用传统方式直接调用目标类的方法。下面我们通过Java反射机制以一种间接的方式操控目标类,如代码清单4-10所示。

复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTest{
  public static Car initByDefaultConst() throws Throwable{
    //通过类装载器获取Car类对象
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    Class clazz = loader.loadClass("com.smart.reflect.Car");
    //获取类的默认构造器对象并通过它实例化Car
    Constructor cons = clazz.getDeclaredConstructor((Class[])null);
    Car car = (Car)cons.newInstance();
    //通过反射方法设置属性
    Method setBrand = clazz.getMethod("setBrand", String.class);
    setBrand.invoke(car, "红旗CA72");
    Method setColor = clazz.getMethod("setColor", String.class);
    setColor.invoke(car, "黑色");
    Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
    setMaxSpeed.invoke(car, 200);
    return car;
  }
}
复制代码

 

posted on   XiaoNiuFeiTian  阅读(69)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
历史上的今天:
2019-05-07 第一个java程序
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示