官网:http://www.springframework.org/ldap

官方文档及例子(重要):http://docs.spring.io/spring-ldap/docs/2.1.0.RELEASE/reference/

JAVA文档(重要):http://docs.spring.io/spring-ldap/docs/2.1.0.RELEASE/apidocs/

GitHub(大量例子):https://github.com/spring-projects/spring-ldap

Spring LDAP Reference

2.基本使用

2.1 使用AttributesMapper进行search和lookup

(1)通过search返回一个属性值

 import static org.springframework.ldap.query.LdapQueryBuilder.query;

 public class PersonRepoImpl implements PersonRepo{
private LdapTemplate ldapTemplate;

public void setLdapTemplate(LdapTemplate ldapTemplate){
this.ldapTemplate = ldapTemplate;
} public List<String> getAllPersonNames(){
return ldapTemplate.search({
query().where("objectclass").is("person"),
new AttributeMapper<String>(){
public String mapFromAttributes(Attribute attrs)throws NamingException{
return (String) attrs.get("cn").get();
}
}
}
});
}
}

(2)通过search返回一个Person对象

 package com.example.repo;
import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
private class PersonAttributesMapper implements AttributesMapper<Person> {
public Person mapFromAttributes(Attributes attrs) throws NamingException {
Person person = new Person();
person.setFullName((String)attrs.get("cn").get());
person.setLastName((String)attrs.get("sn").get());
person.setDescription((String)attrs.get("description").get());
return person;
}
} public List<Person> getAllPersons() {
return ldapTemplate.search(query()
.where("objectclass").is("person"), new PersonAttributesMapper());
}
}

(3)通过lookup返回一个Person对象

在ldap中,有两个"查询"概念,search和lookup。search是ldaptemplate对每一个entry进行查询,lookup是通过DN直接找到某个条目。

"Entries in LDAP are uniquely identified by their distinguished name (DN). If you have the DN of an entry, you can retrieve(找回) the entry directly without searching for it. This is called a lookup in Java LDAP."

在下面的lookup代码中,ldap会跳过为AttributesMapper查找属性。

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
public Person findPerson(String dn) {
return ldapTemplate.lookup(dn, new PersonAttributesMapper());
}
}

2.2 创建LDAP Queries

ldap的search 包含许多参数,比如:

 Base LDAP path 基本路径(search应该从LDAP树的哪里开始)
Search scope 查询范围(search应该进行到LDAP树的哪一层)
returned attributes要返回的属性
Search filter 查询过滤器
spring-ldap为我们提供了LdapQueryBuilder来创建LDAP Queries。
假设现在需要执行一个查询:
base DN为"dc=261consulting,dc=com",返回的属性为"cn"和"sn",filter为"(&(objectclass=person)(sn=?))",此处? 用lastName的值。
为了简化复杂的search参数,LdapQueryBuilder及其相关类,也提供了避开某些字符的搜索。AttributesMapper也只是其中一个可用的callback interfaces。更多的使用方法可以查看ldapTemplate的方法。

 package com.example.repo;
import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
public List<String> getPersonNamesByLastName(String lastName) { LdapQuery query = query()
            .base("dc=261consulting,dc=com")
            .attributes("cn", "sn") //返回的属性
            .where("objectclass").is("person")
            .and("sn").is(lastName); return ldapTemplate.search(query, new AttributesMapper<String>() {
public String mapFromAttributes(Attributes attrs)throws NamingException {
return attrs.get("cn").get(); //查询每一个entry的"cn"值
}
});
}
}

2.3 动态创建 Distinguished Names(DN)

为了简化对DN的使用,spring-ldap提供了LdapNameBuilder,和工具类LdapUtils

假设一个Person有如下的属性:

 Attribute Name                 Attribute Value
country Sweden
company Some Company
fullname Some Person

