详解服务器端的项目框架

导读

我一直相信这句话,他山之石可以攻玉。在自己能力不够时,多学习别人的东西。这样,对自己只有好处,没有坏处。因而,经过将近一年的工作,研读了公司所使用的框架。我本想往架构师的方向靠近,但,自己的能力可能还不够,因而,不断地给自己充电。

公司的项目是前后端分离的,前端使用HTML5,css3、jquery、vue.js、bootstrap等,以SVN做代码托管。后端采用maven构建项目,以git lab做代码托管。肯定有人会问,这两个都是版本库,但它们有什么区别?如果想要了解的话,可以参考该文档:Svn与Git的区别

现在几乎所有的公司都采用maven构建项目,很少会采用导入jar包的方式依赖第三方框架。

maven介绍

maven构建的项目有很多好处,首先其可以统一管理jar包,也就是说,我们在项目中不用手动导入jar包,我们只要添加依赖即可,如代码所示:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${jdbc.version}</version>
</dependency>

添加依赖之后,maven就会导入该jar包,导入jar包的顺序为:

  • 首先查找本地仓库,如果本地仓库没有,进入下面步骤。
  • maven settings profile中的repository;
  • pom.xml中profile中定义的repository。profile激活配置文件,比如正式环境的配置文件prd.properties和开发环境的Dev.properties文件。这也是打包的依据,是打开发环境的包,还是打正式环境的包,如图所示:

《详解服务器端的项目框架》

  • pom.xml中的repositorys(定义多个repository,按定义顺序找);
  • 如果经过上面的步骤,没有找到相应的jar包,最后到我们的镜像(mirror)中查找。如果mirror中存在该jar包,从mirror中拷贝下来,存储到本地仓库中,进入到最初一步。如果mirror中也没有,maven就会报相应的错误。

maven报出相应的错误时,也许,是我们本地没有该jar包,远程仓库也没有该jar包,我们可以参考这篇博客:在maven的pom.xml中添加本地jar包。它会教你如何创建本地仓库,并导入创建好的本地仓库。

【备注】这篇博客以架构师的角度来讲解maven,所以,不具体讲解maven各个标签的含义。如果你想了解pom的各个标签的含义,可以参考这篇文档:pom.xml详解,或者,参考这篇教程:maven教程|菜鸟教程

上面解说只是配置jar文件,但是,maven的功能远不止这些,从我们创建maven项目时,就已经进入到maven的开发环境中。

maven项目

有人和我说过,学一项知识,什么方式最快?那就是通过做项目。你在做项目的过程中,肯定会遇到很多问题,而你不得不去查找资料,从根源上认识到这个问题。因而,我以公司所做的某个项目为例,来讲解maven的依赖、继承、聚合等关系。

我们所说的maven中的关系,其实就是pom的关系,即项目对象模型(Project Object Model)的简称。

maven聚合

首先,我们在创建cloudCodeSale项目时,就已经创建了父pom文件,如图所示:

《详解服务器端的项目框架》

上图就是我们的父pom文件,你很清楚的看到gav坐标。同时,你从这张图上,也能得到其他信息,其打包方式是 pom,其还关联其他module,module名称和左边的列表名称一样。这就是我们所说的maven的聚合。父类同时聚合其子类。

聚合的条件有两个:

  1. 修改被聚合项目的pom.xml中的packaging元素的值为pom
  2. 在被聚合项目的pom.xml中的modules元素下指定它的子模块项目

既然所有的子模块的pom都继承父pom,为什么父pom要聚合子模块的pom文件?这个问题很好。

因为对于聚合而言,当我们在被聚合的项目上使用Maven命令时,实际上这些命令都会在它的子模块项目上使用。这就是Maven中聚合的一个非常重要的作用。在实际开发的过程中,我们只需要打包(mvn install)父pom文件。

我们在父pom上使用mvn celan、mvn compile和mvn package,其会自动对子模块:platform-core、platform-core-controller、portal/member-portal、portal/platform-portal、platform-cms、platform-cms-controller、platform-custom执行mvn celan、mvn compile和mvn package。没必要一个一个地打包,这样极容易出现错误,如图所示:

《详解服务器端的项目框架》

maven继承机制

如果很多模块都需要相同的jar包,我们可以单独写在一个pom中,其他模块使用该模块的公共部分,这就是我们常说的父类。不论是java语言,还是c++语言,或者现在的pom,其都体现这个思想。

我们在上文也提到了子模块,现在模块platform-core讲解。继承父类的结构一般是这样的:

<parent>  
    <groupId>parent.groupId</groupId>  
    <artifactId>parent.artifactId</artifactId>  
    <version>parent.version</version>  
       <relativePath>../pom.xml</relativePath>  
  </parent>  

relativePath是父pom.xml文件相对于子pom.xml文件的位置,针对被继承的父pom与继承pom的目录结构是不是父子关系。如果是父子关系,则不用该标签;如果不是,那么就用该标签。因为在当前项目中,platform-core模块的目录的pom在父目录的pom中,其和父pom的目录结构是父子关系,因而可以省略relativePath该标签,如图所示:

《详解服务器端的项目框架》

parent标签中的groupId、artifactId、version要和父pom中的标签中一致。

maven的依赖关系

正如我们所知道的,maven构建项目,不仅是因为其能够管理jar包,其还使模块化开发更简单了。因而,我们在开发的过程中,一般都分模块化开发。模块与模块之间的信息是不通的,但是,我们想要模块间的之间能够通信,这时,我们就想到了java中的依赖关系。

比如,我们有一个模块,这个模块封装好了微信支付、支付宝支付、处理json格式、操作文件、ResultUtil、lambdaUtil、commonUtil等工具类,还有附件、头像、用户等实体类。这些工具类在任何项目中不会轻易改变,如果为了满足某些需求而不得不得修改,需要得到架构师的同意。

因而,我们可以把它拿出来,单独定义为一个模块,也就是platform-core模块。但是,我们还有一个模块,在这个模块中,根据不同的项目,其定义不同的实体类、dao层类、事务层类、枚举类、接收前端传来参数封装成的query类、从数据库中取出的数据封装成的data类,到事务层可能会调用模块plateform-core中的方法,比如调用第三方系统接口的HTTPClientUtil.doPost(String url, Map<String, String> param),判断处理lambda表达式的LambdaUtil.ifNotBlankThen(String value, Consumer<String> function) ,等等。

这个自定义类的模块,我们可定义为plateform-custom。plateform-custom需要用到platform-core中的方法,因而,这时,我们就需要考虑依赖关系,怎么添加对platform-core的依赖呢?如代码所示:

<dependency>
    <groupId>com.zfounder.platform</groupId>
    <artifactId>platform-core</artifactId>
</dependency>

我们这边是前后台分离的,后台用来录入数据,前台用来展示数据,因而,我们有一个portal目录,该目录下有两个子模块。一个是member-portal模块,一个是platform-portal模块,前者接收前台的接口参数,后者接收后台的接口参数。但不论哪个模块,都需要依赖plateform-custom中的事务层方法,同时,我们传的参数,可能信息分发的platform-cms-controller中的接口,也可能是核心接口platform-core-controller中的接口。因而,我们这里以member-portal模块来举例,依赖其他模块的代码如下:

<dependencies>
    <dependency>
        <groupId>com.zfounder.platform</groupId>
        <artifactId>platform-core-controller</artifactId>
    </dependency>
    <dependency>
        <groupId>com.zfounder.platform</groupId>
        <artifactId>platform-cms-controller</artifactId>
    </dependency>
    <dependency>
        <groupId>com.zfounder.platform</groupId>
        <artifactId>platform-custom</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

这些模块你会在上面的图片找得到。同时,我们来看member-portal的pom文件继承的父pom是怎么写的:

《详解服务器端的项目框架》

补充上面的继承关系。这里面用到了<relativePath>../../pom.xml</relativePath>你会奇怪的是,为什么这里面用到了呢?其和父pom不是父子关系,而是孙子关系。这里使用到了两次点点,这是什么意思呢? ..表示上级目录。举个例子说明:

比如,在我的服务器上的www目录中,有三个文件,分别是rsa_private_key.pem, rsa_private_key_pkcs8.pem, rsa_public_key.pem,还有一个testDir目录,testDir目录中还有目录testDir,现在我们通过
cd ../testDir/testDir进入到子目录中,现在,我们想返回到www的根目录中,并查看rsa_public_key.pem文件的内容,因而,我们可以用
cat ../../rsa_public_key.pem命令,其首先返回两级目录,然后找到rsa_public_key.pem文件并打开该文件。

《详解服务器端的项目框架》

“被继承的父pom与继承pom的目录结构是不是父子关系”也不是绝对的,主要是选择继承者的pom中的子目录和父目录之间的关系,其中间隔了几层目录。

maven激活文件

激活文件在上文也提到了,我们为什么需要激活文件?如下面的两个配置文件,一个是测试环境的配置文件,名为platform-dev.properties,一个是正式环境的配置文件,名为platform-prd.properties。两个配置文件中都存在与数据库的连接,但是呢,数据库的ip地址是不一样的。如一下的代码所示:

  • 正式服的platform-prd.properties配置文件
jdbc.url=jdbc:mysql://localhost/prd_database
jdbc.username=prd_username
jdbc.password=prd_password

jdbc.validationQuery=select 1 from dual
jdbc.removeAbandonedTimeout=180
jdbc.initialSize=10
jdbc.minIdle=30
jdbc.maxActive=100
jdbc.maxWait=30000

。。。
  • 测试服的platform-dev.properties配置文件
jdbc.url=jdbc:mysql://intranet_ip/dev_database
jdbc.username=dev_username
jdbc.password=dev_password

