深入了解MyBatis参数

来源:互联网 发布:游族网络市值 编辑:程序博客网 时间:2024/05/29 04:22
偶尔记一下
没事看看 - MyBatis工具
[原]MyBatis 最常见错误,启动时控制台无限输出日志
2016年4月28日 21:46

你是否遇到过下面的情况,控制台无限的输出下面的日志:

    Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.
    Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.
    Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.
    Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.
    Logging initialized using ‘class org.apache.ibatis.logging.log4j.Log4jImpl’ adapter.

这个错误只有在和Spring集成的情况下才会出现。

每次只要出现这个错误,我都知道是XML出错了,但是具体是那个XML还没法直接确认,因为这里的日志看不出来任何有用的信息。

想定位这个错误,我有一个常见的方法,就是从程序启动的某一个入口断点,然后逐步定位这个错误。

不过这种方式仍然很麻烦,这里要说的是一种迅速定位解决的办法,操作起来很简单。

找到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 类,在下面方法:

protected void autowireByType(
            String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

这个方法大概在1200行左右。找到这个方法中catch异常的地方:

catch (BeansException ex) {
    throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
}

在throw这一行断点即可,这个地方是最早捕获异常的地方,当Mapper.xml文件出错的时候,这里的异常信息如下:
这里写图片描述

异常信息是很详细的,具体异常文字如下:

org.springframework.core.NestedIOException:
Failed to parse mapping resource:
'file [F:\Liu\Git\bhgl\target\Franchisee-1.0\WEB-INF\classes\com\abel533\property\dao\EmployeeMapper.xml]';
nested exception is org.apache.ibatis.builder.BuilderException:
Error creating document instance.  
Cause: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; 前言中不允许有内容。

打开这个出错的XML后,发现一个很无语的错误:
这里写图片描述

不知道什么情况,开头多了emplo,基本上只要是 XML 中出什么错,都是类似的异常信息,一般都是 XML 解析出的错。
还有一个问题,为什么出错后只能看到无限输出的一行日志,而看不到这里具体的异常信息呢?

通过追踪代码,发现在org.springframework.beans.factory.support.AbstractBeanFactory类中的方法:

protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
    if (!mbd.isSingleton()) {
        return null;
    }
    try {
        FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
        return getTypeForFactoryBean(factoryBean);
    }
    catch (BeanCreationException ex) {
        // Can only happen when getting a FactoryBean.
        if (logger.isDebugEnabled()) {
            logger.debug("Ignoring bean creation exception on FactoryBean type check: " + ex);
        }
        onSuppressedException(ex);
        return null;
    }
}

这里捕获异常后,直接return null导致异常被吞。

由于这里是最后一层捕获异常的地方,而且这个地方捕获到的异常范围会更广,因此在这里断点查看问题也是很不错的选择,由于这里经过多层异常处理,真正的错误信息隐藏的比较深,如下图:
这里写图片描述

看到这儿,相信再遇到这个问题的时候应该会很容易解决了。
作者:isea533 发表于2016/4/28 21:46:49 原文链接
阅读:146 评论:1 查看评论
[原]EasyUI - datagrid属性idField详解
2016年3月19日 11:50
EasyUI - datagrid属性idField详解

idField在treegrid中的是一个必选的属性,在datagrid中是一个可选的属性。

也许有人为了方便使用getRowIndex会在datagrid中设置idField属性,如果不注意这个属性,那么在调用getSelected或者getChecked方法时会引起更多莫名其妙的问题。

这篇博客就是讲datagrid中的idField对getSelected和getChecked方法相关方法的影响。

因为selected和checked情况类似,下面就以selected的情况来说明。
在选中一行的时候

执行的部分关键代码如下:

if (opts.idField) {
    _5cf(_6b9.selectedRows, opts.idField, row);
}
opts.finder.getTr(_6b6, _6b7).addClass("datagrid-row-selected");
opts.onSelect.apply(_6b6, _5d0(_6b6, [_6b7, row]));

这里的_6b9是datagrid的data-datagrid属性,获取方法为:

//使用jquery获取
$('#datagridId').data('datagrid');
//在easyui源码中使用下面方法获取
$.data(_6a7, "datagrid");

这个对象的一级属性如下图:

这里写图片描述

注意看这里的checkedRows和selectedRows属性。

继续看第一段代码,当if (opts.idField)存在idField属性的时候,会调用_5cf方法:

function _5cf(a, o, r) {
    for (var i = 0, len = a.length; i < len; i++) {
        if (a[i][o] == r[o]) {
            return;
        }
    }
    a.push(r);
};

这个方法就是当新选择的值和selectedRows中已有的idField值都不相同的时候,放到selectedRows中。通过idField避免重复!

当没有设置idField属性的时候,就没有selectedRows的事,只是调用下面的代码:

opts.finder.getTr(_6b6, _6b7).addClass("datagrid-row-selected");

这个代码就是给当前点击的行添加一个选中的样式,如下图:

这里写图片描述

通过上面代码我们应该已经了解选中一行的过程。
下面我们看当取消选中行时是如何处理的

关键代码如下:

opts.finder.getTr(_6bb, _6bc).removeClass("datagrid-row-selected");
if (opts.idField) {
    _5cd(_6be.selectedRows, opts.idField, row[opts.idField]);
}

看过前面的再看这里就容易很多了,首先removeClass去掉选中的样式。

然后如果有idField属性,执行_5cd方法:

function _5cd(a, o, id) {
    if (typeof o == "string") {
        for (var i = 0, len = a.length; i < len; i++) {
            if (a[i][o] == id) {
                a.splice(i, 1);
                return;
            }
        }
    } else {
        var _5ce = _5cc(a, o);
        if (_5ce != -1) {
            a.splice(_5ce, 1);
        }
    }
};

如果取消的列在selectedRows中,就会从中移除(这里的else一般不会出现,不需要重视这里)。

到这儿我们就了解了selectedRows增加和删除的情况。
注意1

这里要强调的是selectedRows中的值只能通过取消选择,或者unselectAll取消 【当前页】 的选择,即使重新加载数据,或者清空数据,都不会影响selectedRows中的值。想要取消全部选择怎么办?你可以使用clearSelections方法:

$('#id').datagrid('clearSelections');

取消全部选择,这个方法和当前显示的数据或者加载过的数据无关。这个方法如下:

clearSelections: function (jq) {
    return jq.each(function () {
        var _76d = $.data(this, "datagrid");
        var _76e = _76d.selectedRows;
        var _76f = _76d.checkedRows;
        _76e.splice(0, _76e.length);
        _6ba(this);
        if (_76d.options.checkOnSelect) {
            _76f.splice(0, _76f.length);
        }
    });
}

获取selectedRows后,通过_76e.splice(0, _76e.length);清空selectedRows,这一步已经达到我们的基本目标了,然后是调用_6ba(this);,这一步是取消当前页的选中状态(就是unselectAll方法)。如果checkOnSelect=true,在_6ba方法中也有这个判断,会取消复选框选中状态,在这里会清空checkedRows的值。

当你只想获取当前页的选择项时,最好的解决方法就是不设置idField属性。否则就要自己处理好调用clearSelections的时机。
注意2

同时你也应该了解,合理的使用idField还可以实现翻页选择(checkbox一样),这种情况下,你的datagrid能记住每一页的选中情况,而且通过getSelections(或getChecked)来获取所有页中选中的行。
下面我们看当调用getSelected方法时,是如何处理的

这个方法的定义如下:

getSelected: function (jq) {
    var rows = _6a6(jq[0]);
    return rows.length > 0 ? rows[0] : null;
}

获取rows后返回第一个或者null。在看_6a6方法:

function _6a6(_6a7) {
    var _6a8 = $.data(_6a7, "datagrid");
    var opts = _6a8.options;
    var data = _6a8.data;
    if (opts.idField) {
        return _6a8.selectedRows;
    } else {
        var rows = [];
        opts.finder.getTr(_6a7, "", "selected", 2).each(function () {
            rows.push(opts.finder.getRow(_6a7, $(this)));
        });
        return rows;
    }
};

当我们设置idField时,直接将selectedRows返回了。
如果没有设置idField,就会使用else中的方法,获取所有selected的元素,然后一个个循环,放到rows数组中返回。
从上面这两种处理方式来看,显然是有idField的时候效率更高(else效率也不低)。

其他和selected有关的方法,最终调用执行的都是上面提到的这些方法。这里不一一介绍了。
idField最有用的地方getRowIndex

getRowIndex需要一个参数,row或者id的值,使用id的前提就是设置idField。

下面是getRowIndex方法:

function _6a3(_6a4, row) {
    var _6a5 = $.data(_6a4, "datagrid");
    var opts = _6a5.options;
    var rows = _6a5.data.rows;
    if (typeof row == "object") {
        return _5cc(rows, row);
    } else {
        for (var i = 0; i < rows.length; i++) {
            if (rows[i][opts.idField] == row) {
                return i;
            }
        }
        return -1;
    }
};

当使用row参数时,满足typeof row == "object",之后调用_5cc方法,和所有的rows一一比较返回。

另外就是使用idField方式,_6a5.data.rows包含了当前页的所有行。对所有行循环,比较idField相同的值,直接返回行号,这种方式使用也方便,因为我们业务中很容易能获取id的值,而不容易得到一行row。
最后

官方文档中对idField的介绍只有下面的这些:

    idField - string - Indicate which field is an identity field.

只有通过查看源码你才能发现原来idField竟然起了这么多的作用,这个属性隐藏的含义太多,不注意就会遇到“莫名其妙”的问题。

希望通过本文能让你对datagrid中的idField有所了解。
作者:isea533 发表于2016/3/19 11:50:51 原文链接
阅读:589 评论:0 查看评论
[原]MyBatis 3.3.1 版本新功能示例
2016年3月6日 18:51
MyBatis 3.3.1版本新功能示例

MyBatis3.3.1更新日志:
https://github.com/mybatis/mybatis-3/issues?q=milestone%3A3.3.1

这里不对更新做翻译或者其他详细介绍。
这个更新除了一些bug修复,还有两个新增的功能:

    增加了对批量插入回写自增主键的功能
    增加了注解引用@Results的功能

下面通过简单例子来介绍这两个功能,为了例子的简洁,这里都使用注解实现的,没有用XML,批量插入的例子很容易就能变成XML形式的,大家自己尝试。
先看基础的表和对应的POJO。

city表:

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for city
-- ----------------------------
DROP TABLE IF EXISTS `city`;
CREATE TABLE `city` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
  `state` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of city
-- ----------------------------
INSERT INTO `city` VALUES ('1', '石家庄', '河北');
INSERT INTO `city` VALUES ('2', '邯郸', '河北');

city对象:

public class City2 {
    private Integer id;

    private String cityName;

    private String cityState;

    public City2() {
    }

    public City2(String cityName, String cityState) {
        this.cityName = cityName;
        this.cityState = cityState;
    }

    //省略setter,getter

    @Override
    public String toString() {
        return "City2{" +
                "id=" + id +
                ", cityName='" + cityName + '\'' +
                ", cityState='" + cityState + '\'' +
                '}';
    }
}

定义如下MyBatis331Mapper接口

/**
 * mybatis3.3.1版本新增功能测试
 *
 * @author liuzh
 * @since 2016-03-06 17:22
 */
public interface MyBatis331Mapper {

    /**
     * 批量插入
     *
     * @param cities
     * @return
     */
    @Insert("<script>" +
            "insert into city (id, name, state) values " +
            "<foreach collection=\"list\" item=\"city\" separator=\",\" >" +
                "(#{city.id}, #{city.cityName}, #{city.cityState})" +
            "</foreach>" +
            "</script>")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertCities(List<City2> cities);

