Spring实践之1:开始使用Spring

来源:互联网 发布:知乎 撩妹技巧 编辑:程序博客网 时间:2024/06/05 23:47

  • 简介
  • 从一个小程序开始
    • 创建工程
    • 实现功能
    • 运行程序
  • 使用Spring
    • 工欲善其事必先利其器
    • IOCDIAOP
    • 改造
    • 遗留问题

简介

此“Spring实践”系列是笔者在开发过程中对Spring相关使用经验的简单总结,欢迎各位读者与笔者讨论或斧正。

从一个小程序开始

假设我们需要开发一个程序,主要功能是向Product Manager询问某个product的开发进度,而Product Manager又转而询问Software Engineer。

创建工程

首先我们使用maven创建一个简单的Java工程:

$ mvn archetype:generate[INFO] Scanning for projects...[INFO][INFO] ------------------------------------------------------------------------[INFO] Building Maven Stub Project (No POM) 1[INFO] ------------------------------------------------------------------------[INFO][INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) > generate-sources @ standalone-pom >>>[INFO][INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) < generate-sources @ standalone-pom <<<[INFO][INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom ---[INFO] Generating project in Interactive mode[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)Choose archetype:1: remote -> am.ik.archetype:maven-reactjs-blank-archetype (Blank Project for React.js)2: remote -> am.ik.archetype:msgpack-rpc-jersey-blank-archetype (Blank Project for Spring Boot + Jersey)3: remote -> am.ik.archetype:mvc-1.0-blank-archetype (MVC 1.0 Blank Project)……1895: remote -> us.fatehi:schemacrawler-archetype-plugin-command (-)1896: remote -> us.fatehi:schemacrawler-archetype-plugin-dbconnector (-)1897: remote -> us.fatehi:schemacrawler-archetype-plugin-lint (-)Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1007:Choose org.apache.maven.archetypes:maven-archetype-quickstart version:1: 1.0-alpha-12: 1.0-alpha-23: 1.0-alpha-34: 1.0-alpha-45: 1.06: 1.1Choose a number: 6: 6Define value for property 'groupId': : practice.springDefine value for property 'artifactId': : spriceDefine value for property 'version':  1.0-SNAPSHOT: : 1.0Define value for property 'package':  practice.spring: : practice.spring.spriceConfirm properties configuration:groupId: practice.springartifactId: spriceversion: 1.0package: practice.spring.sprice Y: : y[INFO] ----------------------------------------------------------------------------...[INFO] BUILD SUCCESS...

这里笔者使用了“mvn archetype:generate”没有附带参数的用法,所以maven进入了Interactive Mode,然后我们选择了“maven-archetype-quickstart”这个archetype,指定groupId,artifactId,version,package分别为practice.spring,sprice,1.0和practice.spring.sprice。
自动生成的App是一个HelloWorld程序,在这个基础上,我们开始增加需要的功能。

sprice即“spring practice”

实现功能

在这个程序里,笔者定义了两个interface:Manager和Engineer,并使用两个class:ProductManager和SoftwareEngineer来分别实现它们。

Manager.java

package practice.spring.sprice;public interface Manager {    String queryProgress(String productName);}

Engineer.java

package practice.spring.sprice;public interface Engineer {    String reportProgress(String productName);}

ProductManager.java

package practice.spring.sprice;public class ProductManager implements Manager {    private Engineer engineer = new SoftwareEngineer();    @Override    public String queryProgress(String productName) {        return this.engineer.reportProgress(productName);    }}

SoftwareEngineer.java

package practice.spring.sprice;import org.apache.commons.math3.random.RandomDataGenerator;public class SoftwareEngineer implements Engineer {    @Override    public String reportProgress(String productName) {        int progress = (new RandomDataGenerator()).nextInt(1, 100);        return String.format("%d%%", progress);    }}

可以看到ProductManager的queryProgress()调用了SoftwareEngineer的reportProgress(),而后者从1到100中随机了一个值作为工作进度返回。

修改App.java,调用这些新的模块。

App.java

package practice.spring.sprice;public class App {    public static void main(String[] args) {        (new App()).run();    }    public void run() {        String productName = "3D-Map";        Manager manager = new ProductManager();        String progress = manager.queryProgress(productName);        System.out.println(progress);    }}

由于代码中引用了“org.apache.commons.math3.random.RandomDataGenerator”,所以需要在pom.xml中增加dependency:
pom.xml

<dependency>    <groupId>org.apache.commons</groupId>    <artifactId>commons-math3</artifactId>    <version>3.6.1</version></dependency>

编译这个工程:

$ mvn clean package...

编译完成后,在target文件夹下会生成一个.jar文件:

$ ls ./target/classes/  maven-archiver/  maven-status/  sprice-1.0.jar  surefire-reports/  test-classes/

此时这个sprice-1.0.jar还不能直接使用“java -jar”命令运行,因为:
1. Manifest中没有主清单属性。
2. 依赖的“org.apache.commons.math3.random.RandomDataGenerator”在.jar文件中不存在。

所以接下来我们需要对pom.xml做适当更改,以使程序运行起来。

运行程序

关于如何创建可运行的fat-jar,请参考maven打包可运行的fat-jar的简单方法。

修改之后,就可以编译运行了:

$ mvn clean package...
$ ls ./target/archive-tmp/  classes/  maven-archiver/  maven-status/  sprice-1.0.jar  sprice-1.0-jar-with-dependencies.jar  surefire-reports/  test-classes/$ java -jar ./target/sprice-1.0-jar-with-dependencies.jar29%

使用Spring

接下来就是使用Spring来改造sprice这个小程序了。但在这之前,还是要先探讨一下,为什么要使用Spring来搭建程序呢?

工欲善其事必先利其器

软件设计里面有一个重要原则:强内聚,低耦合。在此基础上,重用性、鲁棒性、可维护性等等是评价一个软件设计是否优秀的重要指标。
在软件设计过程中,有很多种对应的方法来达成这些目标。Spring遵循的是“控制反转/依赖注入+面向切面编程”(IOC/DI+AOP)。

IOC/DI+AOP

关于“IOC/DI+AOP”这些概念的定义,在网上可以搜索到很多。如果列举它到底要做什么,那么大概有以下几点:
1. Framework扮演了Container(容器)的角色。
2. 使用者负责定义各个组件以及它们的配置方法。
3. Container负责创建、装配组件和管理组件的生命周期。
4. 以编织(weaving)的方式扩展组件功能。

以sprice中的ProductManager为例,ProductManager对SoftwareEngineer对象的使用存在两个问题:
1. Engineer对象成为了ProductManager的一个组成部分(即组合关系)。
2. ProductManager与Engineer的子类SoftwareEngineer形成了强耦合。

所以接下来使用Spring对sprice这个小程序做一些改造,以达到强内聚、低耦合的目的。

改造

首先把ProductManager与SoftwareEngineer解耦并增加Setter:

package practice.spring.sprice;public class ProductManager implements Manager {    private Engineer engineer;    @Override    public String queryProgress(String productName) {        return this.engineer.reportProgress(productName);    }    public void setEngineer(Engineer engineer) {        this.engineer = engineer;    }}

然后增加一个Spring Context的配置文件“ApplicationContext.xml”:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:aop="http://www.springframework.org/schema/aop"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="        http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        http://www.springframework.org/schema/context/spring-context.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="softwareEngineer" class="practice.spring.sprice.SoftwareEngineer">    </bean>    <bean id="productManager" class="practice.spring.sprice.ProductManager">        <property name="engineer" ref="softwareEngineer" />    </bean></beans>

在“ApplicationContext.xml”中我们配置为在容器中创建两个bean“softwareEngineer”和“productManager”,然后在“App.java”里面就可以使用这个xml创建ApplicationContext并获得bean:

注:ClassPathXmlApplicationContext会加载jar包中指定路径的文件。若配置文件在jar包之外,则可以使用FileSystemXmlApplicationContext,文件路径可以是绝对路径或相对路径(这个相对路径不是配置文件相对于jar包的路径,而是配置文件相对于运行java命令之工作目录的路径)。

package practice.spring.sprice;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class App {    public static final String PRODUCT_NAME = "3D-Map";    public static void main(String[] args) {        String xmlPath = "context/ApplicationContext.xml";        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);        Manager manager = (Manager)applicationContext.getBean("productManager");        String progress = manager.queryProgress(PRODUCT_NAME);        System.out.println(progress);    }}

在“pom.xml”中添加对“org.springframework.context”的依赖:

<dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>4.3.9.RELEASE</version></dependency>

此时整个项目的文件目录结构为:

│  pom.xml│└─src    ├─main       ├─java       │  └─practice       │      └─spring       │          └─sprice       │                  App.java       │                  Engineer.java       │                  Manager.java       │                  ProductManager.java       │                  SoftwareEngineer.java       │       └─resources           └─context                   ApplicationContext.xml

编译运行:

$ mvn clean package...$ java -jar ./target/sprice-1.0-jar-with-dependencies.jar一月 15, 2017 6:55:20 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@17a7cec2: startup date [Sun Jan 15 18:55:20 CST 2017]; root of context hierarchy一月 15, 2017 6:55:21 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions信息: Loading XML bean definitions from class path resource [context/ApplicationContext.xml]51%

遗留问题

在改造过程中,虽然ProductManager与SoftwareEngineer解耦了,但是它们仍然是聚合关系,这其实是不符合现实中产品经理与软件工程师的真实关系的。

这里仅提出一种解决方案:增加一个类Group(产品小组),Group聚合Engineer,而Manager关联Group和Engineer。