jdbc.validationQuery=select 1 from dual
jdbc.removeAbandonedTimeout=180
jdbc.initialSize=10
jdbc.minIdle=30
jdbc.maxActive=100
jdbc.maxWait=30000

。。。

我们的在配置文件中配置好了数据项,但是呢,我们怎么切换不同的配置文件呢?换句话说,我们怎么想要打正式服的包放到正式服上,怎么选择platform-prd.properties的配置文件呢?反之,怎么选择platform-dev.properties配置文件?

这时,我们就用到了maven当中的profile标签,如下代码所示:

 <profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <build>
            <filters>
                <filter>../../platform-dev.properties</filter>
            </filters>
        </build>
    </profile>
    <profile>
        <id>prd</id>
        <build>
            <filters>
                <filter>../../platform-prd.properties</filter>
            </filters>
        </build>
    </profile>
</profiles>

这些配置文件时写在member-portal、platform-portal、plateform-core和plateform-cms、plateform-customer模块的pom中的。但是,plateform-core和plateform-cms的配置中的filter和上面连个略有差异,其filter是这样的 <filter>../platform-dev.properties</filter> <filter>../platform-prd.properties</filter>,这就涉及到目录点的问题。

maven依赖第三方包

maven项目除了依赖本项目的,其还会依赖第三方包,比如自动生成set和get方法的lombok包,处理json格式的阿里巴巴下的fastjson包等等,我们也可以使用这种格式的依赖:

<!--mysql jdbc驱动包 开始-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${jdbc.version}</version>
</dependency>
<!--mysql jdbc驱动包 结束-->

开发常用的jar包

lombok

<!-- lombok驱动包 开始-->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.10</version>
</dependency>
<!-- lombok驱动包 开始-->

我们在没有使用lombok之前,经常手动创建javabean的set个get方法,使用这个框架之后,其以注解的方式,可以自动生成set和get方法。同时,其强大的功能远不止这些,也可以生成无参构造器、全参构造器,指定参数构造器、重写toString方法、重写Hashcode、equals方法等等。

如代码所示:

/**
 * Created By zby on 17:37 2019/1/30
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class Address {

    /**
     * 收货人
     */
    private String consignee;

    /**
     * 手机号码
     */
    private String phone;

    /**
     * 所在地区
     */
    private String area;

    /**
     * 详细地址
     */
    private String detail;

    /**
     * 标签
     */
    private AddressTagEnum addressTag;
}

想要更深层次的了解这个框架,可以参考这个博客:Lombok使用详解

fastjson

<!-- fastjson驱动包 开始-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.28</version>
</dependency>
<!-- fastjson驱动包 结束-->

fastjson是阿里巴巴开源的框架,其用来处理服务器端的json格式的数据。比如,我们需要将服务端的对象以json(JavaScript object Notation,js对象标记)格式传输到前端,但是,如果自己手动创建的话,势必会非常的麻烦,于是,我们借助这个框架,帮助我们生成json格式的对象。

同时,如果我们要调用第三方接口,比如调用连连绑定银行卡的接口,其返回给我们的也是json对象的数据。但是,我们需要将其转化为我们定义的对象,调用其save方法,保存到数据库中,对账所用。

对于,将对象转化为json格式的对象,如代码所示:

@Test
public void test() {
//        地址
    Address address = new Address();
    address.setAddressTag(AddressTagEnum.ADDRESS_TAG_COMPANY);
    address.setArea("杭州市....");
    address.setConsignee("zby");

//        用户
    User user = new User();
    user.setHobby(HobbyEnum.HOBBY_DANCING);
    user.setGender("男");
    user.setUserName("蒋三");

//        订单
    OrderSnapshot orderSnapshot = new OrderSnapshot();
    orderSnapshot.setAddress(address);
    orderSnapshot.setId(1L);
    orderSnapshot.setName("复读机");
    orderSnapshot.setOrderNo(Long.valueOf(System.currentTimeMillis()).toString() + "1L");
    orderSnapshot.setUser(user);

    System.out.println(JSON.toJSON(orderSnapshot));
}

其输出结果如图所示:

《详解服务器端的项目框架》

但是,类似于解析json格式的数据,不只有fastjson框,还有org.json框架、Jackson框架。但经过有人验证呢,还是fastjson的效率更高一些。可以参考这篇博客:Gson、FastJson、org.JSON到底哪一个效率更高,速度更快

org.json也是通过maven配置的,如代码所示:

<!-- json驱动包 开始-->
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20140107</version>
</dependency>
<!-- json驱动包 开始-->

如果想要深层次了解org.json,可以参考这篇博客:Java使用org.json.jar构造和解析Json数据

想要更深层次的了解fastjson,可以参考这篇博客:Fastjson 简明教程

spring相关配置

如果从事java-web开发,一般会用到spring框架,这方面的教程太多了,笔者就不在这介绍,但我们会用到spring的这些框架:

<!--spring 相关配置开始-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>