(1)使用 LdapNameBuilder 动态创建 LdapName

 package com.example.repo;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.Name; public class PersonRepoImpl implements PersonRepo {
public static final String BASE_DN = "dc=example,dc=com"; protected Name buildDn(Person p) {
return LdapNameBuilder.newInstance(BASE_DN)
.add("c", p.getCountry())
.add("ou", p.getCompany())
.add("cn", p.getFullname())
.build();
}
...

(2)用 LdapUtils 获取属性值

 package com.example.repo;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.Name;
public class PersonRepoImpl implements PersonRepo {
...
protected Person buildPerson(Name dn, Attributes attrs) {
Person person = new Person();
person.setCountry(LdapUtils.getStringValue(dn, "c"));
person.setCompany(LdapUtils.getStringValue(dn, "ou"));
person.setFullname(LdapUtils.getStringValue(dn, "cn"));
// Populate rest of person object using attributes. return person;
}

2.4 绑定和解绑

在Ldap中,新增与删除叫做绑定和解绑。

2.4.1 新增数据

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
public void create(Person p) {
Name dn = buildDn(p);
ldapTemplate.bind(dn, null, buildAttributes(p));
} private Attributes buildAttributes(Person p) {
Attributes attrs = new BasicAttributes();
BasicAttribute ocattr = new BasicAttribute("objectclass");
ocattr.add("top");
ocattr.add("person");
attrs.put(ocattr);
attrs.put("cn", "Some Person");
attrs.put("sn", "Person");
return attrs;
}
}

2.4.2 删除数据

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
public void delete(Person p) {
Name dn = buildDn(p);
ldapTemplate.unbind(dn);
}
}

2.4.3 更新数据

(1)使用 rebind 更新数据

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
public void update(Person p) {
Name dn = buildDn(p);
ldapTemplate.rebind(dn, null, buildAttributes(p));
}
}

(2)使用 modifyAttributes 更新数据

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate;
...
public void updateDescription(Person p) {
Name dn = buildDn(p);
Attribute attr = new BasicAttribute("description", p.getDescription())
ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item});
}
}

3.简化 Attribute 的获取和 DirContextAdapter 的操作

3.1 介绍

Java LDAP API 可以注册一个DirContextAdapter来自动创建对象。spring-ldap使用了这个特点,在search和lookup中返回DirContextAdapter实例。

3.2 通过ContextMapper来search 和lookup

任何时候,想要在LDAP数据树中查找entry,spring-ldap都会使用这个entry的DN和Attributes来构建一个DirContextAdapter,这使得我们不再需要使用 AttributesMapper,而是使用ContextMapper来对获取的属性值进行转换

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
...
private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
return p;
}
} public Person findByPrimaryKey(String name, String company, String country) {
Name dn = buildDn(name, company, country);
return ldapTemplate.lookup(dn, new PersonContextMapper());
}
}

这里特别方便的一点是:当属性具有多值时,可以通过getStringAttributes()来获取。

 private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
// The roleNames property of Person is an String array
p.setRoleNames(context.getStringAttributes("roleNames"));
return p;
}
}

3.2.1 AbstactContextMapper

spring-ldap提供了一个ContextMapper的抽象的基础实现类:AbstractContextMapper。自定义的的PersonContextMapper可以这样写:

 private static class PersonContextMapper extends AbstractContextMapper {
public Object doMapFromContext(DirContextOperations ctx) { //ctx没有用到??
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
return p;
}
}

3.3 使用DirContextAdapter新增和更新数据

注意新增的时候用的是:DirContextAdapter。更新的时候用的是:DirContextOperations。二者的关系:DirContextAdapter实现了DirContextOperations接口。

3.3.1 新增数据

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
...
public void create(Person p) {
Name dn = buildDn(p);
DirContextAdapter context = new DirContextAdapter(dn);
//和获取一样,set也可以有多值
context.setAttributeValues("objectclass", new String[] {"top", "person"});
context.setAttributeValue("cn", p.getFullname());
context.setAttributeValue("sn", p.getLastname());
context.setAttributeValue("description", p.getDescription()); ldapTemplate.bind(context);
}
}

3.3.2 更新数据

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
...
public void update(Person p) {
Name dn = buildDn(p);
DirContextOperations context = ldapTemplate.lookupContext(dn); context.setAttributeValue("cn", p.getFullname());
context.setAttributeValue("sn", p.getLastname());
context.setAttributeValue("description", p.getDescription()); ldapTemplate.modifyAttributes(context);
}
}

3.3.3 合并新增和更新数据的代码

从前面两段代码可知,新增和更新有重复的代码,因此合并重复代码,整理如下:

 package com.example.repo;

 public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate; ...