    /**
     * 根据主键查询一个
     *
     * @param id
     * @return
     */
    @Results(id = "cityResult", value = {
        @Result(property = "id", column = "id", id = true),
        @Result(property = "cityName", column = "name", id = true),
        @Result(property = "cityState", column = "state", id = true)
    })
    @Select("select id, name, state from city where id = #{id}")
    City2 selectByCityId(Integer id);

    /**
     * 查询全部,引用上面的Results
     *
     * @return
     */
    @ResultMap("cityResult")
    @Select("select id, name, state from city")
    List<City2> selectAll();
}

这里详细说一下这两个新功能的用法。
先看批量插入的例子

@Insert("<script>" +
        "insert into city (id, name, state) values " +
        "<foreach collection=\"list\" item=\"city\" separator=\",\" >" +
            "(#{city.id}, #{city.cityName}, #{city.cityState})" +
        "</foreach>" +
        "</script>")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertCities(List<City2> cities);

首先接口参数只能有一个(默认情况下),如果你参数有多个,那么要返回主键的那个List必须加注解@Param("list")或者在参数Map中对应的key为"list"。这一点很重要,只有看源码才能了解(当然除了"list"还有另外的名字,例如支持数组的"array"),参考Jdbc3KeyGenerator类中的这段代码:

if (parameterMap.containsKey("collection")) {
    parameters = (Collection) parameterMap.get("collection");
} else if (parameterMap.containsKey("list")) {
    parameters = (List) parameterMap.get("list");
} else if (parameterMap.containsKey("array")) {
    parameters = Arrays.asList((Object[]) parameterMap.get("array"));
}

然后就是必须使用useGeneratedKeys的方式,注解使用下面的方式:

@Options(useGeneratedKeys = true, keyProperty = "id")

XML使用类似下面的方式:

<insert id="insertList" useGeneratedKeys="true" keyProperty="id">

只要注意上面这几点,批量插入应该就能返回自增的值了。

注意:大家应该能理解,自增的不一定是主键,而且一个表中可能有多个自增的值。这些情况下都能获取到,keyProperty需要设置多个属性值,逗号隔开即可。
再看引用@Results

(此功能是否为新增功能,我并不确定,因为我平时不用注解)

用MyBatis的人中,使用注解的是少数,但是有些企业由于领导或者别的原因,会限制必须用注解。

这对一些复杂的情况来说,使用起来不如XML的方便,但是不得不用。

以前如果返回一个对象的属性需要配置映射,那么每个对象上都需要这段重复的代码,看起来很乱很麻烦。

在上面的例子中,在selectByCityId上定义了Results,在下面的方法selectAll上通过@ResultMap("cityResult")直接引用的上面的Results。这个功能在使用的时候没有特别注意的地方。
测试

写个简单的测试,代码如下:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@Transactional
@SpringApplicationConfiguration(Application.class)
public class MyBatis331Test {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private MyBatis331Mapper mapper;

    @Test
    @Rollback
    public void testInsertList() {
        List<City2> city2List = new ArrayList<City2>();
        city2List.add(new City2("石家庄", "河北"));
        city2List.add(new City2("邯郸", "河北"));
        city2List.add(new City2("秦皇岛", "河北"));
        Assert.assertEquals(3, mapper.insertCities(city2List));
        for (City2 c2 : city2List) {
            logger.info(c2.toString());
            Assert.assertNotNull(c2.getId());
        }
    }

    @Test
    public void testSelectById(){
        City2 city2 = mapper.selectByCityId(1);
        logger.info(city2.toString());
        Assert.assertNotNull(city2);
        Assert.assertNotNull(city2.getCityName());
        Assert.assertNotNull(city2.getCityState());
    }

    @Test
    public void testSelectAll(){
        List<City2> city2List = mapper.selectAll();
        for(City2 c2 : city2List){
            logger.info(c2.toString());
            Assert.assertNotNull(c2);
            Assert.assertNotNull(c2.getCityName());
            Assert.assertNotNull(c2.getCityState());
        }
    }

}

第一个测试方法输出的部分日志如下:

==>  Preparing: insert into city (id, name, state) values (?, ?, ?) , (?, ?, ?) , (?, ?, ?)
==> Parameters: null, 石家庄(String), 河北(String), null, 邯郸(String), 河北(String), null, 秦皇岛(String), 河北(String)
<==    Updates: 3
City2{id=6, cityName='石家庄', cityState='河北'}
City2{id=7, cityName='邯郸', cityState='河北'}
City2{id=8, cityName='秦皇岛', cityState='河北'}

后两个方法输出的部分日志如下:

==>  Preparing: select id, name, state from city where id = ?
==> Parameters: 1(Integer)
<==      Total: 1
City2{id=1, cityName='石家庄', cityState='河北'}

==>  Preparing: select id, name, state from city
==> Parameters:
<==      Total: 2
City2{id=1, cityName='石家庄', cityState='河北'}
City2{id=2, cityName='邯郸', cityState='河北'}

注:由于批量插入事务并没有提交,因此这里查询出来的结果就是表中原有的两条数据。
最后

为了方便尝试上面的代码,可以直接查看下面项目的src/test:

MyBatis-Spring-Boot: https://github.com/abel533/MyBatis-Spring-Boot

另外mybatis-spring项目也同时更新到了1.2.4,这个版本对于使用SpringBoot的开发人员非常有用,这个版本解决了mybatis的循环依赖异常,如果你在使用SpringBoot,赶紧升级到最新的版本试试吧。
作者:isea533 发表于2016/3/6 18:51:03 原文链接
阅读:1177 评论:2 查看评论
[原]Spring MVC 4.2 增加 CORS 支持
2016年1月3日 23:06
Spring MVC 4.2 增加 CORS 支持

跨站 HTTP 请求(Cross-site HTTP request)是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。比如说,域名A(http://domaina.example)的某 Web 应用程序中通过标签引入了域名B(http://domainb.foo)站点的某图片资源(http://domainb.foo/image.jpg),域名A的那 Web 应用就会导致浏览器发起一个跨站 HTTP 请求。在当今的 Web 开发中,使用跨站 HTTP 请求加载各类资源(包括CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。

正如大家所知,出于安全考虑,浏览器会限制脚本中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy)。 具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。为了能开发出更强大、更丰富、更安全的Web应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。比如,可以使用 XMLHttpRequest 发起跨站 HTTP 请求。(这段描述跨域不准确,跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是crsf跨站攻击原理,请求是发送到了后端服务器无论是否跨域!注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)

更多CORS介绍请看这里:

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

在WEB项目中,如果我们想支持CORS,一般都要通过过滤器进行实现,可以定义一些基本的规则,但是不方便提供更细粒度的配置,如果你想参考过滤器实现,你可以阅读下面这篇文章:

    http://my.oschina.net/huangyong/blog/521891

Spring MVC 从4.2版本开始增加了对CORS的支持

在Spring MVC 中增加CORS支持非常简单,可以配置全局的规则,也可以使用@CrossOrigin注解进行细粒度的配置。
使用@CrossOrigin注解

先通过源码看看该注解支持的属性:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {

    String[] DEFAULT_ORIGINS = { "*" };

    String[] DEFAULT_ALLOWED_HEADERS = { "*" };

    boolean DEFAULT_ALLOW_CREDENTIALS = true;

    long DEFAULT_MAX_AGE = 1800;


    /**
     * 同origins属性一样
     */
    @AliasFor("origins")
    String[] value() default {};

    /**
     * 所有支持域的集合,例如"http://domain1.com"。
     * <p>这些值都显示在请求头中的Access-Control-Allow-Origin
     * "*"代表所有域的请求都支持
     * <p>如果没有定义,所有请求的域都支持
     * @see #value
     */
    @AliasFor("value")
    String[] origins() default {};

    /**
     * 允许请求头重的header,默认都支持
     */
    String[] allowedHeaders() default {};

    /**
     * 响应头中允许访问的header,默认为空
     */
    String[] exposedHeaders() default {};

    /**
     * 请求支持的方法,例如"{RequestMethod.GET, RequestMethod.POST}"}。
     * 默认支持RequestMapping中设置的方法
     */
    RequestMethod[] methods() default {};

    /**
     * 是否允许cookie随请求发送,使用时必须指定具体的域
     */
    String allowCredentials() default "";

    /**
     * 预请求的结果的有效期,默认30分钟
     */
    long maxAge() default -1;

}

如果你对这些属性的含义不是很明白,建议阅读下面的文章了解更多:

    http://fengchj.com/?p=1888

下面举例在方法和Controller上使用该注解。
在Controller上使用@CrossOrigin注解

@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @RequestMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

这里指定当前的AccountController中所有的方法可以处理http://domain2.com域上的请求,
在方法上使用@CrossOrigin注解

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("http://domain2.com")
    @RequestMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

在这个例子中,AccountController类上也有@CrossOrigin注解,retrieve方法上也有注解,Spring会合并两个注解的属性一起使用。
CORS全局配置

除了细粒度基于注解的配置,你可能会想定义一些全局CORS的配置。这类似于使用过滤器,但可以在Spring MVC中声明,并结合细粒度@CrossOrigin配置。默认情况下所有的域名和GET、HEAD和POST方法都是允许的。
基于JAVA的配置

看下面例子:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}

您可以轻松地更改任何属性,以及配置适用于特定的路径模式的CORS:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("http://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(false).maxAge(3600);
    }
}

如果你使用Spring Boot,你可以通过这种方式方便的进行配置。
基于XML的配置

<mvc:cors>
    <mvc:mapping path="/**" />
</mvc:cors>

这个配置和上面JAVA方式的第一种作用一样。

同样,你可以做更复杂的配置:

<mvc:cors>

    <mvc:mapping path="/api/**"
        allowed-origins="http://domain1.com, http://domain2.com"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="false"
        max-age="123" />

    <mvc:mapping path="/resources/**"
        allowed-origins="http://domain1.com" />

</mvc:cors>

作者:isea533 发表于2016/1/3 23:06:22 原文链接
阅读:1787 评论:0 查看评论
[原]参与 Spring 4 中文文档翻译
2016年1月3日 11:43
参与 Spring 4 中文文档翻译
我们从2014年12月开始翻译Spring 4的框架文档,虽然至今已有一年,但是进度很慢。其中一部分原因是因为Spring 文档有1000多页,而且翻译的时候根据章节分配,导致许多人一次要翻译2000~3000行的文档,致使效率过低。
近日已经将文档按照一定的章节重新拆分,每一个需要翻译的文件在20行~200行之间,行数少的纯翻译内容较多,行数多的包含的代码(代码只需要翻译注释)多,现在每一个文件翻译耗时在10分钟到1小时左右。
为了能更快的翻译完Spring 4的文档,我们希望有更多的人能够加入,即使您只翻译一段20行的内容,也会对我们整体进度起到很大的作用!
Spring文档非常的全面,翻译的同时也是一个深入学习的过程!
Spring4中文文档地址
http://spring.cndocs.tk
参与翻译
请查看本项目git地址
http://git.oschina.net/free/spring-framework-reference
短的内容

这里写图片描述
长的内容

这里写图片描述
作者:isea533 发表于2016/1/3 11:43:31 原文链接
阅读:1161 评论:4 查看评论
[原]Spring Boot 入门
2015年12月27日 15:41
Spring Boot 入门

