BeanUtils.copyProperties的一个大坑:为null的包装类(Integer等)复制后自动变为0

最近业务里经常要在两个对象间复制属性,于是想当然的就用了BeanUtils.copyProperties来复制。然而有一天突然发现,用复制后的对象调用一个方法一直失败,用被复制的那个去调用却没有任何问题。检查了很久才发现,原来BeanUtils.copyProperties方法在遇到包装类时,竟然会把原来为null的属性,复制后赋值为0…(对于Boolean,复制后会变为false),难怪用复制后的对象会出错……

解决方法就很简单了,换用PropertyUtils.copyProperties即可
不过这两者之间还是有一定区别的,PropertyUtils.copyProperties比起BeanUtils.copyProperties来说,少了类型转换的支持,也就是说必须要类型和name一样才可以复制。比如源对象有一个类型为Long的属性,而目标对象同名属性的类型为Date,BeanUtils.copyProperties可以复制,而PropertyUtils.copyProperties会报错。

解决Spring Boot自定义错误页导致文件无法上传的问题

网上的Spring Boot自定义错误页教程,基本上都是自己自定义一个ServletRegistrationBean,把DispatcherServlet里的setThrowExceptionIfNoHandlerFound设置为true,代码样例如下:

@Configuration
public class ErrorConfiguration {
    @Bean
    public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean registration = new ServletRegistrationBean(
                dispatcherServlet);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
        return registration;
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return (container -> {
            ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html");
            ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
            ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");
            container.addErrorPages(error401Page, error404Page, error500Page);
        });
    }
}

但这种配置方式存在一个问题,我们自定义了ServletRegistrationBean之后,无法使用系统自带的DispatcherServletAutoConfiguration自动配置,于是缺少了很多配置,其中就包含上传文件所需要的配置。

解决方法很简单。只为了自定义错误页的话,根本无需像上面代码段中粗体部分那样自己自定义ServletRegistrationBean。直接在application.yml(或application.properties)里配置一句spring.mvc.throw-exception-if-no-handler-found: true,就可以把DispatcherServlet里的setThrowExceptionIfNoHandlerFound设置为true了。

Spring整合Hive

最近项目中的一个页面要从Hive中取数据,研究了一下如何把Hive整合进Spring中。

其实还是很简单的,Spring提供了spring-data-hadoop这个包,引进来之后新增一个配置文件就可以了。配置文件样例如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/hadoop"
             xmlns:beans="http://www.springframework.org/schema/beans"
             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/hadoop http://www.springframework.org/schema/hadoop/spring-hadoop.xsd">

    <beans:bean id="hiveDriver" class="org.apache.hive.jdbc.HiveDriver"/>

    <beans:bean id="hiveDataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <beans:constructor-arg name="driver" ref="hiveDriver"/>
        <beans:constructor-arg name="url" value="jdbc:hive2://填数据库地址"/>
    </beans:bean>

    <hive-client-factory id="hiveClientFactory" hive-data-source-ref="hiveDataSource"/>

    <hive-template id="hiveTemplate"/>
</beans:beans>

用的时候直接autowire hiveTemplate就可以了。

不过在实际使用的时候遇到了另一个问题,HiveTemplate的功能非常弱鸡,连QueryForObject都没有。于是我用Hive的DataSource配置了一个JdbcTemplate,需要查Object的地方就直接用JdbcTemplate了。

<beans:bean id="hiveJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <beans:property name="dataSource" ref="hiveDataSource"/>
</beans:bean>

最后说句题外话,用多了MyBatis之后,再用JdbcTemplate,还真是不习惯。最麻烦的就属QueryForList不能直接返回实体的List了,要么自己写一个映射的方法,要么手动一个个set到实体对象里,无论如何都很麻烦……哎,还是MyBatis好。

解决Java Web项目中Word、Excel等二进制文件编译后无法打开的问题

今天写新项目的时候遇到一个问题,在resources目录下存储的.xlsx文件,编译过后会增大几kb,无法打开。