public void create(Person p) {
Name dn = buildDn(p);
DirContextAdapter context = new DirContextAdapter(dn);
context.setAttributeValues("objectclass", new String[] {"top", "person"});
mapToContext(p, context);
ldapTemplate.bind(context);
} public void update(Person p) {
Name dn = buildDn(p);
DirContextOperations context = ldapTemplate.lookupContext(dn);
mapToContext(person, context);
ldapTemplate.modifyAttributes(context);
} protected void mapToContext (Person p, DirContextOperations context) {
context.setAttributeValue("cn", p.getFullName());
context.setAttributeValue("sn", p.getLastName());
context.setAttributeValue("description", p.getDescription());
}
}

3.4 DirContextAdapter和作为属性值的DN

When managing security groups in LDAP it is very common to have attribute values that represent distinguished names. Since distinguished name equality differs from String equality (例如,空格和大小写在DN的判等中是无视的), calculating attribute modifications using string equality will not work as expected.

假设一个member属性值为:cn=John Doe,ou=People。如果代码写作如下,会被认为是两个值,实际上它代表了同一个DN。

 ctx.addAttributeValue("member", "CN=John Doe, OU=People")

要写作如下:

 ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))

使用DirContextAdapter来修改group membership

 public class GroupRepo implements BaseLdapNameAware {
private LdapTemplate ldapTemplate;
private LdapName baseLdapPath; public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
} public void setBaseLdapPath(LdapName baseLdapPath) {
this.setBaseLdapPath(baseLdapPath);
} public void addMemberToGroup(String groupName, Person p) {
Name groupDn = buildGroupDn(groupName);
Name userDn = buildPersonDn(person.getFullname(),person.getCompany(), person.getCountry()); DirContextOperation ctx = ldapTemplate.lookupContext(groupDn);
ctx.addAttributeValue("member", userDn); ldapTemplate.update(ctx);
} public void removeMemberFromGroup(String groupName, Person p) {
Name groupDn = buildGroupDn(String groupName);
Name userDn = buildPersonDn(person.getFullname(),person.getCompany(),person.getCountry()); DirContextOperation ctx = ldapTemplate.lookupContext(groupDn);
ctx.removeAttributeValue("member", userDn); ldapTemplate.update(ctx);
} private Name buildGroupDn(String groupName) {
return LdapNameBuilder.newInstance("ou=Groups").add("cn", groupName).build();
} private Name buildPersonDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance(baseLdapPath).add("c", country).add("ou", company).add("cn", fullname).build();
}
}

3.5 使用spring-ldap和DirContextAdapter的完整代码

 package com.example.repo;
import java.util.List; import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName; import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter; import static org.springframework.ldap.query.LdapQueryBuilder.query; public class PersonRepoImpl implements PersonRepo {
private LdapTemplate ldapTemplate; public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
} public void create(Person person) {
DirContextAdapter context = new DirContextAdapter(buildDn(person));
mapToContext(person, context);
ldapTemplate.bind(context);
} public void update(Person person) {
Name dn = buildDn(person);
DirContextOperations context = ldapTemplate.lookupContext(dn);
mapToContext(person, context);
ldapTemplate.modifyAttributes(context);
} public void delete(Person person) {
ldapTemplate.unbind(buildDn(person));
} public Person findByPrimaryKey(String name, String company, String country) {
Name dn = buildDn(name, company, country);
return ldapTemplate.lookup(dn, getContextMapper());
} public List findByName(String name) {
LdapQuery query = query()
.where("objectclass").is("person")
.and("cn").whitespaceWildcardsLike("name"); return ldapTemplate.search(query, getContextMapper());
} public List findAll() {
EqualsFilter filter = new EqualsFilter("objectclass", "person");
return ldapTemplate.search(LdapUtils.emptyPath(), filter.encode(), getContextMapper());
} protected ContextMapper getContextMapper() {
return new PersonContextMapper();
} protected Name buildDn(Person person) {
return buildDn(person.getFullname(), person.getCompany(), person.getCountry());
} protected Name buildDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance()
.add("c", country)
.add("ou", company)
.add("cn", fullname)
.build();
} protected void mapToContext(Person person, DirContextOperations context) {
context.setAttributeValues("objectclass", new String[] {"top", "person"});
context.setAttributeValue("cn", person.getFullName());
context.setAttributeValue("sn", person.getLastName());
context.setAttributeValue("description", person.getDescription());
} private static class PersonContextMapper extends AbstractContextMapper<Person> {
public Person doMapFromContext(DirContextOperations context) {
Person person = new Person();
person.setFullName(context.getStringAttribute("cn"));
person.setLastName(context.getStringAttribute("sn"));
person.setDescription(context.getStringAttribute("description"));
return person;
}
}
}

