Java Spring项目国际化(i18n)详细方法与实例


Posted in Python onMarch 20, 2020

Spring国际化概述

国际化基本规则

国际化信息”也称为“本地化信息”,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。Java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。

语言参数使用ISO标准语言代码表示,这些代码是由ISO-639标准定义的,每一种语言由两个小写字母表示。在许多网站上都可以找到这些代码的完整列表,下面的网址是提供了标准语言代码的信息:http://www.loc.gov/standards/iso639-2/php/English_list.php。

国家/地区参数也由标准的ISO国家/地区代码表示,这些代码是由ISO-3166标准定义的,每个国家/地区由两个大写字母表示。用户可以从以下网址查看ISO-3166的标准代码:http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html,部分语言和国家/地区的标准代码如下所示:

语言 简称
简体中文(中国) zh_CN
繁体中文(中国台湾) zh_TW
繁体中文(中国香港) zh_HK
英语(中国香港) en_HK
英语(美国) en_US
英语(英国) en_GB
英语(全球) en_WW
英语(加拿大) en_CA
英语(澳大利亚) en_AU
英语(爱尔兰) en_IE
英语(芬兰) en_FI
芬兰语(芬兰) fi_FI
英语(丹麦) en_DK
丹麦语(丹麦) da_DK
英语(以色列) en_IL
希伯来语(以色列) he_IL
英语(南非) en_ZA
英语(印度) en_IN
英语(挪威) en_NO
英语(新加坡) en_SG
英语(新西兰) en_NZ
英语(印度尼西亚) en_ID
英语(菲律宾) en_PH
英语(泰国) en_TH
英语(马来西亚) en_MY
英语(阿拉伯) en_XA
韩文(韩国) ko_KR
日语(日本) ja_JP
荷兰语(荷兰) nl_NL
荷兰语(比利时) nl_BE
葡萄牙语(葡萄牙) pt_PT
葡萄牙语(巴西) pt_BR
法语(法国) fr_FR
法语(卢森堡) fr_LU
法语(瑞士) fr_CH
法语(比利时) fr_BE
法语(加拿大) fr_CA
西班牙语(拉丁美洲) es_LA
西班牙语(西班牙) es_ES
西班牙语(阿根廷) es_AR
西班牙语(美国) es_US
西班牙语(墨西哥) es_MX
西班牙语(哥伦比亚) es_CO
西班牙语(波多黎各) es_PR
德语(德国) de_DE
德语(奥地利) de_AT
德语(瑞士) de_CH
俄语(俄罗斯) ru_RU
意大利语(意大利) it_IT
希腊语(希腊) el_GR
挪威语(挪威) no_NO
匈牙利语(匈牙利) hu_HU
土耳其语(土耳其) tr_TR
捷克语(捷克共和国) cs_CZ
斯洛文尼亚语 sl_SL
波兰语(波兰) pl_PL
瑞典语(瑞典) sv_SE
西班牙语(智利) es_CL

语言类型判断

1)基于浏览器语言

根据Request Headers中的Accept-language来判断。

2)基于客户端传参

要求客户端第一次(或者每次)传递的自定义参数值来判断,如规定传locale,值为zh-cn、en-us等内容,如果只在第一次传入则local以及timeZone先关信息要存入session或者cookie中,后面的请求语言方式则直接从两者中取,其有效时间与session和cookie设置的生命周期关联。

3)基于默认配置

当获取语言类型时没有找到对应类型时,会使用默认的语言类型。

语言类型保存

<!-- 定义本地化变更拦截器 -->
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
<!-- 定义注解URL映射处理器,所有的请求映射关联本地化拦截器,或者也可自定义该拦截器路径映射-->
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
 <property name="interceptors" ref=" localeChangeInterceptor " />
 <property name="order" value="1"></property>
</bean>

基于url

该种方式需要每次都在请求的url上带上local参数,指定该次需要的语言类型,并且该方式的local解析器需要配置,如下:

<a href="xxx.do?locale=zh_CN" rel="external nofollow" >中文</a>或<a href="xxx.do?locale=en" rel="external nofollow" >英文</a>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"/>

但在该配置下使用会抛Cannot change HTTP accept header - use a different locale resolution strategy异常,这是因为spring source做了限制,无法对本地的local赋值修改,解决办法如下,新建一个类MyLocaleResolver继承AcceptHeaderLocaleResolver,重写resolveLocale和setLocale方法,并将上面的localeResolver的class指向如下MyLocaleResolver类:

public class MyLocaleResolver extends AcceptHeaderLocaleResolver {
 private Locale myLocal;
 
public Locale resolveLocale(HttpServletRequest request) {
 return myLocal == null ? request.getLocale() : myLocal;
}
 
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
 myLocal = locale;
 }
}

基于session

基于session的状态保存方式只需要在第一次请求的时候指定语言类型,localResolver会将该属性保存到session中,后面的请求直接从session中获取该语言类型,该种方式的localResolver对应的类为SessionLocaleResolver,如下配置:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>

基于cookie

与session的机制类似,差异在于两者的存储和周期,鉴于安全、大小以及体验等因素的影响,实际使用中使用者更倾向于前者,该种cookie保存方式的localResolver为

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />

文案数据来源

对于语言类型的资源文件,需要开发者对文案进行搜集整理,并翻译成相应的语言确定关键字key,目前大多数情况是将这些信息置于.properties文件中,在使用的时候直接访问获取,当然也可置于数据库中,但频繁的文案获取会影响服务器性能及产品体验,可结合数据字典以及缓存工具使用。

数据库

1)spring 配置方式

<!-- 默认的注解映射的支持 -->
<mvc:annotation-driven validator="validator" conversion-service="conversionService" />
<!-- 资源文件 -->
<bean id="propertiesMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basenames">
 <list>
 <value>resource</value>
 <value>validation</value>
 </list>
 </property>
</bean>
 
<bean id="databaseMessageSource" class="com.obs2.util.MessageResource">
 <property name="parentMessageSource" ref="propertiesMessageSource"/>
</bean>
 
<bean id="messageInterpolator" class="com.obs2.util.MessageResourceInterpolator">
 <property name="messageResource" ref="databaseMessageSource"/>
</bean>
 
<!-- 验证器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
 <property name="messageInterpolator" ref="messageInterpolator"/>
</bean>

这里定义了一个propertiesMessageSource,一个databaseMessageSourcer,和一个messageInterpolator。propertiesMessageSource用于读取properties文件databaseMessageSourcer用于读取数据库的数据配置,其中,有一个属性设置它的父MessageSource为propertiesMessageSource。意思是如果数据库找不到对应的数据,到properties文件当中查找。messageInterpolator是个拦截器。

2)数据库的POJO定义

@Entity
@SuppressWarnings("serial")
@Table(name="resource")
 
public class Resource implements Serializable {
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="resource_id")
 private long resourceId;
 
 @Column(name="name", length=50, nullable=false)
 private String name;
 
 @Column(name="text", length=1000, nullable=false)
 private String text;
 
 @Column(name="language", length=5, nullable=false)
 private String language;
 
 public long getResourceId() {
 return resourceId;
 }
 
 public void setResourceId(long resourceId) {
 this.resourceId = resourceId;
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public String getText() {
 return text;
 }
 
 public void setText(String text) {
 this.text = text;
}
 
 public String getLanguage() {
 return language;
 }
 
 public void setLanguage(String language) {
 this.language = language;
 }
}

定义了一张表[resource],字段有:[resource_id]、[name]、[text]、[language]。

3)读取数据库的MessageResource类

/**
* 取得资源数据
* @author Robin
*/
public class MessageResource extends AbstractMessageSource implements ResourceLoaderAware {
 
 @SuppressWarnings("unused")
 private ResourceLoader resourceLoader;
 
 @Resource
 private ResourceService resourceService;
 /**
 * Map切分字符
 */
 protected final String MAP_SPLIT_CODE = "|";
 protected final String DB_SPLIT_CODE = "_";
 