spring会集合很多框架,比如具有拦截效果的shiro框架,持久层的hibernate和mybatis框架等等。spring通过配置文件通过注解或者配置文件的方式,实现依赖注入(dependency injection)和控制反转(inversion of control)。通过@controller遍历相应的接口,实现前后端的接口对接。spring可以实现面向切面编程,实现某个业务点的单一执行。比如,专门处理事务的业务点。

spring并不难,很快就能掌握到其精髓。

如果想深入了解,可以参考这篇教程:Spring教程

hibernate框架

hibernate框架就类似于mybatis框架,其专门处理持久层的技术。我们将瞬时状态的对象存储到数据库中,变成持久状态的对象。我们也可以从数据库中取数据,以瞬时态的对象返回到前端。这就是一存一取的框架。

其可以使用注解的方式创建数据表,也可以通过配置文件创建瞬时态的对象。但就目前为止,在很多情况下,我们都是通过注解的方式,实现数据表的创建。导入hibernate相关的框架,如下所示:

<!--hibernate相关配置 开始-->
<!--hibernateh核心框架-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
</dependency>
<!--hibernateh验证器-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<!--hibernateh缓存技术-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>
 <!--Java Persistence API ORM映射元数据 查询语言-->
<dependency>
    <groupId>org.hibernate.java-persistence</groupId>
    <artifactId>jpa-api</artifactId>
</dependency>
<!--hibernate相关配置 结束-->

hibernate和mybatis具有同样的功能,如果想要了解mybatis,可以参考这篇教程:mybatis教程

想要深入理解hibernate,可参考这篇教程:hibernate教程_w3cschool

jbdc驱动包

我们上面说了hibernate框架,但前提是,我们需要导入jdbc的框架包,如代码所示:

 <!-- 数据库驱动包相关 开始-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 数据库驱动包相关 结束-->

这个驱动包主要处理java与数据库的连接,实现数据的增、删、改、查。我们没有用到这个包,但是hibernate用到了这个包,因而,我们需要导入这个包,以免数据库报错。

alibaba的Druid包

这个包有什么用吗?我们既然通过hibernate实现与数据库的交互,那么就需要在初始化时创建连接池。现在连接池有很多种,我们为什么选择了它Druid。

<!-- Druid驱动包相关 开始-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>
<!-- Druid驱动包相关 结束-->

它是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid是阿里巴巴开发的号称为监控而生的数据库连接池!

时代在变化,我们也应该应世而生,与时俱进,才不会和时代脱轨。

我们使用Druid来实现数据的配置,如代码所示:

 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="maxActive" value="${jdbc.maxActive}"/>
    <property name="initialSize" value="${jdbc.initialSize}"/>
    <property name="removeAbandoned" value="true"/>
    <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
    <property name="testOnBorrow" value="true"/>
    <property name="minIdle" value="${jdbc.minIdle}"/>
    <property name="maxWait" value="${jdbc.maxWait}"/>
    <property name="validationQuery" value="${jdbc.validationQuery}"/>
    <property name="connectionProperties" value="clientEncoding=UTF-8"/>
</bean>

你们可以看到,value值是形参,而不是具体的值。因为我们根据不同的打包方式,其传入形参对应的实参不同。这也就是我们上文提到的,platform-dev.properties和platform-prd.properties配置文件,以及maven配置的激活文件。

如果想要深入了解阿里巴巴的Druid框架,可以参考这篇博客:DRUID连接池的实用 配置详解

阿里云短信

短信业务一般固定不变,由架构师封装好,其他人直接调用即可,因而,该框架可以写进plateform-core模块中,其配置的代码如下所示:

<!-- 阿里短息驱动包配置 开始 -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<!-- 阿里短息驱动包配置 结束 -->

日志相关配置

我们在开发的过程中,经常会使用到日志,来记录相应的错误、警告、信息。比如,我在使用连连支付做提现业务时,提现成功后其会回调我们的接口,从而显示在服务端的Tomcat页面中。再比如,我们在登录时,其会在Tomcat中显示相关信息,如图所示:

《详解服务器端的项目框架》

我们都知道日志分为几种级别。这里就不再赘述了。日志分为好多种,我们推荐使用slf4j+logback模式。因为logback自身实现了slf4j的接口,无须额外引入适配器,另外logback是log4j的升级版,具备比log4j更多的优点,我们可以通过如下配置进行集成:

<!-- 日志驱动包配置 开始 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.7</version>
</dependency>
<!-- 日志驱动包配置 结束 -->

我们这时就用到了plateform-prd.properties文件和plateform-dev.properties文件,因为,我们需要在这里面配置日志的输出位置。然后,在logback.xml中以参数的形式,调用文件中的输出位置,如图所示:

《详解服务器端的项目框架》

如果想要了解更多的配置文件信息,请参考这篇博客:使用 logback + slf4j 进行日志记录

commons家族