PersonRepoImpl

4. ODM(Object-Directory Mapping)

4.1 介绍

对象-关系映射框架比如Hibernate和JPA,都可以使用注解来将数据库的表关系映射成java的对象。spring-ldap也提供类似的功能。

LdapPerations里有这些方法:

     <T> T findByDn(Name dn, Class<T> clazz)
<T> T findOne(LdapQuery query, Class<T> clazz)
<T> List<T> find(LdapQuery query, Class<T> clazz)
<T> List<T> findAll(Class<T> clazz)
<T> List<T> findAll(Name base, SearchControls searchControls, Class<T> clazz)
<T> List<T> findAll(Name base, Filter filter, SearchControls searchControls, Class<T> clazz) void create(Object entry)
void update(Object entry)
void delete(Object entry)

4.2 注解

每个注解的含义暂时先不详细解释。//todo

 @Entry (required)
@Id (required)
@Attribute
@DnAttribute
@Transient

4.3 执行

 @Entry(objectClasses = { "person", "top" }, base="ou=someOu")
public class Person {
@Id
private Name dn; @Attribute(name="cn")
@DnAttribute(value="cn", index=)
private String fullName; // No @Attribute annotation means this will be bound to the LDAP attribute with the same value
private String description; @DnAttribute(value="ou", index=0)
@Transient
private String company; @Transient
private String someUnmappedField;
// ...more attributes below
}
24 public class OdmPersonRepo {
25 @Autowired
26 private LdapTemplate ldapTemplate;
27
28 public Person create(Person person) {
29 ldapTemplate.create(person);
30 return person;
31 }
32
33 public Person findByUid(String uid) {
34 return ldapTemplate.findOne(query().where("uid").is(uid), Person.class);
35 }
36
37 public void update(Person person) {
38 ldapTemplate.update(person);
39 }
40
41 public void delete(Person person) {
42 ldapTemplate.delete(person);
43 }
44
45 public List<Person> findAll() {
46 return ldapTemplate.findAll(Person.class);
47 }
48
49 public List<Person> findByLastName(String lastName) {
50 return ldapTemplate.find(query().where("sn").is(lastName), Person.class);
51 }
52 }

4.4 ODM和作为属性值的DN

ldap中的安全组通常包含多值属性,每一个属性值都是一个user的DN。这些属性值的处理在前面的 3.4 DirContextAdapter和作为属性值的DN 中提到过。ODM同样有简单的处理办法。

 @Entry(objectClasses = {"top", "groupOfUniqueNames"}, base = "cn=groups")
public class Group { @Id
private Name dn; @Attribute(name="cn")
@DnAttribute("cn")
private String name; @Attribute(name="uniqueMember")
private Set<Name> members;

public Name getDn() {return dn;}
public void setDn(Name dn) {this.dn = dn;} public Set<Name> getMembers() {return members;}
public void setMembers(Set<Name> members) {this.members = members;} public String getName() { return name;}
public void setName(String name) {this.name = name;} public void addMember(Name member) {members.add(member);}
public void removeMember(Name member) {members.remove(member);}
}

 5.高级LDAP Queries

//todo 暂略。

6.配置

6.1 介绍

采用xml文件方式配置时,需要加入如下xml的命名空间。

 <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ldap="http://www.springframework.org/schema/ldap"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/ldap
http://www.springframework.org/schema/ldap/spring-ldap.xsd">

 6.2 ContextSource 配置

最简单的配置至少需要:username,password,url。完整的属性参看官方文档的说明。

 <ldap:context-source
username="cn=Administrator"
password="secret"
url="ldap://localhost:389" />