 private final Map<String, String> properties = new HashMap<String, String>();
 
 public MessageResource() {
 reload();
 }
 
 public void reload() {
 properties.clear();
 properties.putAll(loadTexts());
 }
 
 protected Map<String, String> loadTexts() {
 Map<String, String> mapResource = new HashMap<String, String>();
 List<com.obs2.service.bean.Resource> resources = resourceService.findAll();
 for (com.obs2.service.bean.Resource item : resources) {
 String code = item.getName() + MAP_SPLIT_CODE + item.getLanguage();
 mapResource.put(code, item.getText());
 }
 
 return mapResource;
 }
 
 private String getText(String code, Locale locale) {
 String localeCode = locale.getLanguage() + DB_SPLIT_CODE + locale.getCountry();
 String key = code + MAP_SPLIT_CODE + localeCode;
 String localeText = properties.get(key);
 String resourceText = code;
 if(localeText != null) {
  resourceText = localeText;
 }else {
  localeCode = Locale.ENGLISH.getLanguage();
  key = code + MAP_SPLIT_CODE + localeCode;
  localeText = properties.get(key);
  if(localeText != null) {
  resourceText = localeText;
  }else {
  try {
   if(getParentMessageSource() != null) {
   resourceText = getParentMessageSource().getMessage(code, null, locale);
   }
  } catch (Exception e) {
   logger.error("Cannot find message with code: " + code);
  }
  }
 }
 return resourceText;
 }
 
 @Override
 public void setResourceLoader(ResourceLoader resourceLoader) {
 this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
 }
 
 @Override
 protected MessageFormat resolveCode(String code, Locale locale) {
 String msg = getText(code, locale);
 MessageFormat result = createMessageFormat(msg, locale);
 return result;
 }
 
 @Override
 protected String resolveCodeWithoutArguments(String code, Locale locale) {
 String result = getText(code, locale);
 return result;
 }
}

主要是重载AbstractMessageSource和ResourceLoaderAware,以实现Spring MVC的MessageSource国际化调用。类中的reload()方法,我把它写到了一个ServletListener当中,让项目启动时,自动加载数据到static的map中。

4)Listener

/**
* 系统启动监听
* @author Robin
*/
public class SystemListener implements ServletContextListener {
/**
* context初始化时激发
*/
@Override
public void contextInitialized(ServletContextEvent e) {
// 取得ServletContext
ServletContext context = e.getServletContext();
WebApplicationContext applicationContext = WebApplicationContextUtils .getWebApplicationContext(context);
// 设置国际化多语言
MessageResource messageSource = applicationContext.getBean(MessageResource.class);
 messageSource.reload();
}
 
/**
* context删除时激发
*/
@Override
public void contextDestroyed(ServletContextEvent e) {
}
 
/**
* 创建一个 session时激发
* @param e
*/
 
public void sessionCreated(HttpSessionEvent e) {
}
 
/**
* 当一个 session失效时激发
* @param e
*
public void sessionDestroyed(HttpSessionEvent e) {
}
/**
* 设置 context的属性,它将激发attributeReplaced或attributeAdded方法
* @param e
*/
public void setContext(HttpSessionEvent e) {
}
 
/**
* 增加一个新的属性时激发
* @param e
*/
public void attributeAdded(ServletContextAttributeEvent e) {
}
 
/**
*删除一个新的属性时激发
* @param e
*/
 
public void attributeRemoved(ServletContextAttributeEvent e) {
 
}
 
/*
* 属性被替代时激发
* @param e
*/
public void attributeReplaced(ServletContextAttributeEvent e) {
}
}

该Listener需要加入到web.xml当中:

<!-- 系统启动监听 -->
<listener>
 <listener-class>com.obs2.util.SystemListener</listener-class>
</listener>

5)Interceptor拦截器

/**
* 拦截Annotation验证信息
* @author Robin
*
*/
 