Spring Boot是Spring社区较新的一个项目。该项目的目的是帮助开发者更容易的创建基于Spring的应用程序和服务,让更多人的人更快的对Spring进行入门体验,让Java开发也能够实现Ruby on Rails那样的生产效率。为Spring生态系统提供了一种固定的、约定优于配置风格的框架。

Spring Boot具有如下特性:

    为基于Spring的开发提供更快的入门体验
    开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求。
    提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等。
    Spring Boot并不是不对Spring功能上的增强,而是提供了一种快速使用Spring的方式。

Spring Boot 系列

由于我博客Spring Boot 系列文章还不够多,所以暂时不打算创建专栏,如果再多几篇我就建专栏。

    Spring Boot 入门

    Spring Boot 属性配置和使用

    Spring Boot 集成MyBatis

    Spring Boot 静态资源处理

本文根据官方文档深入讲解一段代码
简单例子

Spring Boot建议使用Maven或Gradle,本文以Maven为例。

首先创建一个一般的Maven项目,有一个pom.xml和基本的src/main/java结构。
在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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.abel533</groupId>
    <artifactId>spring-boot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>1.2.5.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

首先是增加了<parent>

增加父pom比较简单,而且spring-boot-starter-parent包含了大量配置好的依赖管理,在自己项目添加这些依赖的时候不需要写<version>版本号。

使用父pom虽然简单,但是有些情况我们已经有父pom,不能直接增加<parent>时,可以通过如下方式:

<dependencyManagement>
     <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>1.2.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

java.version属性

上面pom.xml虽然没有出现这个属性,这里要特别提醒。

Spring默认使用jdk1.6,如果你想使用jdk1.8,你需要在pom.xml的属性里面添加java.version,如下:

<properties>
    <java.version>1.8</java.version>
</properties>

添加spring-boot-starter-web依赖

Spring通过添加spring-boot-starter-*这样的依赖就能支持具体的某个功能。

我们这个示例最终是要实现web功能,所以添加的是这个依赖。

更完整的功能列表可以查看:using-boot-starter-poms
添加spring-boot-maven-plugin插件

该插件支持多种功能,常用的有两种,第一种是打包项目为可执行的jar包。

在项目根目录下执行mvn package将会生成一个可执行的jar包,jar包中包含了所有依赖的jar包,只需要这一个jar包就可以运行程序,使用起来很方便。该命令执行后还会保留一个XXX.jar.original的jar包,包含了项目中单独的部分。

生成这个可执行的jar包后,在命令行执行java -jar xxxx.jar即可启动项目。

另外一个命令就是mvn spring-boot:run,可以直接使用tomcat(默认)启动项目。

在我们开发过程中,我们需要经常修改,为了避免重复启动项目,我们可以启用热部署。

Spring-Loaded项目提供了强大的热部署功能,添加/删除/修改 方法/字段/接口/枚举 等代码的时候都可以热部署,速度很快,很方便。

想在Spring Boot中使用该功能非常简单,就是在spring-boot-maven-plugin插件下面添加依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>springloaded</artifactId>
        <version>1.2.5.RELEASE</version>
    </dependency>
</dependencies>

添加以后,通过mvn spring-boot:run启动就支持热部署了。

注意:使用热部署的时候,需要IDE编译类后才能生效,你可以打开自动编译功能,这样在你保存修改的时候,类就自动重新加载了。
创建一个应用类

我们创建一个Application类:

@RestController
@EnableAutoConfiguration
public class Application {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

    @RequestMapping("/now")
    String hehe() {
        return "现在时间:" + (new Date()).toLocaleString();
    }

    public static void main(String[] args) {
        SpringApplication.run(Example.class, args);
    }

}

注意

Spring Boot建议将我们main方法所在的这个主要的配置类配置在根包名下。

类似如下结构:

com
 +- example
     +- myproject
         +- Application.java
         |
         +- domain
         |   +- Customer.java
         |   +- CustomerRepository.java
         |
         +- service
         |   +- CustomerService.java
         |
         +- web
             +- CustomerController.java

在Application.java中有main方法。

因为默认和包有关的注解,默认包名都是当前类所在的包,例如@ComponentScan, @EntityScan, @SpringBootApplication注解。
@RestController

因为我们例子是写一个web应用,因此写的这个注解,这个注解相当于同时添加@Controller和@ResponseBody注解。
@EnableAutoConfiguration

Spring Boot建议只有一个带有该注解的类。

@EnableAutoConfiguration作用:Spring Boot会自动根据你jar包的依赖来自动配置项目。例如当你项目下面有HSQLDB的依赖时,Spring Boot会创建默认的内存数据库的数据源DataSource,如果你自己创建了DataSource,Spring Boot就不会创建默认的DataSource。

如果你不想让Spring Boot自动创建,你可以配置注解的exclude属性,例如:

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

@SpringBootApplication

由于大量项目都会在主要的配置类上添加@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解。

因此Spring Boot提供了@SpringBootApplication注解,该注解可以替代上面三个注解(使用Spring注解继承实现)。
home等方法

@RequestMapping("/")
String home() {
    return "Hello World!";
}

@RequestMapping("/now")
String hehe() {
    return "现在时间:" + (new Date()).toLocaleString();
}

这些方法都添加了@RequestMapping("xxx"),这个注解起到路由的作用。
启动项目SpringApplication.run

启动Spring Boot项目最简单的方法就是执行下面的方法:

SpringApplication.run(Application.class, args);

该方法返回一个ApplicationContext对象,使用注解的时候返回的具体类型是AnnotationConfigApplicationContext或AnnotationConfigEmbeddedWebApplicationContext,当支持web的时候是第二个。

除了上面这种方法外,还可以用下面的方法:

SpringApplication application = new SpringApplication(Application.class);
application.run(args);

SpringApplication包含了一些其他可以配置的方法,如果你想做一些配置,可以用这种方式。

除了上面这种直接的方法外,还可以使用SpringApplicationBuilder:

new SpringApplicationBuilder()
        .showBanner(false)
        .sources(Application.class)
        .run(args);

当使用SpringMVC的时候由于需要使用子容器,就需要用到SpringApplicationBuilder,该类有一个child(xxx...)方法可以添加子容器。
运行

在IDE中直接直接执行main方法,然后访问http://localhost:8080即可。

另外还可以用上面提到的mvn,可以打包为可执行jar包,然后执行java -jar xxx.jar。

或者执行mvn spring-boot:run运行项目。

项目启动后输出如下日志:

[INFO] Attaching agents: [F:\.m2\repository\org\springframework\springloaded\1.2.5.RELEASE\springloaded-1.2.5.RELEASE.jar]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

2015-12-12 22:26:35.298  INFO 9844 --- [           main] c.github.abel533.springboot.Application  : Starting Application on liuzh-PC with PID 9844 (F:\Liu\IDEA\SpringBoot\spring-boot\target\classes started by liuzh_3nofxnp in F:\Liu\IDEA\SpringBoot\spring-boot)
2015-12-12 22:26:35.332  INFO 9844 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@a38d7a3: startup date [Sat Dec 12 22:26:35 CST 2015]; root of context hierarchy
2015-12-12 22:26:35.734  INFO 9844 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-12-12 22:26:36.302  INFO 9844 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-12-12 22:26:36.456  INFO 9844 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-12-12 22:26:36.457  INFO 9844 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.20
2015-12-12 22:26:36.537  INFO 9844 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-12-12 22:26:36.537  INFO 9844 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1207 ms
2015-12-12 22:26:36.941  INFO 9844 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-12-12 22:26:36.944  INFO 9844 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-12-12 22:26:36.945  INFO 9844 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-12-12 22:26:37.111  INFO 9844 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@a38d7a3: startup date [Sat Dec 12 22:26:35 CST 2015]; root of context hierarchy
2015-12-12 22:26:37.152  INFO 9844 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto java.lang.String com.github.abel533.springboot.Application.home()
2015-12-12 22:26:37.152  INFO 9844 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/now],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto java.lang.String com.github.abel533.springboot.Application.hehe()
2015-12-12 22:26:37.156  INFO 9844 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-12-12 22:26:37.156  INFO 9844 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-12-12 22:26:37.175  INFO 9844 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-12 22:26:37.175  INFO 9844 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-12 22:26:37.195  INFO 9844 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-12 22:26:37.237  INFO 9844 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-12-12 22:26:37.279  INFO 9844 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-12-12 22:26:37.280  INFO 9844 --- [           main] c.github.abel533.springboot.Application  : Started Application in 2.181 seconds (JVM running for 2.607)

最后

以上是Spring Boot基础的内容,有些不全面的地方或者读者有更多疑问,可以查看Spring Boot完整文档。

关于Spring Boot更多的内容可以继续关注本博客。
作者:isea533 发表于2015/12/27 15:41:29 原文链接
阅读:7109 评论:3 查看评论
[原]Spring Boot 静态资源处理
2015年12月27日 15:38
Spring Boot 静态资源处理

Spring Boot 默认的处理方式就已经足够了,默认情况下Spring Boot 使用WebMvcAutoConfiguration中配置的各种属性。

建议使用Spring Boot 默认处理方式,需要自己配置的地方可以通过配置文件修改。

但是如果你想完全控制Spring MVC,你可以在@Configuration注解的配置类上增加@EnableWebMvc,增加该注解以后WebMvcAutoConfiguration中配置就不会生效,你需要自己来配置需要的每一项。这种情况下的配置方法建议参考WebMvcAutoConfiguration类。

本文以下内容针对Spring Boot 默认的处理方式,部分配置通过在application.yml配置文件中设置。
配置资源映射