6.2.1 DirContextAuthentication

创建并使用DirContext实例时,通常需要认证这些contexts,有不同的配置方式可供选择。

在这一节里(12. User Authentication using Spring LDAP)讨论了使用spring ldap进行用户验证。

authenticated contexts可以默认创建为read-only和read-write两种。在context-source的配置中指定username和password。如果username是一个LDAP 用户的dn,那么不管在context-source中是否配置了base LDAP path,这个user的DN也必须是username。(没看懂)

有些LDAP服务器支持匿名read-only访问。如果需要支持这个功能,将属性anonymous-read-only设置为true即可。

(1)自定义DirContext的认证过程

spring ldap中的默认认证策略是简单的。它将principal(这里是username)和credentials(这里是password)放在哈希表里传递给DirContext的构造函数。这通常是不够的。因此可以在context-source里配置 authentication-strategy-ref,来指定自定义策略。

(2)使用SpringSecurityAuthenticationSource我们前面的配置,直接将username和password写死了。但是一个更常见的场景是,当为某个用户执行LDAP操作时,应该使用的是当前用户的principals和credentials,而不是显式指定。这可以通过配置SpringSecurityAuthenticationSource实现动态配置。

 <beans>
...
<ldap:context-source
url="ldap://localhost:389"
authentication-source-ref="springSecurityAuthenticationSource/> <bean id="springSecurityAuthenticationSource"
class="org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource" />
...
</beans>

6.2.2 本地java LDAP Pooling//todo

6.2.3 高级ContextSource配置//todo

6.3 LdapTemplate Configuration

 <ldap:ldap-template />

可供配置的属性有:

属性  默认值 说明
id ldapTemplate  
context-source-ref contextSource 使用的contextSource
count-limit 0 search的默认个数限制,0指的是没有限制。
time-limit 0 search的默认时间限制,0指的是没有限制。
search-scope SUBTREE 可选值有:OBJECT,ONELEVEL,SUBTREE
ignore-name-not-found false 指明search时异常NameNotFoundException是否被忽视。
ignore-partial-result false 指明search时异常PartialResultException是否被忽视。
odm-ref   使用的ObjectDirectoryMapper的bean id。默认是DefaultObjectDirectoryMapper
 

6.4 获取base LDAP path的值

通常情况下,在contextSource里定义了base LDAP path,然后操作时都是用的相对路径。但也有某些情况下,需要用到base LDAP path的完整值。比如,操作LDAP groups时,在这里group member的属性值必须是member的完整DN

step1:实现接口BaseLdapNameAware

 package com.example.service;
public class PersonService implements PersonService, BaseLdapNameAware {
...
private LdapName basePath; public void setBaseLdapPath(LdapName basePath) {
this.basePath = basePath;
}
...
private LdapName getFullPersonDn(Person person) {
return LdapNameBuilder.newInstance(basePath)
.add(person.getDn())
.build();
}
...
}

step2:配置BaseLdapPathBeanPostProcessor

 <beans>
...
<ldap:context-source
username="cn=Administrator"
password="secret"
url="ldap://localhost:389"
base="dc=261consulting,dc=com" />
...
<bean class="org.springframework.ldap.core.support.BaseLdapPathBeanPostProcessor" />
</beans>
 
 