public class MessageResourceInterpolator implements MessageInterpolator {
@Resource
private MessageResource messageResource;
 
public void setMessageResource(MessageResource messageResource) {
 this.messageResource = messageResource;
}
 
@Override
public String interpolate(String messageTemplate, Context context) {
 
String messageTemp = null;
 if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {
 messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1);
 }else {
 return messageTemplate;
 }
 
 String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");
 MessageBuilder builder = new MessageBuilder().code(messageTemp);
 
 if (params != null) {
 for (String param : params) {
 builder = builder.arg(param);
 }
 
 }
 
 String result = builder.build().resolveMessage(messageResource, Locale.ENGLISH).getText();
 return result;
 
 }
 
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
 
 String messageTemp = null;
 if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {
 messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1);
 }else {
 return messageTemplate;
 }
 
 String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");
 
 MessageBuilder builder = new MessageBuilder().code(messageTemp);
 if (params != null) {
 builder = builder.args(params);
 }
 
 String result = builder.build().resolveMessage(messageResource, locale).getText();
 
 return result
 }
 
}

静态资源

<!-- 资源文件绑定器,文件名称:messages.properties(没有找到时的默认文件), messages_en.properties(英文),messages_zh_CN.properties(中文),等等-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basename" value="config.messages.messages" />
 <property name="defaultEncoding" value="UTF-8"/>
 <property name="basename" value="i18n.messages"/>
 <property name="useCodeAsDefaultMessage" value="true" />
</bean>

文案获取

资源获取接口

MessageSource详解

Spring定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类。首先来了解一下该接口的几个重要方法:

1)String getMessage(String code, Object[] args, String defaultMessage, Locale locale) code

表示国际化资源中的属性名;args用于传递格式化串占位符所用的运行期参数;当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;locale表示本地化对象;

2)String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException

与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常;

3)String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException

MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个接口方法相同。

MessageSource类结构

MessageSource分别被HierarchicalMessageSource和ApplicationContext接口扩展,这里我们主要看一下HierarchicalMessageSource接口的几个实现类

Java Spring项目国际化(i18n)详细方法与实例

HierarchicalMessageSource接口添加了两个方法,建立父子层级的MessageSource结构,类似于前面我们所介绍的HierarchicalBeanFactory。该接口的setParentMessageSource (MessageSource parent)方法用于设置父MessageSource,而getParentMessageSource()方法用于返回父MessageSource。

HierarchicalMessageSource接口最重要的两个实现类是ResourceBundleMessageSource和ReloadableResourceBundleMessageSource。它们基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源。ReloadableResourceBundleMessageSource提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。StaticMessageSource主要用于程序测试,它允许通过编程的方式提供国际化信息。而DelegatingMessageSource是为方便操作父MessageSource而提供的代理类。

ResourceBundleMessageSource与ReloadableResourceBundleMessageSource对比

1)通过ResourceBundleMessageSource配置资源

<bean id=" messageSource "
class="org.springframework.context.support.ResourceBundleMessageSource">
 <!--①通过基名指定资源,相对于类根路径-->
 <property name="basenames">
 <list>
 <value>com/baobaotao/i18n/fmt_resource</value>
 </list>
 </property>
 </bean>

2)通过ReloadableResourceBundleMessageSource配置资源

<bean id="messageSource "
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
 <property name="basenames">
 <list>
 <value>com/baobaotao/i18n/fmt_resource</value>
 </list>
 </property>
 <!--①刷新资源文件的周期,以秒为单位-->
 <property name="cacheSeconds" value="5"/>
</bean>

3)对比

两者都是利用资源名通过getMessage()接口就可以加载整套的国际化资源文件,唯一区别在于ReloadableResourceBundleMessageSource可以定时刷新资源文件,以便在应用程序不重启的情况下感知资源文件的变化。很多生产系统都需要长时间持续运行,系统重启会给运行带来很大的负面影响,这时通过该实现类就可以解决国际化信息更新的问题。上面的配置中cacheSeconds属性让ReloadableResourceBundleMessageSource每5秒钟刷新一次资源文件(在真实的应用中,刷新周期不能太短,否则频繁的刷新将带来性能上的负面影响,一般不建议小于30分钟)。cacheSeconds默认值为-1表示永不刷新,此时,该实现类的功能就蜕化为ResourceBundleMessageSource的功能。