Google了一番之后,发现问题源自于maven-resources-plugin这个插件。这个插件会把resources目录下的文本文件进行转码,但它无法正确的识别哪些是文本文件,因而会错误的将不需要进行转码的二进制文件也进行转码,导致这些二进制文件无法打开。

解决的方法是在pom.xml中的maven-resources-plugin下,将不需要转码的文件扩展名填入nonFilteredFileExtensions,格式如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.0.2</version>
    <configuration>
        <encoding>UTF-8</encoding>
        <nonFilteredFileExtensions>
            <nonFilteredFileExtension>xls</nonFilteredFileExtension>
            <nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
        </nonFilteredFileExtensions>
    </configuration>
</plugin>

SpringMVC中使用RequestMapping自定义多个404页

一般来说,Java Web项目在自定义404页面时,直接在web.xml中增加一段<error-page>就可以实现,例如:

<error-page>
    <error-code>404</error-code>
    <location>/404.jsp</location>
</error-page>

然而,使用这种方法,项目中所有的404页都只能指向同一个HTML页。若想让项目中PC端的404页和手机端的404页指向不同的页面,则使用该方法无法实现。

若想自定义多个404页,我们可以使用SpringMVC中@RequestMapping最精确匹配原则来实现。该原则的意思是,如果我们在Controller中有两个方法,一个拦截了admin/**,一个拦截了admin/login,则在访问admin/login时,SpringMVC会进入匹配最为精确的那个方法,即拦截了admin/login的那个方法。

利用这个原则,我们可以这样来自定义多个404页:假设项目中所有PC端页面的地址均为admin/开头,所有移动端页面的地址均为mobile/开头,那么我们可以写两个Controller方法,PC端404页拦截admin/**,移动端404页拦截mobile/**,即分别使用@RequestMapping(value = "admin/**")@RequestMapping(value = "mobile/**"),这样在admin/及mobile/下的所有未被其他Controller拦截的地址,都会分别被这两个方法拦截,我们只需要分别在这两个方法中return到对应的错误页面就可以了。

需要注意的是,我们需要写/**,而不是/*,若使用*,则只能拦截到第一级的页面(如admin/a),而对于admin/a/b、admin/a/b/c这种多级的页面,则无法拦截到。

《信息检索》大作业1、2:建立索引、检索、查询扩展、系统评估

源代码下载:https://github.com/houseyoung/IR-Homework1-2

作业一:索引的建立
1、切词及词频统计:利用已提供的海量智能分词软件(代码中使用的是庖丁解牛Paoding Analyzer)对文档进行切词处理,并进行词频统计,形成DocIndex文件,结构为:文档号、频率、词。
2、分配词权重:采用词频标准化tfi=tfi/Max(tf)tf*idf两种方式分配词的权重。由DocIndex文件生成DocIndex(tf)DocIndex(tf*idf)文件。注意阈值的确定,词的取舍。
3、形成倒置文档:将DocIndex(tf)DocIndex(tf*idf)文件转换为DocInvert(tf)DocInvert (tf*idf)文件。

作业二:检索及评估系统
1、Query处理(与文档处理采用相同方法:切词,选择关键词等)。
2、用VSM(向量空间模型)方法,计算一个查询与一个文档集合中各文档的相似度,并排序输出结果(对问题1020输出详细结果,其他问题只记录文档排序即可)
3、应用查询扩展方法对Query处理后,对同一问题再次进行检索,并排序输出结果(对问题1020输出详细结果,其他问题只记录文档排序即可)
4、应用MRR(Mean Reciprocal Rank)方法,计算系统的MRR值。

使用Nginx等反向代理时如何从Request中获取客户端真实IP地址

在Java Web项目中,若想获取客户端的IP地址,最简单的方式便是使用HttpServletRequest中的getRemoteAddr()方法。但若使用该方法进行获取,一旦项目经过了反向代理(如Nginx),则获取到的IP地址是反向代理服务器的IP地址,而非客户端的真实IP地址。

在使用Nginx做反向代理的情况下,若想获取到客户端的真实IP地址,首先要保证Nginx的配置文件中有proxy_set_header X-Real-IP $remote_addr; 这一句。然后使用HttpServletRequest中的getHeader("X-Real-IP")方法,获取HTTP请求头中"X-Real-IP"中的内容,该内容即为客户端的真实IP地址。

代码样例如下:

// 访问者的IP地址(若未经过反向代理,则该地址为真实的访问者IP地址,否则为反向代理服务器的IP地址)
String customerIp = httpServletRequest.getRemoteAddr();
// 访问者的真实IP地址(若经过反向代理,则该地址为真实的访问者IP地址,否则为空)
String xRealIp = httpServletRequest.getHeader("X-Real-IP");
// 若存在X-Real-IP,则用X-Real-IP作为访问者的IP地址
if (xRealIp != null && !"".equals(xRealIp)) {
 customerIp = xRealIp;
}

蚁群算法解决TSP问题的Java实现(带图形用户界面)

截图:
TSP

源代码下载:https://github.com/houseyoung/TSP

算法主要基于杨剑峰《蚁群算法及其应用研究》论文第3.2~3.4节中的“基本蚁群算法”。代码基于http://blog.csdn.net/wangqiuyun/article/details/8878298进行改进。图形用户界面使用Swing编写。

TSP数据提供了ATT48(美国本土48州首府)、CHN31(中国大陆31省省会)、CHN144(中国144个主要城市)三种。读者可自行根据格式更换不同的测试数据。

注:GUI部分使用IntelliJ IDEA自带的GUI Form创建,有网友测试发现在Eclipse中无法正确执行。

在Mac下安装Common Lisp运行环境(SBCL + Emacs + SLIME)

安装SBCL

SBCL(Steel Bank Common Lisp)是一个开源的Common Lisp编译器,安装方法如下:

1.进入http://www.sbcl.org/platform-table.html,下载相应的版本。
2.将下载下来的.tar.bz2文件解压缩。
3.在终端中进入上一步解压缩得到的文件夹。
4.执行如下指令:

sh install.sh

安装完毕后,我们在终端中执行sbcl,就可以执行Common Lisp指令了。不过,sbcl自带的编辑器功能较弱,因而我们一般使用Emacs+Slime来作为IDE。

安装Homebrew

OS X自带Emacs,然而这个Emacs的版本过低,因而我们需要更新它,我们可以借助Homebrew来更新Emacs。

Homebrew是一个Mac下的包管理器,类似于CentOS下的yum、Ubuntu下的apt-get。

Homebrew的安装方法非常简单,进入官网:http://brew.sh/index_zh-cn.html,将指令复制到终端中,执行即可。

更新Emacs

使用Homebrew更新Emacs非常简单,在终端中执行如下指令即可:

brew install emacs –HEAD –use-git-head –cocoa –with-gnutls

安装SLIME

SLIME(The Superior Lisp Interaction Mode for Emacs)是Emacs下的Lisp开发插件,安装方法如下:

1.进入https://github.com/slime/slime,将Slime下载下来。
2.将下载的zip包解压缩,将解压缩得到的目录复制到~/.emacs.d目录下(若该目录不存在,需在终端中执行mkdir ~/.emacs.d指令创建该目录)
3.配置Emacs的配置文件。编辑~/.emacs(若该文件不存在,需在终端中执行touch ~/.emacs指令创建该文件),添加如下内容:

(setq inferior-lisp-program "/usr/local/bin/sbcl")
(add-to-list 'load-path "~/.emacs.d/slime/")
(require 'slime)
(slime-setup)

保存后,在终端中运行emacs,按下M-x(在Mac下即为Option-x),输入slime,编辑器底部显示“Swank started at port: XXXXX.”后,就可以在Emacs中进行Common Lisp开发了。