2017.4.10 spring-ldap官方文档学习的更多相关文章

  1. Spring 4 官方文档学习(十二)View技术

    关键词:view technology.template.template engine.markup.内容较多,按需查用即可. 介绍 Thymeleaf Groovy Markup Template ...

  2. Spring 4 官方文档学习(十一)Web MVC 框架之配置Spring MVC

    内容列表: 启用MVC Java config 或 MVC XML namespace 修改已提供的配置 类型转换和格式化 校验 拦截器 内容协商 View Controllers View Reso ...

  3. Spring Boot 官方文档学习(一)入门及使用

    个人说明:本文内容都是从为知笔记上复制过来的,样式难免走样,以后再修改吧.另外,本文可以看作官方文档的选择性的翻译(大部分),以及个人使用经验及问题. 其他说明:如果对Spring Boot没有概念, ...

  4. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)

    题外话:本篇是对之前那篇的重排版.并拆分成两篇,免得没了看的兴趣. 前言 在Spring Framework官方文档中,这三者是放到一起讲的,但没有解释为什么放到一起.大概是默认了读者都是有相关经验的 ...

  5. Spring boot官方文档学习(一)

    个人说明:本文内容都是从为知笔记上复制过来的,样式难免走样,以后再修改吧.另外,本文可以看作官方文档的选择性的翻译(大部分),以及个人使用经验及问题. 其他说明:如果对Spring Boot没有概念, ...

  6. Spring 4 官方文档学习(十一)Web MVC 框架之resolving views 解析视图

    接前面的Spring 4 官方文档学习(十一)Web MVC 框架,那篇太长,故另起一篇. 针对web应用的所有的MVC框架,都会提供一种呈现views的方式.Spring提供了view resolv ...

  7. Spring 4 官方文档学习(十一)Web MVC 框架

    介绍Spring Web MVC 框架 Spring Web MVC的特性 其他MVC实现的可插拔性 DispatcherServlet 在WebApplicationContext中的特殊的bean ...

  8. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(二)

    接前一篇 Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 本篇主要内容:Spring Type Conver ...

  9. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion

    本篇太乱,请移步: Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 写了删删了写,反复几次,对自己的描述很不 ...

  10. Spring 4 官方文档学习(十四)WebSocket支持

    个人提示:如果需要用到页面推送,高频且要低延迟,WebSocket无疑是最佳选择.否则还是轮询和long polling吧. 做了一个小demo放在码云上,有兴趣的可以看一下,简单易懂:websock ...

随机推荐

  1. Codeforces 932.A Palindromic Supersequence

    A. Palindromic Supersequence time limit per test 2 seconds memory limit per test 256 megabytes input ...

  2. 【Error】Python:UnicodeDecodeError: ‘XXX' codec can't decode bytes in position... 解决方法

    错误信息: UnicodeDecodeError: ‘XXX' codec can't decode bytes in position 2-5: illegal multibyte sequence ...

  3. [03]使用阿里RAP搭建前端Mock Server

    MockServer可以减少前端开发对后端的依赖,提高前端开发的效率,同时也利于团队的协作. 什么是RAP? RAP是阿里团队出的一款WEB接口管理工具,帮助开发人员更高效的管理接口文档,同时通过分析 ...

  4. IEjs 调试、火狐 js 调试

    http://www.jb51.net/article/26707.htm IE下调试代码, 在代码中写 debugger; 然后IE启用调试, 会执行到debugger 断点出, 双击变量 右键 添 ...

  5. Linux中权限(r、w、x)对于目录与文件的意义

    Linux中权限(r.w.x)对于目录与文件的意义 一.权限对于目录的意义 1.首先要明白的是目录主要的内容是记录文件名列表和子目录列表,而不是实际存放数据的地方. 2.r权限:拥有此权限表示可以读取 ...

  6. ie下,jquery为动态添加的节点添加事件,用live

    jQuery向动态生成的内容添加事件响应 jQuery live() 方法详解 [收藏] 发布时间:2013-07-24 点击次数:176 来源:www.daimajiayuan.com jQuery ...

  7. Centos 7 ssh登录速度慢

    在server上/etc/hosts文件中把你本机的ip和hostname加入 hostname ifconifg 在server上/etc/ssh/sshd_config文件中修改或加入UseDNS ...

  8. git代码仓库迁移(从github到oschina)【转】

    转自:http://blog.csdn.net/a5244491/article/details/44807937 版权声明:本文为博主原创文章,未经博主允许不得转载. 因为一些特殊原因,需要将公司原 ...

  9. 华为上机测试题(数字字符串转二进制-java)

    PS:此题满分,可参考 /*  * 题目:数字字符串转二进制 * 描述: 输入一串整数,将每个整数转换为二进制数,如果倒数第三个Bit是“0”,则输出“0”,如果是“1”,则输出“1”. 题目类别: ...

  10. Appium+python自动化17-启动iOS模拟器APP源码案例【转载】

    前言 上一篇已经可以启动iOS模拟器上的safari浏览器了,启动app比启动浏览器要复杂一点,本篇以github上的源码为案例详细介绍如何启动iOS模拟器的app 一.clone源码 1.githu ...