Spring Boot 默认配置的/**映射到/static(或/public ,/resources,/META-INF/resources),/webjars/**会映射到classpath:/META-INF/resources/webjars/。

注意:上面的/static等目录都是在classpath:下面。

如果你想增加如/mystatic/**映射到classpath:/mystatic/,你可以让你的配置类继承WebMvcConfigurerAdapter,然后重写如下方法:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/mystatic/**")
            .addResourceLocations("classpath:/mystatic/");
}

这种方式会在默认的基础上增加/mystatic/**映射到classpath:/mystatic/,不会影响默认的方式,可以同时使用。

静态资源映射还有一个配置选项,为了简单这里用.properties方式书写:

spring.mvc.static-path-pattern=/** # Path pattern used for static resources.

这个配置会影响默认的/**,例如修改为/static/**后,只能映射如/static/js/sample.js这样的请求(修改前是/js/sample.js)。这个配置只能写一个值,不像大多数可以配置多个用逗号隔开的。
使用注意

例如有如下目录结构:

└─resources
    │  application.yml
    │
    ├─static
    │  ├─css
    │  │      index.css
    │  │
    │  └─js
    │          index.js
    │
    └─templates
            index.ftl

在index.ftl中该如何引用上面的静态资源呢?
如下写法:

<link rel="stylesheet" type="text/css" href="/css/index.css">
<script type="text/javascript" src="/js/index.js"></script>

注意:默认配置的/**映射到/static(或/public ,/resources,/META-INF/resources)

当请求/css/index.css的时候,Spring MVC 会在/static/目录下面找到。

如果配置为/static/css/index.css,那么上面配置的几个目录下面都没有/static目录,因此会找不到资源文件!

所以写静态资源位置的时候,不要带上映射的目录名(如/static/,/public/ ,/resources/,/META-INF/resources/)!
使用WebJars

WebJars:http://www.webjars.org/

例如使用jquery,添加依赖:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>1.11.3</version>
</dependency>

然后可以如下使用:

<script type="text/javascript" src="/webjars/jquery/1.11.3/jquery.js"></script>

你可能注意到href中的1.11.3版本号了,如果仅仅这么使用,那么当我们切换版本号的时候还要手动修改href,怪麻烦的,我们可以用如下方式解决。

先在pom.xml中添加依赖:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator</artifactId>
</dependency>

增加一个WebJarController:

@Controller
public class WebJarController {
    private final WebJarAssetLocator assetLocator = new WebJarAssetLocator();

    @ResponseBody
    @RequestMapping("/webjarslocator/{webjar}/**")
    public ResponseEntity locateWebjarAsset(@PathVariable String webjar, HttpServletRequest request) {
        try {
            String mvcPrefix = "/webjarslocator/" + webjar + "/";
            String mvcPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
            String fullPath = assetLocator.getFullPath(webjar, mvcPath.substring(mvcPrefix.length()));
            return new ResponseEntity(new ClassPathResource(fullPath), HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }
}

然后使用的时候按照如下方式:

<script type="text/javascript" src="/webjarslocator/jquery/jquery.js"></script>

注意:这里不需要在写版本号了,但是注意写url的时候,只是在原来url基础上去掉了版本号,其他的都不能少!
静态资源版本管理

Spring MVC 提供了静态资源版本映射的功能。

用途:当我们资源内容发生变化时,由于浏览器缓存,用户本地的静态资源还是旧的资源,为了防止这种情况导致的问题,我们可能会手动在请求url的时候加个版本号或者其他方式。

版本号如:

<script type="text/javascript" src="/js/sample.js?v=1.0.1"></script>

Spring MVC 提供的功能可以很容易的帮助我们解决类似问题。

Spring MVC 有两种解决方式。

注意:下面的配置方式针对freemarker模板方式,其他的配置方式可以参考。
资源名-md5 方式

例如:

<link rel="stylesheet" type="text/css" href="/css/index-2b371326aa93ce4b611853a309b69b29.css">

Spring 会自动读取资源md5,然后添加到index.css的名字后面,因此当资源内容发生变化的时候,文件名发生变化,就会更新本地资源。

配置方式:

在application.properties中做如下配置:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

这样配置后,所有/**请求的静态资源都会被处理为上面例子的样子。

到这儿还没完,我们在写资源url的时候还要特殊处理。

首先增加如下配置:

@ControllerAdvice
public class ControllerConfig {

    @Autowired
    ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("urls")
    public ResourceUrlProvider urls() {
        return this.resourceUrlProvider;
    }

}

然后在页面写的时候用下面的写法:

<link rel="stylesheet" type="text/css" href="${urls.getForLookupPath('/css/index.css')}">

使用urls.getForLookupPath('/css/index.css')来得到处理后的资源名。
版本号 方式

在application.properties中做如下配置:

spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/**,/v1.0.0/**
spring.resources.chain.strategy.fixed.version=v1.0.0

这里配置需要特别注意,将version的值配置在paths中。原因我们在讲Spring MVC 处理逻辑的时候说。

在页面写的时候,写法如下:

<script type="text/javascript" src="${urls.getForLookupPath('/js/index.js')}"></script>

注意,这里仍然使用了urls.getForLookupPath,urls配置方式见上一种方式。

在请求的实际页面中,会显示为:

<script type="text/javascript" src="/v1.0.0/js/index.js"></script>

可以看到这里的地址是/v1.0.0/js/index.js。
静态资源版本管理 处理过程

在Freemarker模板首先会调用urls.getForLookupPath方法,返回一个/v1.0.0/js/index.js或/css/index-2b371326aa93ce4b611853a309b69b29.css。

这时页面上的内容就是处理后的资源地址。

这之后浏览器发起请求。

这里分开说。
第一种md5方式

请求/css/index-2b371326aa93ce4b611853a309b69b29.css,我们md5配置的paths=/**,所以Spring MVC 会尝试url中是否包含-,如果包含会去掉后面这部分,然后去映射的目录(如/static/)查找/css/index.css文件,如果能找到就返回。
第二种版本方式

请求/v1.0.0/js/index.js。

如果我们paths中没有配置/v1.0.0,那么上面这个请求地址就不会按版本方式来处理,因此会找不到上面的资源。

如果配置了/v1.0.0,Spring 就会将/v1.0.0去掉再去找/js/index.js,最终会在/static/下面找到。
本文参考

    http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html
    http://www.webjars.org/documentation
    http://www.mscharhag.com/spring/resource-versioning-with-spring-mvc
    https://spring.io/blog/2014/07/24/spring-framework-4-1-handling-static-web-resources

如果你使用的JSP或者其他模板,你可以参考上面几个链接的内容。
最后

以上是Spring Boot 静态资源处理的内容,有些不全面的地方或者读者有更多疑问,可以查看Spring Boot完整文档或本文参考的内容。

关于Spring Boot更多的内容可以继续关注本博客。
Spring Boot 系列

由于我博客Spring Boot 系列文章还不够多,所以暂时不打算创建专栏,如果再多几篇我就建专栏。

    Spring Boot 入门

    Spring Boot 属性配置和使用

    Spring Boot 集成MyBatis

    Spring Boot 静态资源处理

作者:isea533 发表于2015/12/27 15:38:10 原文链接
阅读:4080 评论:1 查看评论
[原]Spring Boot 集成MyBatis
2015年12月27日 15:29
Spring Boot 集成MyBatis

在集成MyBatis前,我们先配置一个druid数据源。
Spring Boot 集成druid

druid有很多个配置选项,使用Spring Boot 的配置文件可以方便的配置druid。

在application.yml配置文件中写上:

spring:
    datasource:
        name: test
        url: jdbc:mysql://192.168.16.137:3306/test
        username: root
        password:
        # 使用druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        filters: stat
        maxActive: 20
        initialSize: 1
        maxWait: 60000
        minIdle: 1
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: select 'x'
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxOpenPreparedStatements: 20

这里通过type: com.alibaba.druid.pool.DruidDataSource配置即可!
Spring Boot 集成MyBatis

Spring Boot 集成MyBatis有两种方式,一种简单的方式就是使用MyBatis官方提供的:

    mybatis-spring-boot-starter

另外一种方式就是仍然用类似mybatis-spring的配置方式,这种方式需要自己写一些代码,但是可以很方便的控制MyBatis的各项配置。
一、mybatis-spring-boot-starter方式
在pom.xml中添加依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

mybatis-spring-boot-starter依赖树如下:
依赖树

其中mybatis使用的3.3.0版本,可以通过:
<mybatis.version>3.3.0</mybatis.version>属性修改默认版本。
mybatis-spring使用版本1.2.3,可以通过:
<mybatis-spring.version>1.2.3</mybatis-spring.version>修改默认版本。
在application.yml中增加配置:

mybatis:
  mapperLocations: classpath:mapper/*.xml
  typeAliasesPackage: tk.mapper.model

除了上面常见的两项配置,还有:

    mybatis.config:mybatis-config.xml配置文件的路径
    mybatis.typeHandlersPackage:扫描typeHandlers的包
    mybatis.checkConfigLocation:检查配置文件是否存在
    mybatis.executorType:设置执行模式(SIMPLE, REUSE, BATCH),默认为SIMPLE

二、mybatis-spring方式

这种方式和平常的用法比较接近。需要添加mybatis依赖和mybatis-spring依赖。

然后创建一个MyBatisConfig配置类:

/**
 * MyBatis基础配置
 *
 * @author liuzh
 * @since 2015-12-19 10:11
 */
@Configuration
@EnableTransactionManagement
public class MyBatisConfig implements TransactionManagementConfigurer {

    @Autowired
    DataSource dataSource;

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("tk.mybatis.springboot.model");

        //分页插件
        PageHelper pageHelper = new PageHelper();
        Properties properties = new Properties();
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("returnPageInfo", "check");
        properties.setProperty("params", "count=countSql");
        pageHelper.setProperties(properties);

        //添加插件
        bean.setPlugins(new Interceptor[]{pageHelper});

        //添加XML目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }
}

上面代码创建了一个SqlSessionFactory和一个SqlSessionTemplate,为了支持注解事务,增加了@EnableTransactionManagement注解,并且反回了一个PlatformTransactionManagerBean。

另外应该注意到这个配置中没有MapperScannerConfigurer,如果我们想要扫描MyBatis的Mapper接口,我们就需要配置这个类,这个配置我们需要单独放到一个类中。

/**
 * MyBatis扫描接口
 *
 * @author liuzh
 * @since 2015-12-19 14:46
 */
@Configuration
//TODO 注意,由于MapperScannerConfigurer执行的比较早,所以必须有下面的注解
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
        mapperScannerConfigurer.setBasePackage("tk.mybatis.springboot.mapper");
        return mapperScannerConfigurer;
    }

}

这个配置一定要注意@AutoConfigureAfter(MyBatisConfig.class),必须有这个配置,否则会有异常。原因就是这个类执行的比较早,由于sqlSessionFactory还不存在,后续执行出错。

做好上面配置以后就可以使用MyBatis了。
关于分页插件和通用Mapper集成

分页插件作为插件的例子在上面代码中有。

通用Mapper配置实际就是配置MapperScannerConfigurer的时候使用tk.mybatis.spring.mapper.MapperScannerConfigurer即可,配置属性使用Properties。
Spring Boot集成MyBatis的基础项目

我上传到github一个采用第二种方式的集成项目,并且集成了分页插件和通用Mapper,项目包含了简单的配置和操作,仅作为参考。

项目地址:https://github.com/abel533/MyBatis-Spring-Boot

分页插件和通用Mapper的相关信息可以通过上面地址找到。
Spring Boot 系列

由于我博客Spring Boot 系列文章还不够多,所以暂时不打算创建专栏,如果再多几篇我就建专栏。

    Spring Boot 入门

    Spring Boot 属性配置和使用

    Spring Boot 集成MyBatis

    Spring Boot 静态资源处理

作者:isea533 发表于2015/12/27 15:29:01 原文链接
阅读:6976 评论:6 查看评论
[原]Spring Boot 属性配置和使用
2015年12月27日 15:27
Spring Boot 属性配置和使用

Spring Boot 允许通过外部配置让你在不同的环境使用同一应用程序的代码,简单说就是可以通过配置文件来注入属性或者修改默认的配置。

Spring Boot 入门 请看:http://blog.csdn.net/isea533/article/details/50278205
Spring Boot 支持多种外部配置方式

这些方式优先级如下:

    命令行参数
    来自java:comp/env的JNDI属性
    Java系统属性(System.getProperties())
    操作系统环境变量
    RandomValuePropertySource配置的random.*属性值
    jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
    jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
    jar包外部的application.properties或application.yml(不带spring.profile)配置文件
    jar包内部的application.properties或application.yml(不带spring.profile)配置文件
    @Configuration注解类上的@PropertySource
    通过SpringApplication.setDefaultProperties指定的默认属性

命令行参数

通过java -jar app.jar --name="Spring" --server.port=9090方式来传递参数。

参数用--xxx=xxx的形式传递。

可以使用的参数可以是我们自己定义的,也可以是Spring Boot中默认的参数。

很多人可能会关心如web端口如何配置这样的问题,这些都是Spring Boot中提供的参数,部分可用参数如下:

# LOGGING
logging.path=/var/logs
logging.file=myapp.log
logging.config= # location of config file (default classpath:logback.xml for logback)
logging.level.*= # levels for loggers, e.g. "logging.level.org.springframework=DEBUG" (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF)

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
server.port=8080
server.address= # bind to a specific NIC
server.session-timeout= # session timeout in seconds
server.context-parameters.*= # Servlet context init parameters, e.g. server.context-parameters.a=alpha
server.context-path= # the context path, defaults to '/'
server.servlet-path= # the servlet path, defaults to '/'