我们在开发的过程中,经常用到Commons家族的驱动包,比如文件操作的io包,MD5加密和解密用的codec包。当然,我们也会用到java自带的local_policy驱动包,但有时需要替换替换该驱动包,否则,就会报出Illegal Key Size的错误。文件上传下载的fileupload驱动包,操作字符串类型的lang3包,配置的驱动包如下所示:

<!--comon包相关配置-->
<commons-io.version>2.4</commons-io.version>
<commons-lang3.version>3.4</commons-lang3.version>
<commons-codec.version>1.10</commons-codec.version>
<commons-fileupload.version>1.3.1</commons-fileupload.version>

<!-- apache common 开始 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${commons-lang3.version}</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>${commons-io.version}</version>
</dependency>
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>${commons-codec.version}</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>${commons-fileupload.version}</version>
</dependency>
<!-- apache common 结束 -->
  • lang3包

我们可以用其分割字符串,判断字符串是否为空格,判断字符串是否为空等等。如以下代码所示:

public static void main(String[] args) {
    String keyword = "1-1-2";
    if (StringUtils.isNotBlank(keyword)) {
        System.out.println("keyword = " + keyword);
    }
    String[] keys = StringUtils.split(keyword, "-");
    for (String key : keys) {
        System.out.println("key=" + key);
    }
}

我们有时还会用其操作时间类,比如格式化时间等等,入一下代码:

@Test
public void testDate(){
   String date1= FastDateFormat.getInstance("yyyy-MM-dd").format(System.currentTimeMillis());
    System.out.println("System.currentTimeMillis:"+date1);
    String date2= FastDateFormat.getInstance("yyyy-MM-dd").format(new Date());
    System.out.println("new Date:"+date2);
}

其功能远不止这些,具体可以参考这篇博客:commons-lang3工具包

  • io包

见名知意,IO即input和output的简写,即输入流和输出流。因而,我们经常使用到java自带的InputStream或FileInputStream的字节输入流,以及OutputStream或FileOutputStream的输出流。如果更高级的话,那么,就使用到了带有缓存效果的bufferReader输入流和bufferWrite输出流。这里面用到了装饰设计模式。什么是装修设计模式,可以自行学习。

上面的操作比较复杂,我们就用到了apache下的io驱动包。这里就当做抛砖引玉了,想要有更深的了解,可以参考这篇博客:io包工具类

  • codec包

codec包是Commons家族中的加密和解密用的包,这里不做任何解释,具体可以参考这篇博客:Commons Codec基本使用

  • fileupload包

我们如果做java-web开发,经常会有文件上传和文件下载的功能。这时,我们就考虑到了Apache下面的 fileupload包,这可以完成文件的上传和下载。这里的文件不单单是指doc文件,也会指图片和视频文件。

具体想要有更多的理解,可以参考这篇文档:commons-fileupload上传下载

shiro包

我们在web开发时,经常会涉及到权限问题,比如哪些页面不需要登录就能看,而哪些页面只能登录才能看。当用户在打开该页面之前,就进入到相应的过滤器中,来做相关业务的判断。如果通过,就进入到controller层;不通过,则抛出相应的异常给前端。这里就需要相应的权限控制。

说到权限控制,我们不得不提到shiro框架。其有三大核心组件Subject, SecurityManager 和 Realms。这个百度百科上也说了,可以查看其解说内容:java安全框架

 <!-- shiro驱动包  开始 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
</dependency>
<!--shiro驱动包 结束 -->