页面获取文案

利用Spring标签获取

引入标签库:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

获取文案:

<s:message code="test.app"/>
<spring:message code="main.title" />

利用JSTL标签获取

引入标签库:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%>

获取文案:

<fmt:message key="test.app"/>

Java代码中获取文案

利用MessageSource接口获取

1)自动注入

@Autowired
private MessageSource messageSource;
String s = messageSource.getMessage("SystemError", new Object[]{}, Locale.US);

2)手动bean获取

a. 获取容器

容器已经初始化:

WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();

容器没有初始化:

String[] configs = {"com/baobaotao/i18n/beans.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);

b. 获取bean跟文案

MessageSource ms = (MessageSource) wac.getBean("myResource");
Object[] params = {"John", new GregorianCalendar().getTime()};
String str1 = ms.getMessage("greeting.common",params,Locale.US);

利用Spring容器获取

在前面的MessageSource类图结构中我们发现ApplicationContext实现了MessageSource的接口,也就是说ApplicationContext的实现类本身也是一个MessageSource对象。

将ApplicationContext和MessageSource整合起来, Spring此处的设计人为:在一般情况下,国际化信息资源应该是容器级。我们一般不会将MessageSource作为一个Bean注入到其他的Bean中,相反MessageSource作为容器的基础设施向容器中所有的Bean开放。只要我们考察一下国际化信息的实际消费场所就更能理解Spring这一设计的用意了。国际化信息一般在系统输出信息时使用,如Spring MVC的页面标签,控制器Controller等,不同的模块都可能通过这些组件访问国际化信息,因此Spring就将国际化消息作为容器的公共基础设施对所有组件开放。

既然一般情况下我们不会直接通过引用MessageSource Bean使用国际信息,那如何声明容器级的国际化信息呢? Spring容器启动过程时,在初始化容器的时候通过initMessageSource()方法所执行的工作就是初始化容器中的国际化信息资源,它根据反射机制从BeanDefinitionRegistry中找出名称为“messageSource”且类型为org.springframework.context.MessageSource的Bean,将这个Bean定义的信息资源加载为容器级的国际化信息资源。请看下面的配置:

<!--注册资源Bean,其Bean名称只能为messageSource -->
<bean id="messageSource"
 class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basenames">
 <list>
 <value>com/baobaotao/i18n/fmt_resource</value>
 </list>
 </property>
</bean>

然后通过ApplicationContext直接访问国际化信息:

a. 获取容器

容器已经初始化:

WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();

容器没有初始化:

String[] configs = {"com/baobaotao/i18n/beans.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);

b. 获取bean跟文案

Object[] params = {"John", new GregorianCalendar().getTime()};
String str1 = ctx.getMessage("greeting.common",params,Locale.US);

注意事项

1)编码问题

a. 改变properties文件编码为UTF-8/GBK,然而ResourceBundleMessageSource的默认编码defaultEncoding是ISO-8859-1,需要在xml中增加一个相应属性将其改变为你需要的UTF-8/GBK之类。

b. 如果资源文件想统一使用ISO-8859-1格式,可以将原本用UTF-8写好的中文资源文件使用jdk自带的工具native2ascii将UTF-8文件和内容转为ISO-8859-1文件,其中的中文内容会使用16进制unicode编码为\u****格式:

cmd命令:

JAVA_HOME\bin\native2ascii -encoding UTF-8 messages_zh_CN.properties messages_zh_C1N.properties

本文主要讲解了Java Spring项目国际化(i18n)详细方法与实例,更多关于Java Spring项目国际化技巧请查看下面的相关链接

Python 相关文章推荐
python中django框架通过正则搜索页面上email地址的方法
Mar 21 Python
连接Python程序与MySQL的教程
Apr 29 Python
python 全局变量的import机制介绍
Sep 07 Python
pycharm 批量修改变量名称的方法
Aug 01 Python
django 数据库连接模块解析及简单长连接改造方法
Aug 29 Python
python SocketServer源码深入解读
Sep 17 Python
TensorFlow基本的常量、变量和运算操作详解
Feb 03 Python
用Python绘制漫步图实例讲解
Feb 26 Python
Django获取model中的字段名和字段的verbose_name方式
May 19 Python
python3.6.8 + pycharm + PyQt5 环境搭建的图文教程
Jun 11 Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
Sep 28 Python
python生成word合同的实例方法
Jan 12 Python
Python 自由定制表格的实现示例
Mar 20 #Python
python实现opencv+scoket网络实时图传
Mar 20 #Python
python实现同一局域网下传输图片
Mar 20 #Python
python实现udp传输图片功能
Mar 20 #Python
python实现UDP协议下的文件传输
Mar 20 #Python
python实现TCP文件传输
Mar 20 #Python
python实现FTP循环上传文件
Mar 20 #Python
You might like
php 用checkbox一次性删除多条记录的方法
2010/02/23 PHP
php strstr查找字符串中是否包含某些字符的查找函数
2010/06/03 PHP
ThinkPHP实现ajax仿官网搜索功能实例
2014/12/02 PHP
如何通过Linux命令行使用和运行PHP脚本
2015/07/29 PHP
php与c 实现按行读取文件实例代码
2017/01/03 PHP
PHP常用的三种设计模式
2017/02/17 PHP
php实现微信企业号支付个人的方法详解
2017/07/26 PHP
Javascript remove 自定义数组删除方法
2009/10/20 Javascript
js 实现css风格选择器(压缩后2KB)
2012/01/12 Javascript
JavaScript 用cloneNode方法克隆节点的代码
2012/10/15 Javascript
jquery固定底网站底部菜单效果
2013/08/13 Javascript
使用jQuery中的when实现多个AJAX请求对应单个回调的例子分享
2014/04/23 Javascript
js插件设置innerHTML时在IE8下提示“未知运行时错误”解决方法
2015/04/25 Javascript
javascript数据类型验证方法
2015/12/31 Javascript
jQuery基于json与cookie实现购物车的方法
2016/04/15 Javascript
AngularJS使用ng-repeat指令实现下拉框
2016/08/23 Javascript
jQuery实现的自定义弹出层效果实例详解
2016/09/04 Javascript
jQuery表单对象属性过滤选择器实例详解
2016/09/13 Javascript
js 判断附件后缀的简单实现方法
2016/10/11 Javascript
JSON与XML的区别对比及案例应用
2016/11/11 Javascript
JavaScript结合HTML DOM实现联动菜单
2017/04/05 Javascript
JS实现按钮控制计时开始和停止功能
2017/07/27 Javascript
详解.vue文件中style标签的几个标识符
2018/07/17 Javascript
vue+element 模态框表格形式的可编辑表单实现
2019/06/07 Javascript
Python设计模式之命令模式简单示例
2018/01/10 Python
python多线程+代理池爬取天天基金网、股票数据过程解析
2019/08/13 Python
python编写简单端口扫描器
2019/09/04 Python
python中bytes和str类型的区别
2019/10/21 Python
python代数式括号有效性检验示例代码
2020/10/04 Python
Timex手表官网:美国运动休闲手表品牌
2017/01/28 全球购物
澳大利亚领先的美容护肤品零售商之一:SkincareStore
2018/01/22 全球购物
ROSEFIELD手表荷兰官方网上商店:北欧极简设计女士腕表品牌
2018/01/24 全球购物
英语专业自荐书
2014/06/13 职场文书
防邪知识进家庭活动方案
2014/08/26 职场文书
关于工作经历的证明书
2014/10/11 职场文书
Python matplotlib绘制条形统计图 处理多个实验多组观测值
2022/04/21 Python