更多常见的应用属性请浏览这里

注意:命令行参数在app.jar的后面!

可以通过SpringApplication.setAddCommandLineProperties(false)禁用命令行配置。
Java系统属性

注意Java系统属性位置java -Dname="isea533" -jar app.jar,可以配置的属性都是一样的,优先级不同。

例如java -Dname="isea533" -jar app.jar --name="Spring!"中name值为Spring!
操作系统环境变量

配置过JAVA_HOME的应该都了解这一个。

这里需要注意的地方,有些OS可以不支持使用.这种名字,如server.port,这种情况可以使用SERVER_PORT来配置。

具体名字如何匹配,看本文后面。
RandomValuePropertySource

系统中用到随机数的地方,例如:

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

random.int*支持value参数和,max参数,当提供max参数的时候,value就是最小值。
应用配置文件(.properties或.yml)

在配置文件中直接写:

name=Isea533
server.port=8080

.yml格式的配置文件如:

name: Isea533
server:
    port: 8080

当有前缀的情况下,使用.yml格式的配置文件更简单。关于.yml配置文件用法请看这里

注意:使用.yml时,属性名的值和冒号中间必须有空格,如name: Isea533正确,name:Isea533就是错的。
属性配置文件的位置

spring会从classpath下的/config目录或者classpath的根目录查找application.properties或application.yml。

/config优先于classpath根目录
@PropertySource

这个注解可以指定具体的属性配置文件,优先级比较低。
SpringApplication.setDefaultProperties

例如:

SpringApplication application = new SpringApplication(Application.class);
Map<String, Object> defaultMap = new HashMap<String, Object>();
defaultMap.put("name", "Isea-Blog");
//还可以是Properties对象
application.setDefaultProperties(defaultMap);
application.run(args);

应用(使用)属性
@Value(“${xxx}”)

这种方式是最简单的,通过@Value注解可以将属性值注入进来。
@ConfigurationProperties

Spring Boot 可以方便的将属性注入到一个配置对象中。例如:

my.name=Isea533
my.port=8080
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

对应对象:

@ConfigurationProperties(prefix="my")
public class Config {
    private String name;
    private Integer port;
    private List<String> servers = new ArrayList<String>();

    public String geName(){
        return this.name;
    }

    public Integer gePort(){
        return this.port;
    }
    public List<String> getServers() {
        return this.servers;
    }
}

Spring Boot 会自动将prefix="my"前缀为my的属性注入进来。

Spring Boot 会自动转换类型,当使用List的时候需要注意在配置中对List进行初始化!

Spring Boot 还支持嵌套属性注入,例如:

name=isea533
jdbc.username=root
jdbc.password=root
...

对应的配置类:

@ConfigurationProperties
public class Config {
    private String name;
    private Jdbc jdbc;
    class Jdbc {
        private String username;
        private String password;
        //getter...
    }

    public Integer gePort(){
        return this.port;
    }
    public Jdbc getJdbc() {
        return this.jdbc;
    }
}

jdbc开头的属性都会注入到Jdbc对象中。
在@Bean方法上使用@ConfigurationProperties

例如:

@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
    ...
}

Spring Boot 会将foo开头的属性按照名字匹配注入到FooComponent对象中。
属性占位符

例如:

app.name=MyApp
app.description=${app.name} is a Spring Boot application

可以在配置文件中引用前面配置过的属性(优先级前面配置过的这里都能用)。

通过如${app.name:默认名称}方法还可以设置默认值,当找不到引用的属性时,会使用默认的属性。

由于${}方式会被Maven处理。如果你pom继承的spring-boot-starter-parent,Spring Boot 已经将maven-resources-plugins默认的${}方式改为了@ @方式,例如@name@。

如果你是引入的Spring Boot,你可以修改使用其他的分隔符
通过属性占位符还能缩短命令参数

例如修改web默认端口需要使用--server.port=9090方式,如果在配置中写上:

server.port=${port:8080}

那么就可以使用更短的--port=9090,当不提供该参数的时候使用默认值8080。
属性名匹配规则

例如有如下配置对象:

@Component
@ConfigurationProperties(prefix="person")
public class ConnectionSettings {

    private String firstName;

}

firstName可以使用的属性名如下:

    person.firstName,标准的驼峰式命名
    person.first-name,虚线(-)分割方式,推荐在.properties和.yml配置文件中使用
    PERSON_FIRST_NAME,大写下划线形式,建议在系统环境变量中使用

属性验证

可以使用JSR-303注解进行验证,例如:

@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}

最后

以上是Spring Boot 属性配置和使用的内容,有些不全面的地方或者读者有更多疑问,可以查看Spring Boot完整文档 或 Externalized Configuration。

关于Spring Boot更多的内容可以继续关注本博客。
Spring Boot 系列

由于我博客Spring Boot 系列文章还不够多,所以暂时不打算创建专栏,如果再多几篇我就建专栏。

    Spring Boot 入门

    Spring Boot 属性配置和使用

    Spring Boot 集成MyBatis

    Spring Boot 静态资源处理

作者:isea533 发表于2015/12/27 15:27:51 原文链接
阅读:8001 评论:1 查看评论
[原]Spring MVC 集成 jackson-dataformat-xml 问题
2015年12月27日 11:25
Spring MVC 集成 jackson-dataformat-xml 问题
HttpMessageNotWritableException
Could not write content

注:如果你没有遇到这个问题,你可以直接看下面解决方法二。