公司也会做相应的配置,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 缓存管理器 -->
    <bean id="cacheManager" class="com.*.shared.framework.SpringCacheManagerWrapper">
        <property name="cacheManager" ref="springCacheManager"/>
    </bean>
    <bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcacheManager"/>
    </bean>
    <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
    </bean>

    <!-- 凭证匹配器 -->
    <bean id="credentialsMatcher"
          class="com.*.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager"/>
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="2"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>

    <!-- Realm实现 -->
    <bean id="userRealm" class="com.**.shared.web.listener.MemberSecurityRealm">
        <!--<property name="credentialsMatcher" ref="credentialsMatcher"/>-->
        <property name="cachingEnabled" value="false"/>
        <!--<property name="authenticationCachingEnabled" value="true"/>-->
        <!--<property name="authenticationCacheName" value="authenticationCache"/>-->
        <!--<property name="authorizationCachingEnabled" value="true"/>-->
        <!--<property name="authorizationCacheName" value="authorizationCache"/>-->
    </bean>

    <!-- 会话ID生成器 -->
    <!--<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>-->

    <!-- 会话Cookie模板 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="platform-portal-sid"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="7200"/>
    </bean>

    <!-- 会话管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="43200000"/>
        <property name="deleteInvalidSessions" value="true"/>     
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

    <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>

    <!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"
          depends-on="securityManager,memberShiroFilerChainManager">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- 基于url+角色的身份验证过滤器 -->
    <bean id="urlAuthFilter" class="com.zfounder.platform.core.shared.web.filter.UrlAuthFilter">
        <property name="ignoreCheckUriList">
            <list>
                <value>/**/common/enums/**</value>
                <value>/**/security/**</value>
                <value>/**/common/dd/**</value>
                <value>/**/pictures/**</value>
                <value>/**/common/sms/**</value>
                <value>/**/wx/**</value>
            </list>
        </property>
    </bean>

    <bean id="memberFilterChainManager"
          class="com.zfounder.platform.core.shared.web.listener.CustomDefaultFilterChainManager">
        <property name="customFilters">
            <util:map>
                <entry key="roles" value-ref="urlAuthFilter"/>
            </util:map>
        </property>
    </bean>

    <bean id="memberFilterChainResolver"
          class="com.**.shared.web.listener.CustomPathMatchingFilterChainResolver">
        <property name="customDefaultFilterChainManager" ref="memberFilterChainManager"/>
    </bean>

    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" depends-on="shiroFilter">
        <property name="targetObject" ref="shiroFilter"/>
        <property name="targetMethod" value="setFilterChainResolver"/>
        <property name="arguments" ref="memberFilterChainResolver"/>
    </bean>

    <!-- Shiro生命周期处理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

想要对其有更深的理解,请参考这篇博客:Shiro讲解

工具类

<!--汉字转拼音开源工具包-->
 <dependency>
    <groupId>com.github.stuxuhai</groupId>
    <artifactId>jpinyin</artifactId>
</dependency>

<!--网络爬虫的驱动包-->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
</dependency>

<!--验证码生成工具包-->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
</dependency>

<!--发送邮件-->
<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
</dependency>

因为篇幅的限制,这里就不再细说了,如果想要更深层次的了解,可以参考以下博客:

  1. 汉字转拼音开源工具包Jpinyin介绍
  2. 爬虫+jsoup轻松爬知乎
  3. 使用kaptcha生成验证码
  4. 使用javax.mail发送邮件

图片验证码的配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-lazy-init="true">

    <bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
        <property name="config">
            <bean class="com.google.code.kaptcha.util.Config">
                <constructor-arg>
                    <props>
                        <prop key="kaptcha.border">${kaptcha.border}</prop>
                        <prop key="kaptcha.border.color">${kaptcha.border.color}</prop>
                        <prop key="kaptcha.textproducer.font.color">${kaptcha.textproducer.font.color}</prop>
                        <prop key="kaptcha.textproducer.char.space">${kaptcha.textproducer.char.space}</prop>
                        <prop key="kaptcha.textproducer.font.size">${kaptcha.textproducer.font.size}</prop>
                        <prop key="kaptcha.image.width">${kaptcha.image.width}</prop>
                        <prop key="kaptcha.image.height">${kaptcha.image.height}</prop>
                        <prop key="kaptcha.textproducer.char.length">${kaptcha.textproducer.char.length}</prop>
                        <prop key="kaptcha.textproducer.char.string">1234567890</prop>
                        <prop key="kaptcha.textproducer.font.names">宋体,楷体,微软雅黑</prop>
                        <prop key="kaptcha.noise.color">${kaptcha.noise.color}</prop>
                        <prop key="kaptcha.noise.impl">com.google.code.kaptcha.impl.NoNoise</prop>
                        <prop key="kaptcha.background.clear.from">${kaptcha.background.clear.from}</prop>
                        <prop key="kaptcha.background.clear.to">${kaptcha.background.clear.to}</prop>
                        <prop key="kaptcha.word.impl">com.google.code.kaptcha.text.impl.DefaultWordRenderer</prop>
                        <prop key="kaptcha.obscurificator.impl">com.google.code.kaptcha.impl.ShadowGimpy</prop>
                    </props>
                </constructor-arg>
            </bean>
        </property>
    </bean>
</beans>

里面的占位符来源于plateform-dev.properties或者plateform-prd.properties,这就是我们maven激活的配置文件的作用。

测试依赖包

我们在开发完一个功能后,首先会想到测试它走不走得通。我们可能会在main方法中测试,一个项目类中可以写多个main方法。如果每个功能类中都写一个main方法,未免会造成代码的混乱,一点都不美观和儒雅。

java为什么一直推崇面向对象,任何在现实中真实的、虚拟的事物,都可以将其封装为为java中的对象类。对象与对象之间以方法作为消息传递机制,以属性作为数据库存储的机制。

如果我们在每个功能中都写一个main方法,势必会破坏这种对象的美观性。因而,我们把测试的数据以对象的方式操作,这样,将其封装为一个测试包,比如,在我写的spring框架中,就把测试类单独拿出来,如图所示:

《详解服务器端的项目框架》

<!-- 测试依赖包 开始-->

<!-- spring test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
</dependency>

<!-- 路径检索json或设置Json -->
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>${jsonpath.version}</version>
    <scope>test</scope>
</dependency>

<!-- testng -->
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>${testng.version}</version>
</dependency>

<!-- 单元测试的powermock -->
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-testng</artifactId>
    <version>${powermock.version}</version>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito</artifactId>
    <version>${powermock.version}</version>
</dependency>
<!--测试相关 结束-->

以上是几种测试包的依赖,一个是spring的测试包,这里由于篇幅的限制,就不做详细的介绍了,网上有很多这方面的教程,想要深入的了解,可参考这篇博客:Spring-Test(单元测试)

我们有时也会用到TestNG框架,它是Java中的一个测试框架,类似于JUnit 和NUnit,功能都差不多,只是功能更加强大,使用也更方便。测试人员一般用TestNG来写自动化测试,开发人员一般用JUnit写单元测试。如果你是测试人员,想对其有更全面的了解,可以参考这篇教程:TestNG教程,或者这篇博客::testNG常用用法总结

如果想要更深层次的了解powermock,可以参考这篇博客:PowerMock从入门到放弃再到使用

如果想要更深层次的了解JsonPath,可以参考这篇博客:JsonPath教程

图片处理

我们在开发的过程中,会把图片存放到服务器的某个文件夹下,即某个磁盘上。如果图片过大,会占用服务器的磁盘,因而,我们需要将图片缩略,来减少对内存的占用。这时,我们如果使用java原生的图片缩略图,是非常复杂的,因而,我们可以使用以下框架对图片进行操作。

<!--图片处理驱动包 开始-->
<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
</dependency>
<!--图片处理驱动包 结束-->

这里不再细说,想要有更多的了解,可以参考这篇博客:Thumbnailator框架的使用

Excel操作

我们在工作的过程中,经常会将数据导出到Excel表,或将Excel表的数据导入数据库。我们以前使用poi框架,但是,超过一定量的时候,会占用大量的内存,从而降低导入的效率。阿里巴巴现在开放出操作Excel表的easyexcel框架,对百万级的导入影响不是很大。

以下是maven配置两个驱动依赖:

<!--阿里巴巴的easyexcel驱动包 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>{latestVersion}</version>
</dependency>

<!--poi驱动包 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi-ooxml.version}</version>
</dependency>

这两个就不再细说,如果想要对easyexcel更深的了解,可以参考这篇博客:alibaba/easyexcel 框架使用。如果想要对poi有更深的了解,可以参考这篇博客:Apache POI使用详解

guava包

我们在开发的过程中,有时会用到guava驱动包。它是为了方便编码,并减少编码错误,用于提供集合,缓存,支持原语句,并发性,常见注解,字符串处理,I/O和验证的实用方法。

使用它有以下好处:

  • 标准化 – Guava库是由谷歌托管。
  • 高效 – 可靠,快速和有效的扩展JAVA标准库
  • 优化 -Guava库经过高度的优化。

同时,又有增加Java功能和处理能力的函数式编程,提供了需要在应用程序中开发的许多实用程序类的,提供了标准的故障安全验证机制,强调了最佳的做法等等。它的宗旨就是:提高代码质量、简化工作,促使代码更有弹性、更加简洁的工具。

我们在项目中的配置包为:

<!--guava驱动包 开始-->
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
</dependency>
<!--guava驱动包 结束-->

如果想要对其有更深的了解,可以参考这篇教程:guava入门教程

freemarker包

我们在开发的过程中,也许会用到这个框架。为什么要用到这个框架呢?我们有时需要动态地将xml文件转为doc文件,这个时候,就用到了freemarker包,如图所示:

《详解服务器端的项目框架》

截图不是很全面,你会看到画红框的部分,这是一种占位符的标记,就相当于java中的形参一样。 当用户点击前端的下载按钮时,有些数据是无法直接转换成doc的,因为我们先把数据写进xml中,再将xml转化为doc。具体如何转换的可以参考该博客:Java将xml模板动态填充数据转换为word文档

我们可以引用这个包:

 <!--freemarker驱动包 开始-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>${freemarker.version}</version>
</dependency>
<!--freemarker驱动包 结束-->

由于篇幅限制,想要详细了解,可以参考这篇手册: freemarker在线手册

servlet驱动包

我记得当时在学java-web开发时,最开始用的就是servlet。接收客户端的输入,并经过一系列DB操作,将数据返回给客户端。但使用纯servlet不利于可视化界面。后来,使用了JSP开发,其是可视化界面。但是,当我们启动Tomcat后,JSP通过JSP引擎还是会转为servlet。从本质上来说,JSP和servlet是服务端语言。

我最初用servlet和JSP开发的源码地址:图书馆项目

后来,工作了以后。后端就用了springMVC,hibernate框架等,前端使用的是HTML、jQuery等。慢慢地脱离了JSP和servlet。但是,并没与完全与servlet分隔开,我们还时不时会用到servlet的一些类,比如HttpServletRequest,HttpServletResponse等类。

既然使用了spring MVC框架,为什么还要用servlet的东西,比如,我们在导入和导出时,一个是接收前端导入的请求,一个是响应前端导出的请求。

  • 响应前端导出的代码,这里就用到了响应
private static void downloadExcel(HttpServletResponse response, File newFile, String fileName) throws IOException {
    InputStream fis = new BufferedInputStream(new FileInputStream(
            newFile));
    String substring = fileName.substring(fileName.indexOf("/") + 1);
    byte[] buffer = new byte[fis.available()];
    fis.read(buffer);
    fis.close();
    response.reset();
    response.setContentType("text/html;charset=UTF-8");
    OutputStream toClient = new BufferedOutputStream(
            response.getOutputStream());
    response.setContentType("application/x-msdownload");
    String newName = URLEncoder.encode(
            substring + System.currentTimeMillis() + ".xlsx",
            "UTF-8");
    response.addHeader("Content-Disposition",
            "attachment;filename=\"" + newName + "\"");
    response.addHeader("Content-Length", "" + newFile.length());
    toClient.write(buffer);
    toClient.flush();
}
  • 接收前端导入的请求
 public static LinkedHashMap<String, List<JSONObject>> importMultiSheetExcel(HttpServletRequest request, LinkedHashMap<Integer, Integer> sheetDataStartRowMap, LinkedHashMap<Integer, String> sheetDataEndColMap) {
    LinkedHashMap<String, List<JSONObject>> resMap = new LinkedHashMap<>();
    try {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        ifNullThrow(multipartRequest, ResultCodeEnum.ILLEGAL_PARAM);
        MultipartFile file = multipartRequest.getFile("file");
        Workbook work = getWorkbook(file.getInputStream(), file.getOriginalFilename());
        ifNullThrow(work, ResultCodeEnum.ILLEGAL_PARAM);
        。。。
}

虽然我们现在使用了spring MVC,还是用到了servlet,而且shiro里面要使用到,以下是代码的配置:

<!--servlet 开始-->
<!--shiro里面要使用到-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>${servlet.version}</version>
</dependency>
<!--servlet 结束-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>${jstl.version}</version>
</dependency>
<!--servlet 结束-->

如果想要了解servlet的话,可以参考该文档:Java Servlet API中文说明文档

Lucene全文检索

有时,我们在开发的过程中,需要做全文检索数据,就比如,我在Word文档中,全文检索某个词、某句话等,如图所示:

《详解服务器端的项目框架》

这就是web端的全文检索。但是我做Java,当然,也需要全文检索。因而,我们就想到了Lucene。

它是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里,它是一个成熟的免费开源工具。就其本身而言,它是当前以及最近几年最受欢迎的免费Java信息检索程序库。

我们在java的maven库中的配置为:

 <!-- lucene 开始 -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>${lucene.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-highlighter</artifactId>
    <version>${lucene.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-common</artifactId>
    <version>${lucene.version}</version>
</dependency>
<!-- lucene 结束 -->

想要对其有更深的了解,可以参考这篇笔记:Lucene学习笔记

Quartz任务调度

我们在开发的过程中,总想着要在某个时间,执行什么样的事情,于是呢,我们就相当了任务调度,比如:

  1. 每天八点按时起床
  2. 每年农历什么的生日
  3. 每个星期都要爬一次山

我们就可以用到Quartz这个框架,我们需要做一些配置,如图所示:

《详解服务器端的项目框架》

我们可以在maven中的配置为:

<!-- quartz驱动包 开始-->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>${quartz.version}</version>
</dependency>
<!-- quartz驱动包 结束-->

想要对其有根深多的了解,可参考这篇博客:Quartz使用总结

zxing二维码

我们经常使用到二维码,比如,添加微信好友的二维码,支付二维码、扫一扫二维码等等,那么,这是怎么实现的呢,其实,这有一个工具包,就是zxing工具包。它是谷歌旗下的工具类,我们可以用它来生成我们想要的二维码,但是,我们先要在maven项目中配置它。如代码所示:

<!-- 二维码驱动包 开始-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>${zxing.version}</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>${zxing.se.version}</version>
</dependency>
<!-- 二维码驱动包 开始-->

想要对其有根深的了解,可以参考这篇博客:zxing实现二维码生成和解析

WSDL包

这个我也不大懂,也没有操作过,如果想要了解的话,可以参考这篇文档:WebService中的WSDL详细解析

我们在maven中的怕配置为:

<!-- WSDL驱动包 开始-->
 <dependency>
    <groupId>wsdl4j</groupId>
    <artifactId>wsdl4j</artifactId>
    <version>${wsdl4j.version}</version>
</dependency>
<!-- WSDL驱动包 结束-->

配置文件

配置文件来源于框架中的以下文件,如图所示:

《详解服务器端的项目框架》

所有的配置文件都来源于资源包。这里就不再细说。

总结

要想成为架构师,首先学习别人的东西,他山之石,可以攻玉。

    原文作者:念兮
    原文地址: https://segmentfault.com/a/1190000018708671
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