当我在SpringBoot集成Spring MVC中使用XML格式输出的时候,出错了,后台错误信息如下:

    Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException:
    Could not write content: Not implemented (through reference chain: org.github.abel533.springboot.model.Country[“id”]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Not implemented (through reference chain: org.github.abel533.springboot.model.Country[“id”])

页面提示错误如下:

<html>
<body>
<h1>Whitelabel Error Page</h1>
<p>
This application has no explicit mapping for /error, so you are seeing this as a fallback.
</p>
<div id="created">Sun Dec 27 10:35:49 CST 2015</div>
<div>
There was an unexpected error (type=Internal Server Error, status=500).
</div>
<div>
Could not write content: Not implemented (through reference chain: org.github.abel533.springboot.model.Country["id"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Not implemented (through reference chain: org.github.abel533.springboot.model.Country["id"])
</div>
</body>
</html>

在默认情况下产生错误的原因只有一个,那就是启用了下面的配置:

    spring.jackson.serialization.indent_output=true

这个配置会让JSON格式化输出,方便阅读。但是这个配置对XML会起到负面作用。

当输出XML的时候会使用DefaultXmlPrettyPrinter,这个类在调用下面方法输出XML的时候

public void writeLeafElement(XMLStreamWriter2 sw,
            String nsURI, String localName, int value)
        throws XMLStreamException

会使用Stax2WriterAdapter类的下面方法进行输出:

public void writeRaw(String text, int offset, int len) throws XMLStreamException
{
    // There is no clean way to implement this via Stax 1.0, alas...
    throw new UnsupportedOperationException("Not implemented");
}

由于这个方法没有实现,这就导致了上面错误的产生。
解决办法
方法一

由于使用下面的配置:

    spring.jackson.serialization.indent_output=true

产生的问题,所以只要不启用格式化输出(默认false)就不会有这个问题。
方法二

参考:https://github.com/FasterXML/jackson-dataformat-xml#maven-dependency

在官方github中写了:

    Also: you usually also want to make sure that XML library in use is Woodstox since it is not only faster than Stax implementation JDK provides, but also works better and avoids some known issues like adding unnecessary namespace prefixes.

jackson-dataformat-xml默认使用下面的stax2-api依赖:

 <dependency>
     <groupId>org.codehaus.woodstox</groupId>
     <artifactId>stax2-api</artifactId>
     <version>3.1.4</version>
</dependency>

stax2-api的Stax2WriterAdapter有些未完成的实现,还有其他的问题。并且Woodstox比Stax快,所以官方推荐使用woodstox-core-asl(Spring官方也推荐这个):

<dependency>
    <groupId>org.codehaus.woodstox</groupId>
    <artifactId>woodstox-core-asl</artifactId>
    <version>4.4.1</version>
</dependency>

因此我们只要添加上面的woodstox-core-asl依赖即可解决问题。
作者:isea533 发表于2015/12/27 11:25:55 原文链接
阅读:656 评论:0 查看评论
[原]我与MyBatis
2015年12月7日 21:15

今天看到CSDN博客提示有博客之星评选看到需要至少写140字我就犯难了。

打开Notepad++写了我与MyBatis之间的一些事,没想到最后粘贴回去的时候已经超过了最大限制。

本文是完整版,内容如下:

    从我主导公司的第一个项目时,我就选择了从没用过的MyBatis,从新手阶段摸索学习,到后来为了方便自己不得不去实现一个自己想要的分页插件。从此开始深入了解MyBatis,同时我博客中开始大量出现和MyBatis相关的内容。分页插件从1.0发展到现在的4.x版本,中间有很长一段时间我自己没有使用,但是由于问我问题的人越来越多,我帮助了很多人,同时为了帮助很多人解决问题,我看了很多遍MyBatis源码,了解MyBatis内部的各种策略。由于对MyBatis的不断深入,分页插件才会很快(1年时间)从1.0升级到4.x,很多人都说我是版本帝,但是很少有人知道长期维护一个开源项目有多难。

    再后来出差的一天下午写了个MyBatis的通用Mapper(DAO),这就纯粹是为了帮助一部分人才写的一个插件,同时也是对MyBatis很多的积累后写的一个开源项目。这个项目发展到现在,我自己很多项目也都在用,而且以后会越来越好用(最近在准备2016年初的通用Mapper4)。

    通过对MyBatis的深入了解,以及创建一个500人群(目前发展到1300多人)来帮助更多人解决MyBatis问题,有了这些长期的积累,我也写了一系列从源码来了解问题根本原因的文章和一些MyBatis示例的文章,我希望有更多的人能真正了解,并且去帮助更多刚刚入门的人。

    掌握MyBatis的原理和插件中关键的思路可以很轻易的去开发一些很有用的插件,由于我个人开源项目太多,没有更多的精力去写更多的插件,因此我通过直播(完全免费,不定期开讲)来讲MyBatis的源码和插件,让更多的人能轻松掌握MyBatis扩展是我想达到的一个目标,我希望更多人的能掌握这些技能,能够开发出一些通用的插件,我也希望更多的人可以开源自己的插件,最好能展示在mybatis.tk网站上,让更多使用MyBatis的朋友能够方便的了解MyBatis的各方面内容。

我正在参加CSDN 2015博客之星评选,希望得到您的宝贵一票~
地址:http://vote.blog.csdn.net/blogstar2015/candidate?username=isea533
作者:isea533 发表于2015/12/7 21:15:36 原文链接
阅读:792 评论:3 查看评论
[原]MyBatis中的OGNL教程
2015年12月5日 18:47
MyBatis中的OGNL教程

有些人可能不知道MyBatis中使用了OGNL,有些人知道用到了OGNL却不知道在MyBatis中如何使用,本文就是讲如何在MyBatis中使用OGNL。

如果我们搜索OGNL相关的内容,通常的结果都是和Struts有关的,你肯定搜不到和MyBatis有关的,虽然和Struts中的用法类似但是换种方式理解起来就有难度。
MyBatis常用OGNL表达式

    e1 or e2
    e1 and e2
    e1 == e2,e1 eq e2
    e1 != e2,e1 neq e2
    e1 lt e2:小于
    e1 lte e2:小于等于,其他gt(大于),gte(大于等于)
    e1 in e2
    e1 not in e2
    e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
    !e,not e:非,求反
    e.method(args)调用对象方法
    e.property对象属性值
    e1[ e2 ]按索引取值,List,数组和Map
    @class@method(args)调用类的静态方法
    @class@field调用类的静态字段值

上述内容只是合适在MyBatis中使用的OGNL表达式,完整的表达式点击这里。
MyBatis中什么地方可以使用OGNL?

如果你看过深入了解MyBatis参数,也许会有印象,因为这篇博客中提到了OGNL和一些特殊用法。

如果没看过,建议找时间看看,上面这篇博客不是很容易理解,但是理解后会很有用。

MyBatis中可以使用OGNL的地方有两处:

    动态SQL表达式中
    ${param}参数中

上面这两处地方在MyBatis中处理的时候都是使用OGNL处理的。

下面通过举例来说明这两种情况的用法。
1.动态SQL表达式中

例一:

<select id="xxx" ...>
    select id,name,... from country
    <where>
        <if test="name != null and name != ''">
            name like concat('%', #{name}, '%')
        </if>
    </where>
</select>

上面代码中test的值会使用OGNL计算结果。

例二:

<select id="xxx" ...>
    select id,name,... from country
    <bind name="nameLike" value="'%' + name + '%'"/>
    <where>
        <if test="name != null and name != ''">
            name like '${nameLike}'
        </if>
    </where>
</select>

这里<bind>的value值会使用OGNL计算。

注:对<bind参数的调用只能通过${}方式获取,如${nameLike}。

在通用Mapper中支持一种UUID的主键,在通用Mapper中的实现就是使用了<bind>标签,这个标签调用了一个静态方法,大概方法如下:

<bind name="username_bind"
      value='@java.util.UUID@randomUUID().toString().replace("-", "")' />

这种方式虽然能自动调用静态方法,但是没法回写对应的属性值,因此使用时需要注意。
2.${param}参数中

上面like的例子中使用下面这种方式最简单

<select id="xxx" ...>
    select id,name,... from country
    <where>
        <if test="name != null and name != ''">
            name like '${'%' + name + '%'}'
        </if>
    </where>
</select>

这里注意写的是${'%' + name + '%'},而不是%${name}%,这两种方式的结果一样,但是处理过程不一样。

在MyBatis中处理${}的时候,只是使用OGNL计算这个结果值,然后替换SQL中对应的${xxx},OGNL处理的只是${这里的表达式}。

这里表达式可以是OGNL支持的所有表达式,可以写的很复杂,可以调用静态方法返回值,也可以调用静态的属性值。
例子:使用OGNL实现单表的分表功能

上面说的是OGNL简单的使用方法。这里举个OGNL实现数据库分表的例子。

分表这个功能是通用Mapper中的新功能,允许在运行的时候指定一个表名,通过指定的表名对表进行操作。这个功能实现就是使用了OGNL。

首先并不是所有的表都需要该功能,因此定义了一个接口,当参数(接口方法只有实体类一个参数)对象继承该接口的时候,就允许使用动态表名。

public interface IDynamicTableName {

    /**
     * 获取动态表名 - 只要有返回值,不是null和'',就会用返回值作为表名
     *
     * @return
     */
    String getDynamicTableName();
}

然后在XML中写表名的时候使用:

<if test="@tk.mybatis.mapper.util.OGNL@isDynamicParameter(_parameter)
            and dynamicTableName != null
            and dynamicTableName != ''">
    ${dynamicTableName}
</if>
<if test="@tk.mybatis.mapper.util.OGNL@isNotDynamicParameter(_parameter)
            or dynamicTableName == null
            or dynamicTableName == ''">
    defaultTableName
</if>

由于我需要判断_parameter是否继承了IDynamicTableName接口,简单的写法已经无法实现,所以使用了静态方法,这两个方法如下:

/**
 * 判断参数是否支持动态表名
 *
 * @param parameter
 * @return true支持,false不支持
 */
public static boolean isDynamicParameter(Object parameter) {
    if (parameter != null && parameter instanceof IDynamicTableName) {
        return true;
    }
    return false;
}

/**
 * 判断参数是否b支持动态表名
 *
 * @param parameter
 * @return true不支持,false支持
 */
public static boolean isNotDynamicParameter(Object parameter) {
    return !isDynamicParameter(parameter);
}

根据<if>判断的结果来选择使用那个表名。

另外注意XML判断中有一个dynamicTableName,这个参数是根据getDynamicTableName方法得到的,MyBatis使用属性对应的getter方法来获取值,不是根据field来获取值。
最后

如果你真想了解MyBatis中的OGNL用法,自己多写几个例子测试玩玩,动手测试是一种好的学习方式。
作者:isea533 发表于2015/12/5 18:47:31 原文链接
阅读:2742 评论:0 查看评论
[原]Apache Lucene 5.x 集成中文分词库 IKAnalyzer
2015年12月5日 16:24
Apache Lucene 5.x 集成中文分词库 IKAnalyzer

前面写过 Apache Lucene 5.x版本 示例,为了支持中文分词,我们可以使用中文分词库 IKAnalyzer。

由于IKAnalyzer使用的是4.x版本的Analyzer接口,该接口和5.x版本不兼容,因此,如果想要在5.x版本中使用IKAnalyzer,我们还需要自己来实现5.x版本的接口。

通过看源码,发现需要修改两个接口的类。

第一个是Tokenizer接口,我们写一个IKTokenizer5x:

/**
 * 支持5.x版本的IKTokenizer
 *
 * @author liuzh
 */
public class IKTokenizer5x extends Tokenizer {
    private IKSegmenter _IKImplement;
    private final CharTermAttribute termAtt = (CharTermAttribute)this.addAttribute(CharTermAttribute.class);
    private final OffsetAttribute offsetAtt = (OffsetAttribute)this.addAttribute(OffsetAttribute.class);
    private final TypeAttribute typeAtt = (TypeAttribute)this.addAttribute(TypeAttribute.class);
    private int endPosition;

    public IKTokenizer5x() {
        this._IKImplement = new IKSegmenter(this.input, true);
    }

    public IKTokenizer5x(boolean useSmart) {
        this._IKImplement = new IKSegmenter(this.input, useSmart);
    }

    public IKTokenizer5x(AttributeFactory factory) {
        super(factory);
        this._IKImplement = new IKSegmenter(this.input, true);
    }

    public boolean incrementToken() throws IOException {
        this.clearAttributes();
        Lexeme nextLexeme = this._IKImplement.next();
        if(nextLexeme != null) {
            this.termAtt.append(nextLexeme.getLexemeText());
            this.termAtt.setLength(nextLexeme.getLength());
            this.offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
            this.endPosition = nextLexeme.getEndPosition();
            this.typeAtt.setType(nextLexeme.getLexemeTypeString());
            return true;
        } else {
            return false;
        }
    }

    public void reset() throws IOException {
        super.reset();
        this._IKImplement.reset(this.input);
    }

    public final void end() {
        int finalOffset = this.correctOffset(this.endPosition);
        this.offsetAtt.setOffset(finalOffset, finalOffset);
    }
}

该类只是在IKTokenizer基础上做了简单修改,和原方法相比修改了public IKTokenizer(Reader in, boolean useSmart)这个构造方法,不在需要Reader参数。

另一个接口就是Analyzer的IKAnalyzer5x:

/**
 * 支持5.x版本的IKAnalyzer
 *
 * @author liuzh
 */
public class IKAnalyzer5x extends Analyzer {

    private boolean useSmart;

    public boolean useSmart() {
        return this.useSmart;
    }

    public void setUseSmart(boolean useSmart) {
        this.useSmart = useSmart;
    }

    public IKAnalyzer5x() {
        this(false);
    }

    public IKAnalyzer5x(boolean useSmart) {
        this.useSmart = useSmart;
    }

    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        IKTokenizer5x _IKTokenizer = new IKTokenizer5x(this.useSmart);
        return new TokenStreamComponents(_IKTokenizer);
    }
}

这个类的接口由
protected TokenStreamComponents createComponents(String fieldName, Reader in)
变成了
protected TokenStreamComponents createComponents(String fieldName)

方法的实现中使用了上面创建的IKTokenizer5x。

定义好上面的类后,在Lucene中使用IKAnalyzer5x即可。

针对IKAnalyzer5x我们写个简单测试:

/**
 * IKAnalyzer5x 测试
 *
 * @author liuzh
 */
public class IKAnalyzer5xTest {
    public static void main(String[] args) throws IOException {
        Analyzer analyzer = new IKAnalyzer5x(true);
        TokenStream ts = analyzer.tokenStream("field",
                new StringReader(
                    "IK Analyzer 是一个开源的,基于java语言开发的轻量级的中文分词工具包。" +
                    "从2006年12月推出1.0版开始, IKAnalyzer已经推出了4个大版本。" +
                    "最初,它是以开源项目Luence为应用主体的," +
                    "结合词典分词和文法分析算法的中文分词组件。从3.0版本开始," +
                    "IK发展为面向Java的公用分词组件,独立于Lucene项目," +
                    "同时提供了对Lucene的默认优化实现。在2012版本中," +
                    "IK实现了简单的分词歧义排除算法," +
                    "标志着IK分词器从单纯的词典分词向模拟语义分词衍化。"));

        OffsetAttribute offsetAtt = ts.addAttribute(OffsetAttribute.class);
        try {
            ts.reset();
            while (ts.incrementToken()) {
                System.out.println(offsetAtt.toString());
            }
            ts.end();
        } finally {
            ts.close();
        }
    }
}

输出结果:

    ik
    analyzer
    是
    一个
    开源
    的
    基于
    java
    语言
    开发
    的
    轻量级
    的
    中文
    分词
    工具包
    从
    2006年
    12月
    推出
    1.0版

由于结果较长,省略后面的输出内容。
作者:isea533 发表于2015/12/5 16:24:06 原文链接
阅读:877 评论:2 查看评论
[原]MyBatis底层基础和拦截器 - 第一部分
2015年11月30日 21:59
MyBatis底层基础和拦截器 - 第一部分

第一部分包含了下面代码的基本讲解和下面代码与XML配置关系和作用的讲解。

这一部分是了解后续拦截器和SqlSource的重要基础。

本视频不仅对深入学习MyBatis有用,对于一般的MyBatis使用也能加深理解。

第一部分完整视频+源文件+PPT下载地址:http://pan.baidu.com/s/1mgzZnx2

在第一部分视频后,还有一些和MyBatis相关问答的一些视频,这些视频可以从群共享下载。

另外想看第三遍讲解的各位还有看直播的机会,具体时间会在群内公布。

如果有意愿捐赠,可以从 http://mybatis.tk 下方扫描微信二维码或支付宝二维码进行捐赠。

下面是整篇讲解用到的代码:

package tk.mybatis.sample1;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.decorators.*;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.jdbc.JdbcTransaction;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.log4j.Logger;
import org.junit.Test;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 通过本段代码了解MyBatis内部的一些实现
 *
 * @author liuzh
 * @since 2015-11-24 23:04
 */
public class SimpleMyBatis {

    public final Logger logger = Logger.getLogger(SimpleMyBatis.class);

    @Test
    public void test() throws IOException, SQLException {
        //创建配置文件
        final Configuration config = new Configuration();
        config.setCacheEnabled(true);
        config.setLazyLoadingEnabled(false);
        config.setAggressiveLazyLoading(true);

        //为了后续说明拦截器,这里添加两个简单例子
        config.addInterceptor(new SampleExecutorInterceptor());
        config.addInterceptor(new SampleResultSetHandlerInterceptor());

        //创建DataSource
        Properties props = Resources.getResourceAsProperties("jdbc.properties");
        UnpooledDataSource dataSource = new UnpooledDataSource();
        dataSource.setDriver(props.getProperty("driver"));
        dataSource.setUrl(props.getProperty("url"));
        dataSource.setUsername(props.getProperty("username"));
        dataSource.setPassword(props.getProperty("password"));

        //创建Executor
        //<transactionManager type="JDBC"/>
        Transaction transaction = new JdbcTransaction(dataSource, null, false);

        //config.newExecutor会将符合条件的拦截器添加到Executor代理链上
        final Executor executor = config.newExecutor(transaction);

        //cache是一个多层代理【装饰模式】的缓存对象,通过一级一级代理使得一个简单的缓存拥有了复杂的功能
        //<cache/>
        final Cache countryCache =
                new SynchronizedCache(//同步缓存
                        new SerializedCache(//序列化缓存
                                new LoggingCache(//日志缓存
                                        new LruCache(//最少使用缓存
                                                new PerpetualCache("country_cache")//持久缓存
                                        ))));


        //类型处理注册器
        //自己写TypeHandler的时候可以参考该注册器中已经存在的大量实现
        final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();

        //================== 下面的步骤相当于解析XML或者解析接口注解方法生成ms =====================

        //创建静态sqlSource
        //最简单的,相当于从xml或接口注解获取SQL,创建合适的sqlSource对象
        StaticSqlSource sqlSource = new StaticSqlSource(config, "SELECT * FROM country WHERE id = ?");

        //由于上面的SQL有个参数id,这里需要提供ParameterMapping(参数映射)
        List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
        //通过ParameterMapping.Builder创建ParameterMapping
        parameterMappings.add(new ParameterMapping.Builder(config, "id", registry.getTypeHandler(int.class)).build());
        //通过ParameterMap.Builder创建ParameterMap
        ParameterMap.Builder paramBuilder = new ParameterMap.Builder(config, "defaultParameterMap", Country.class, parameterMappings);

        //创建ms
        MappedStatement.Builder msBuilder = new MappedStatement.Builder(config, "tk.mybatis.selectCountry", sqlSource, SqlCommandType.SELECT);
        msBuilder.parameterMap(paramBuilder.build());

        //<resultMap>
        final ResultMap resultMap = new ResultMap.Builder(config, "defaultResultMap", Country.class,
                new ArrayList<ResultMapping>() {
                    {
                        add(new ResultMapping.Builder(config, "id", "id", int.class).build());
                        add(new ResultMapping.Builder(config, "countryname", "countryname", String.class).build());
                        add(new ResultMapping.Builder(config, "countrycode", "countrycode", registry.getTypeHandler(String.class)
                        ).build());
                    }
                }).build();

        //2:不设置具体的映射,只是用类型,相当于只配置resultType="tk.mybatis.sample1.Country"
        //final ResultMap resultMap = new ResultMap.Builder(config, "defaultResultMap", Country.class, new ArrayList<ResultMapping>()).build();

        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        resultMaps.add(resultMap);
        //设置返回值的resultMap和resultType
        msBuilder.resultMaps(resultMaps);
        //设置缓存
        msBuilder.cache(countryCache);

        //创建ms
        MappedStatement ms = msBuilder.build();

        //第一种使用executor执行
        List<Country> countries = executor.query(ms, 1, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);

        for (Country country : countries) {
            logger.info(country);
        }

        //第二种
        //首先添加ms到config
        config.addMappedStatement(ms);
        //创建sqlSession
        SqlSession sqlSession = new DefaultSqlSession(config, executor, false);
        //查询
        Country country = sqlSession.selectOne("selectCountry", 35);
        logger.info(country);
        //关闭
        sqlSession.close();
    }
}

作者:isea533 发表于2015/11/30 21:59:31 原文链接
阅读:1244 评论:0 查看评论
[原]小工具 - 批量删除Maven下载失败的文件夹
2015年11月18日 13:12

用过Maven的应该都遇到过,当网速不好或者源有问题的时候,Maven的依赖包经常下载失败。

下载失败后在本地仓库对应的文件夹中有一个以.lastUpdated结尾的文件,如果不手动删除这个文件,就不能重新更新依赖,重新下载对应的jar包。

一般情况下遇到的时候可能直接手动找到目录删除。

当出现很多这样的情况时,一个个找起来也很麻烦。

因此本文提供一个小工具,就是一段Java代码,通过这段代码来删除。

CleanMvn.java:

public class CleanMvn {
    public static void main(String[] args){
        if(args.length != 1){
            print("使用方法错误,方法需要一个参数,参数为mvn本地仓库的路径");
        }
        findAndDelete(new File(args[0]);
    }

    public static boolean findAndDelete(File file){
        if(!file.exists()){
        } else if(file.isFile()){
            if(file.getName.endsWith("lastUpdated")){
                deleteFile(file.getParentFile());
                return true;
            }
        } else if(file.isDirectory()){
            File[] files = file.listFiles();
            for(File f : files){
                if(findAndDelete(f)){
                    break;
                }
            }
        }
        return false;
    }

    public static void deleteFile(File file){
        if(!file.exists()){
        } else if(file.isFile()){
            print("删除文件:" + file.getAbsolutePath());
            file.delete();
        } else if(file.isDirectory()){
            File[] files = file.listFiles();
            for(File f : files){
                deleteFile(f);
            }
            print("删除文件夹:" + file.getAbsolutePath());
            print("====================================");
            file.delete();
        }
    }

    public static void print(String msg){
        System.out.println(msg);
    }
}

可以在IDE中指定参数后运行这段代码,例如直接调方法findAndDelete(new File("d:\\.m2\\repository"));

或者在命令行下执行这段代码:

    首先javac CleanMvn.java编译为.class文件。

    然后java CleanMvn d:\.m2\repository通过后面的参数来删除本地仓库中无效的文件。

作者:isea533 发表于2015/11/18 13:12:20 原文链接
阅读:600 评论:1 查看评论
[原]Docx4j 简单操作文字图片(包含页眉页脚和主体内容)
2015年11月12日 23:38

docx4j官方提供了一些例子,本文只是其中一部分应用的简单例子。

需要注意的地方是页眉和页脚,必须创建对应关系才能起作用。

页眉和页脚添加图片的时候,调用的createImagePart方法必须用包含sourcePart参数的,带这个参数的方法会创建图片的对应关系,否则就会出现页眉页脚看不到图片的情况。

基本上掌握了图片和页眉页脚后,其他的都没什么难的,更多的用法可以参考官方的例子。

下面是完整代码例子,具体说明看注释:

import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.jaxb.Context;
import org.docx4j.model.structure.SectionWrapper;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.Part;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.FooterPart;
import org.docx4j.openpackaging.parts.WordprocessingML.HeaderPart;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.*;

import java.io.File;
import java.util.List;

/**
 * @author liuzh
 */
public class Docx4jTest {

    public static void main(String[] args) throws Exception {
        //创建一个word
        //读取可以使用WordprocessingMLPackage.load方法
        WordprocessingMLPackage word = WordprocessingMLPackage.createPackage();
        String imageFilePath = Docx4jTest.class.getResource("/lw.jpg").getPath();

        //创建页眉
        HeaderPart headerPart = createHeader(word);
        //页眉添加图片
        headerPart.getContent().add(newImage(word, headerPart, imageFilePath));

        //创建页脚
        FooterPart footerPart = createFooter(word);
        //添加图片
        footerPart.getContent().add(newImage(word, footerPart, imageFilePath));

        //主内容添加文本和图片
        word.getMainDocumentPart().getContent().add(newText("http://www.mybatis.tk"));
        word.getMainDocumentPart().getContent().add(newText("http://blog.csdn.net/isea533"));

        word.getMainDocumentPart().getContent().add(
                newImage(word, word.getMainDocumentPart(), imageFilePath));

        //保存
        word.save(new File("d:/1.docx"));
    }

    private static final ObjectFactory factory = Context.getWmlObjectFactory();

    /**
     * 创建一段文本
     *
     * @param text
     * @return
     */
    public static P newText(String text){
        P p = factory.createP();
        R r = factory.createR();
        Text t = new Text();
        t.setValue(text);
        r.getContent().add(t);
        p.getContent().add(r);
        return p;
    }

    /**
     * 创建包含图片的内容
     *
     * @param word
     * @param sourcePart
     * @param imageFilePath
     * @return
     * @throws Exception
     */
    public static P newImage(WordprocessingMLPackage word,
                             Part sourcePart,
                             String imageFilePath) throws Exception {
        BinaryPartAbstractImage imagePart = BinaryPartAbstractImage
                .createImagePart(word, sourcePart, new File(imageFilePath));
        //随机数ID
        int id = (int) (Math.random() * 10000);
        //这里的id不重复即可
        Inline inline = imagePart.createImageInline("image", "image", id, id * 2, false);

        Drawing drawing = factory.createDrawing();
        drawing.getAnchorOrInline().add(inline);

        R r = factory.createR();
        r.getContent().add(drawing);

        P p = factory.createP();
        p.getContent().add(r);

        return p;
    }

    /**
     * 创建页眉
     *
     * @param word
     * @return
     * @throws Exception
     */
    public static HeaderPart createHeader(
            WordprocessingMLPackage word) throws Exception {
        HeaderPart headerPart = new HeaderPart();
        Relationship rel = word.getMainDocumentPart().addTargetPart(headerPart);
        createHeaderReference(word, rel);
        return headerPart;
    }

    /**
     * 创建页眉引用关系
     *
     * @param word
     * @param relationship
     * @throws InvalidFormatException
     */
    public static void createHeaderReference(
            WordprocessingMLPackage word,
            Relationship relationship )
            throws InvalidFormatException {
        List<SectionWrapper> sections = word.getDocumentModel().getSections();

        SectPr sectPr = sections.get(sections.size() - 1).getSectPr();
        // There is always a section wrapper, but it might not contain a sectPr
        if (sectPr==null ) {
            sectPr = factory.createSectPr();
            word.getMainDocumentPart().addObject(sectPr);
            sections.get(sections.size() - 1).setSectPr(sectPr);
        }
        HeaderReference headerReference = factory.createHeaderReference();
        headerReference.setId(relationship.getId());
        headerReference.setType(HdrFtrRef.DEFAULT);
        sectPr.getEGHdrFtrReferences().add(headerReference);
    }

    /**
     * 创建页脚
     *
     * @param word
     * @return
     * @throws Exception
     */
    public static FooterPart createFooter(WordprocessingMLPackage word) throws Exception {
        FooterPart footerPart = new FooterPart();
        Relationship rel = word.getMainDocumentPart().addTargetPart(footerPart);
        createFooterReference(word, rel);
        return footerPart;
    }

    /**
     * 创建页脚引用关系
     *
     * @param word
     * @param relationship
     * @throws InvalidFormatException
     */
    public static void createFooterReference(
            WordprocessingMLPackage word,
            Relationship relationship )
            throws InvalidFormatException {
        List<SectionWrapper> sections = word.getDocumentModel().getSections();

        SectPr sectPr = sections.get(sections.size() - 1).getSectPr();
        // There is always a section wrapper, but it might not contain a sectPr
        if (sectPr==null ) {
            sectPr = factory.createSectPr();
            word.getMainDocumentPart().addObject(sectPr);
            sections.get(sections.size() - 1).setSectPr(sectPr);
        }
        FooterReference footerReference = factory.createFooterReference();
        footerReference.setId(relationship.getId());
        footerReference.setType(HdrFtrRef.DEFAULT);
        sectPr.getEGHdrFtrReferences().add(footerReference);
    }
}

作者:isea533 发表于2015/11/12 23:38:10 原文链接
阅读:497 评论:0 查看评论
[原]Apache Lucene 5.x版本 示例
2015年11月6日 23:07
Apache Lucene 5.x版本 示例

由于目前网上关于lucene的资料多是4.x或者更早版本的,5.x版本相比有较大的改动,为了方便学习5.x版本,本文对5.x的示例简单修改做个记录。

本文内容源自官方文档,在core/overview-summary.html上。

本文使用的具体版本是5.3.1,针对5.x版本都适用。
简单例子

Apache Lucene 是一个高性能并且功能全面的文本搜索引擎库,这里有一个如何使用Lucene进行索引和查询的简单例子。

public static void main(String[] args) throws IOException, ParseException {
    Analyzer analyzer = new StandardAnalyzer();

    //将索引存储到内存中
    Directory directory = new RAMDirectory();
    //如下想把索引存储到硬盘上,使用下面的代码代替
    //Directory directory = FSDirectory.open(Paths.get("/tmp/testindex"));
    IndexWriterConfig config = new IndexWriterConfig(analyzer);
    IndexWriter iwriter = new IndexWriter(directory, config);

    String[] texts = new String[]{
        "Mybatis分页插件 - 示例",
        "Mybatis 贴吧问答 第一期",
        "Mybatis 示例之 复杂(complex)属性(property)",
        "Mybatis极其(最)简(好)单(用)的一个分页插件",
        "Mybatis 的Log4j日志输出问题 - 以及有关日志的所有问题",
        "Mybatis 示例之 foreach (下)",
        "Mybatis 示例之 foreach (上)",
        "Mybatis 示例之 SelectKey",
        "Mybatis 示例之 Association (2)",
        "Mybatis 示例之 Association"
    };

    for (String text : texts) {
        Document doc = new Document();
        doc.add(new Field("fieldname", text, TextField.TYPE_STORED));
        iwriter.addDocument(doc);
    }
    iwriter.close();

    //读取索引并查询
    DirectoryReader ireader = DirectoryReader.open(directory);
    IndexSearcher isearcher = new IndexSearcher(ireader);
    //解析一个简单的查询
    QueryParser parser = new QueryParser("fieldname", analyzer);
    Query query = parser.parse("foreach");
    ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs;
    //迭代输出结果
    for (int i = 0; i < hits.length; i++) {
        Document hitDoc = isearcher.doc(hits[i].doc);
        System.out.println(hitDoc.get("fieldname"));
    }
    ireader.close();
    directory.close();
}

代码输出结果:

    Mybatis 示例之 foreach (下)
    Mybatis 示例之 foreach (上)

Lucene API 分成了下面几个包
org.apache.lucene.analysis

定义了从Reader转换为TokenStream的抽象AnalyzerAPI,主要就是分词器。提供了一些默认的实现,包含StopAnalyzer和基于文法的StandardAnalyzer。中文分词可以参考 中文分词库 IKAnalyzer。
org.apache.lucene.codecs

提供了一个抽象的编码和解码的倒排索引结构,还提供了一些不同的实现可以应用于不同的程序需求。
org.apache.lucene.document

提供了一个简单的Document类。一个文档只是一组命名的字段,它的值可以是字符串或者Reader的实例。
org.apache.lucene.index

提供了两个主要的类:IndexWriter用于创建和给文档添加索引,IndexReader用于访问索引数据。
org.apache.lucene.search

提供代表查询的数据结构(例如TermQuery用于单独的关键字查询,PhraseQuery用于短句,BooleanQuery用于布尔联合查询)。
IndexSearcher将查询转换为TopDocs。一些QueryParsers提供了从字符串或者xml生成查询结构的功能。
org.apache.lucene.store

定义了一个抽象类来存储持久化数据,Directory这是一个由IndexOutput和IndexInput分别写和读取的指定文件的集合。提供了多个实现,包括FSDirectory,这个实现使用文件系统目录来存储文件。还有RAMDirectory类实现了文件驻留在内存中的数据结构。
org.apache.lucene.util

包含了一些有用的数据结构和工具类,例如FixedBitSet和PriorityQueue。
应用应该按下面的步骤使用Luncene

    通过添加字段(Field)创建文档(Document);

    创建IndexWriter,通过addDocument()方法添加文档(Document);

    调用QueryParser.parser()方法从字符串生成查询对象;

    创建IndexSearcher并通过search()方法进行查询。

最后

以上内容是Luncene中最基本的内容,关于上面每个包下面都还有一份详细的文档,本文后续可能会对这些内容做一些简单的介绍,如果大家需要用到Luncene,建议下载官方提供的下载,里面包含完整的文档内容。
作者:isea533 发表于2015/11/6 23:07:00 原文链接
阅读:3591 评论:11 查看评论
[原]获取Jar包版本的简单方法
2015年10月15日 13:02
获取Jar包版本的简单方法
针对Maven管理的Jar包

在Maven打包的项目中,都有如下文件:

META-INF

在pom.properties中包含了jar包的版本号信息:
pom.properties

到这一步就已经出来方法了,读取这个文件的version即可。

以MyBatis为例:

例子代码

对于不同的项目,pom.properties路径不一样,只能针对jar包来获取。
针对jar包名字包含版本号

我们可以通过代码获取jar包的完整路径,然后通过截取字符串来得到版本号。

我们需要使用jar包中包含的其中一个类。

例如针对MyBatis:

String jarPath = SqlSession.class.getProtectionDomain().getCodeSource().getLocation().getFile();
//得到/X:/xxxx/xx/mybatis-3.2.8.jar

之后在对字符串进行处理即可。
作者:isea533 发表于2015/10/15 13:02:46 原文链接
阅读:762 评论:0 查看评论
[原]解决IDEA自动重置LanguageLevel和JavaCompiler版本的问题
2015年9月19日 16:18

使用IDEA时,导入的Maven项目默认的LanguageLevel和JavaCompiler都是1.5,1.5的情况下连最简单的@Override注解都不支持,所以项目可能出现一堆错。

虽然在项目上F4可以修改LanguageLevel,在settings中可以修改JavaCompiler版本,但是一旦Maven项目有变化,发生自动的update时,这里做的修改就都白费了。IDEA会重置这些配置。

经过Google搜索,最后找到解决办法,参考如下地址:

    http://stackoverflow.com/questions/27037657/stop-intellij-idea-to-switch-java-language-level-everytime-the-pom-is-reloaded

解决办法就是在pom.xml中指定maven-compiler-plugin的版本,该版本会同时影响LanguageLevel和JavaCompiler,修改后默认就成了这里设置的版本。

添加下面的配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

这里设置的1.8,根据个人需要修改即可。
作者:isea533 发表于2015/9/19 16:18:36 原文链接
阅读:2263 评论:1 查看评论
[原]Apache Thrift 官方JAVA教程
2015年9月19日 16:06
Apache Thrift 官方JAVA教程
本文只是讲如何按照官方教程跑起来代码,并不会对代码细节进行介绍

配置Apache Thrift环境可以查看:Apache Thrift配置环境

这个环境仅仅用来根据thrift文件生成其他语言的代码,和开发环境无关。

官方教程的代码地址:

    https://git1-us-west.apache.org/repos/asf?p=thrift.git;a=tree;f=tutorial;hb=HEAD

上面地址的文件有两种查看方式,blob和raw,blob适合在线看,raw适合复制保存。

从上面地址将最下面的shared.thrift和tutorial.thrift下载到本地,然后在终端执行:

    thrift -r –gen java tutorial.thrift

执行后会出现一个gen-java文件夹,里面包含两个包,每个包都有几个生成的Java文件,文件结构如下:

gen-java
    ├─shared
    │      SharedService.java
    │      SharedStruct.java
    │
    └─tutorial
            Calculator.java
            InvalidOperation.java
            Operation.java
            tutorialConstants.java
            Work.java

我在写这个例子的时候,使用了Maven,创建了3个独立的项目,项目结构如下:

项目结构

thrift-core是thrift生成的代码,client是客户端,server是服务端,client和server都引用了thrift-core。
thrift-core

将上面生成的gen-java中的代码放在了thrift-core项目中,该项目的pom.xml添加了一个依赖:

<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>1.0.0</version>
</dependency>

如果你看过上一篇Apache Thrift配置环境,你就会知道我这里用的1.0.0版本还没有正式发布,所以本文最后会将所有用到的东西打包提供下载。

加上上面的依赖后,gen-java生成的代码才不会报错。
client和server

client和server代码都来自官方教程,教程地址:

    http://thrift.apache.org/tutorial/java

官方源码地址:

    https://git1-us-west.apache.org/repos/asf?p=thrift.git;a=tree;f=tutorial/java/src;h=d56632301e9f9ff6c4ae5a5fd0f09bbb1c481ad3;hb=HEAD

这里提供官方源码地址,主要是因为有个关键的CalculatorHandler类并没有在官方教程中直接出现。

官方教程中服务器提供了两种方式,一般方式(simple)和TSSL方式(secure)。

simple方式很简单就能运行,但是TSSL方式需要用到安全证书。

你可以看看官方教程源码,其中服务端有下面一行代码:

params.setKeyStore("../../lib/java/test/.keystore", "thrift", null, null);

这里的.keystore是私钥,"thrift"是私钥的口令。

在客户端中:

params.setTrustStore("../../lib/java/test/.truststore", "thrift", "SunX509", "JKS");

这里的.truststore是公钥,"SunX509"是公钥的口令,我们需要通过key-tool工具来生成私钥和公钥。
生成TSSL的私钥和公钥

整个使用key-tool工具的过程如下.

输入下面的命令生成私钥,这里的私钥名字是.keystore,名字可以随便写,但是整个过程都要保持名字一致:

keytool -genkeypair -alias certificatekey -keyalg RSA -validity 365 -keystore .keystore

输入上面的命令后按照提示进行操作。这里需要注意,最后一步的certificatekey的口令最好和上面的口令一致,否则可能会遇到其他问题。

输入下面的命令生成server.cer证书:

keytool -export -alias certificatekey -keystore .keystore -rfc -file server.cer

输入下面的命令生成.truststore:

keytool -import -alias certificatekey -file server.cer -keystore .truststore

上面的过程仅仅是为了生成我们需要的.keystore和.truststore,更多的细节可以自己查询。

生成后将这两个文件分别放到server和client中,修改路径和密码,做好这些之后,程序就能启动了。
测试

启动JavaServer后:

    Starting the simple server…
    Starting the secure server…

运行JavaClient后:

    ping()
    1+1=2
    Invalid operation: Cannot divide by 0
    15-10=5
    Check log: 5

此时JavaServer:

    ping()
    add(1,1)
    calculate(1, {DIVIDE,1,0})
    calculate(1, {SUBTRACT,15,10})
    getStruct(1)

源码下载

为了方便大家了解上面的过程和对比官方文档进行学习,本文提供源码下载。

http://pan.baidu.com/s/1qW3P320

源码使用方式,首先你要会Maven,而且还要配置好mvn,将下载的压缩文件解压后,在解压的目录中打开命令行,使用如下命令安装1.0.0版本的thrift:

    mvn install:install-file -Dfile=libthrift-1.0.0.jar -DpomFile=libthrift-1.0.0.pom

安装完成后,导入3个thrift-xxx项目,先运行thrift-server,在运行thrift-client即可。
0 0
原创粉丝点击