
开源技术专家
转自:http://blog.csdn.net/z69183787/article/details/53102112 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注解 先通过源码看看该注解支持的属性: [java] view plain copy @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注解 [java] view plain copy @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注解 [java] view plain copy @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的配置 看下面例子: [java] view plain copy @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } } 您可以轻松地更改任何属性,以及配置适用于特定的路径模式的CORS: [java] view plain copy @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的配置 [java] view plain copy <mvc:cors> <mvc:mapping path="/**" /> </mvc:cors> 这个配置和上面Java方式的第一种作用一样。 同样,你可以做更复杂的配置: [java] view plain copy <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> Access-Control-Allow-Origin: * 来允许任何站点对该资源进行跨域请求 在SpringMVC下的解决方案: 定义CORSFilter [java] view plain copy import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; @Component public class CORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} } web.xml: [html] view plain copy <filter> <filter-name>cors</filter-name> <filter-class>com.web.filter.CORSFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
=(COUNTIF(D9:AH9,"●")+COUNTIF(D7:AH7,"●"))*0.5
主要Shell内置命令 Shell有很多内置在其源代码中的命令。这些命令是内置的,所以Shell不必到磁盘上搜索它们,执行速度因此加快。不同的Shell内置命令有所不同。 A.2.1 bash内置命令 .:执行当前进程环境中的程序。同source。 . file:dot命令从文件file中读取命令并执行。 : 空操作,返回退出状态0。 alias:显示和创建已有命令的别名。 bg:把作业放到后台。 bind:显示当前关键字与函数的绑定情况,或将关键字与readline函数或宏进行绑定。 break:从最内层循环跳出。 builtin [sh-builtin [args]]:运行一个内置Shell命令,并传送参数,返回退出状态0。当一个函数与一个内置命令同名时,该命令将很有用。 cd [arg]:改变目录,如果不带参数,则回到主目录,带参数则切换到参数所指的目录。 command comand [arg]:即使有同名函数,仍然执行该命令。也就是说,跳过函数查找。 declare [var]:显示所有变量,或用可选属性声明变量。 dirs:显示当前记录的目录(pushd的结果)。 disown:从作业表中删除一个活动作业。 echo [args]:显示args并换行。 enable:启用或禁用Shell内置的命令。 eval [args]:把args读入Shell,并执行产生的命令。 exec command:运行命令,替换掉当前Shell。 exit [n]:以状态n退出Shell。 export [var]:使变量可被子Shell识别。 fc:历史的修改命令,用于编辑历史命令。 fg:把后台作业放到前台。 getopts:解析并处理命令行选项。 hash:控制用于加速命令查找的内部哈希表。 help [command]:显示关于内置命令的有用信息。如果指定了一个命令,则将显示该命令的详细信息。 history:显示带行号的命令历史列表。 jobs:显示放到后台的作业。 kill [-signal process]:向由PID号或作业号指定的进程发送信号。输入kill-l查看信号列表。 let:用来计算算术表达式的值,并把算术运算的结果赋给变量。 local:用在函数中,把变量的作用域限制在函数内部。 logout:退出登录Shell。 popd:从目录栈中删除项。 pushd:向目录栈中增加项。 pwd:打印出当前的工作目录。 read [var]:从标准输入读取一行,保存到变量var中。 readonly [var]:将变量var设为只读,不允许重置该变量。 return [n]:从函数中退出,n是指定给return命令的退出状态值。 set:设置选项和位置参量。 shift [n]:将位置参量左移n次。 stop pid:暂停第pid号进程的运行。 suspend:终止当前Shell的运行(对登录Shell无效)。 test:检查文件类型,并计算条件表达式。 times:显示由当前Shell启动的进程运行所累计用户时间和系统时间。 trap [arg] [n]:当Shell收到信号n(n为0、1、2或15)时,执行arg。 type [command]:显示命令的类型,例如:pwd是Shell的一个内置命令。 typeset:同declare。设置变量并赋予其属性。 ulimit:显示或设置进程可用资源的最大限额。 umask [八进制数字]:用户文件关于属主、属组和其他用户的创建模式掩码。 unalias:取消所有的命令别名设置。 unset [name]:取消指定变量的值或函数的定义。 wait [pid#n]:等待pid号为n的后台进程结束,并报告它的结束状态。
今天要配置集成服务器环境 apache + tomcat + php + jsp + mysql + sqlserver 去下载apache 发现有: apache_2.2.14-win32-x86-no_ssl.msi httpd-2.2.25-win32-x86-no_ssl.msi httpd-2.2.25-win32-x86-openssl-0.9.8y.msi apache xx.msi 和 htttpd xx.msi 格式的。 不知道用哪个了在网上找了半天,看到有这么说的: 早期的apache小组,现在已经成为一个拥有巨大力量的apache基金会。 他们把起家的apache更名为httpd,也更符合其http server的特性。而apache现在成为 apache基金会下几十种开源项目的标识。 apache和httpd是一个,到apache2后就叫httpd了。 另外,apache安装后也是有个httpd脚本来控制启动、关闭的。 早期的apache是依靠那三个配置文件(论坛上现在有的介绍Apache的贴子都说有三个文件,httpd.conf access.conf, srm.conf),后来就都放到一个文件中了。
在linux操作系统下,使用shell命令来操作: 关于权限的问题用chmod命令来修改权限 -rw-r-r-- 1 root root 可参考:http://zhidao.baidu.com/link?url=SK1SkxZ77dS_k5xaJ7O-9UCNLjNDlDIXgWY6BF3ZjjPZR4DcCJpl9sGoPdhK8udd2NsdcTY7vk3rQCA_NKsuWa http://blog.csdn.net/vblittleboy/article/details/8103264 r表是读 (Read) 、w表示写 (Write) 、x表示执行 (eXecute)读、写、运行三项权限可以用数字表示,就是r=4,w=2,x=1,777就是rwxrwxrwx,意思是该登录用户(可以用命令id查看)、他所在的组和其他人都有最高权限。 #chmod –R 777 * : 参数-R : 对目前目录下的所有档案与子目录进行相同的权限变更(即以递回的方式逐个变更) *:通配符,指当前目录下的所有文件及目录 将当前目录下的所有文件及子目录的文件拥有者权限设置为读、写、可执行,文件拥有者所在的用户组成员具备读、写、可执行权限,其它用户也具备读、写、可执行权限 应用:.bin 文件是在 Linux 和类 Unix 操作系统上的自执行文件。在执行 .bin 文件之前,你需要给它执行权限 l 安装JDK程序(本文档采用bin文件安装) 上传文件至/opt/sudytech目录内 命令: cd /opt/sudytech chmod 777 jdk-6u45-linux-x64.bin ./jdk-6u45-linux-x64.bin 安装完成后会在/opt/sudytech目录下生成java文件夹,其中包含名为jdk1.6.0_45的子文件夹。
方法一:推荐,不会影响到其它项目 见我的另一篇博客:http://www.cnblogs.com/x_wukong/p/3292664.html 修改方法: 修改tomcat下的conf/server.xml文件,找到Connector标签,添加useBodyEncodingForURI="true",如下代码: <Connector port="8080" useBodyEncodingForURI="true" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" /> 对于 URL 提交的数据和表单中 GET 方式提交的数据,在接收数据的 JSP 中设置 request.setCharacterEncoding 参数是不行的,因为在 Tomcat5.0 中,默认情况下使用ISO-8859-1 对 URL 提交的数据和表单中 GET 方式提交的数据进行重新编码(解码),而不使用该参数对 URL 提交的数据和表单中 GET 方式提交的数据进行重新编码(解码)。要解决该问题,应该在 Tomcat 的配置文件的 Connector 标签中设置useBodyEncodingForURI 或者 URIEncoding 属性,其中 useBodyEncodingForURI 参数表示是否用 request.setCharacterEncoding 参数对 URL 提交的数据和表单中 GET 方式提交的数据进行重新编码,在默认情况下,该参数为 false (Tomcat4.0 中该参数默认为true );URIEncoding 参数指定对所有 GET 方式请求(包括 URL 提交的数据和表单中 GET 方式提交的数据)进行统一的重新编码(解码)的编码。URIEncoding 和 useBodyEncodingForURI 区别是,URIEncoding 是对所有 GET 方式的请求的数据进行统一的重新编码(解码),而 useBodyEncodingForURI 则是根据响应该请求的页面的request.setCharacterEncoding 参数对数据进行的重新编码(解码),不同的页面可以有不同的重新编码(解码)的编码。所以对于 URL 提交的数据和表单中 GET 方式提交的数据,可以修改 URIEncoding 参数为浏览器编码或者修改 useBodyEncodingForURI 为true ,并且在获得数据的 JSP 页面中 request.setCharacterEncoding参数设置成浏览器编码。 方法二:不推荐,有可能影响其他应用 默认情况下,tomcat使用的是iso8859-1的编码编码方式,浏览器的embed标签中src指向的地址要通过tomcat去解析。如果包含中文,采用这种编码方式就会出现乱码问题,而在这种情况下,乱码问题就表现出无法访问该音频文件了。解决方法很简单:修改tomcat下的conf/server.xml文件,如下代码: connectionTimeout="20000"redirectPort="8443" />这段代码规定了Tomcat监听HTTP请求的端口号等信息。可以在这里添加一个属性:URIEncoding,将该属性值设置为UTF-8,即可让Tomcat(默认ISO-8859-1编码)以UTF-8的编码处理get请求。更改后的代码如下所示:URIEncoding="UTF-8"protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" />
http://www.cnblogs.com/Philoo/tag/EasyUI/
在SpringMVC中获取request对象的几种方式 1.最简单的方式(注解法) 1 2 @Autowired private HttpServletRequest request; 2.最麻烦的方法 a. 在web.xml中配置一个监听 <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> b.之后在程序里可以用 HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); 3.最直接的方法 1 public String hello(HttpServletRequest request,HttpServletResponse response) 赠送一个如何在Struts2中获取request对象 HttpServletRequest request = ServletActionContext.getRequest();
最近有个项目场景如下:需要后台遍历图片、视频所在的文件夹,获取的到的路径传递到JSP页面,使用<img src="具体的路径">显示图片。遇到的问题:如果图片的路径、图片名存在中文,则<img> 无法读取到图片。 错误截图: 原因:主要是由于字符编码不一致,Tomcat5 的http Connector默认字符编码是"iso-8859-1"。 解决办法有如下两种方式: 1.修改Tomcat中conf文件夹下的server.xml。找到 <Connector port="">,增加URIEncoding="UTF-8" ,指定URL的编码为UTF-8; 2.本人使用的是内嵌的Tomcat,而项目的运行环境指定编码为GBK,按照方法1修改,对其它功能会产生影响,这时可以这样解决,在JAVA中对中午的路径进行转码,使用URLEncoder.encode( 需要转码的内容, "gbk"),然后传到页面即可正常显示。
http://blog.csdn.net/u013068377/article/details/52703486
附加链接:http://blog.csdn.net/zjw10wei321/article/details/40145241 作用:在启动Web 容器时,自动装配Spring applicationContext.xml 的配置信息。 因为它实现了ServletContextListener 这个接口,在web.xml 配置这个监听器,启动容器时,就会默认执行它实现的方法。在ContextLoaderListener 中关联了ContextLoader 这个类,所以整个加载配置过程由ContextLoader 来完成 pring 在 web 下的入口在配置文件 web.xml 的监听器中 < listener > < listener-class > org.springframework.web.context.ContextLoaderListener </ listener-class > </ listener > <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:conf/spring/applicationContext.xml</param-value> </context-param> 上述是在web.xml 中的配置信息。 // 实现了接口 ServletContextListener, 也就是说他必须实现 contextDestroyed, contextInitialized 这两个方法 public class ContextLoaderListener implements ServletContextListener { private ContextLoader contextLoader; /** * Initialize the root web application context. */ //Spring 框架由此启动 , contextInitialized 也就是监听器类的 main 入口函数 public void contextInitialized (ServletContextEvent event) { this.contextLoader = createContextLoader(); this.contextLoader.initWebApplicationContext(event.getServletContext()); } /** * Create the ContextLoader to use. Can be overridden in subclasses. * @return the new ContextLoader */ protected ContextLoader createContextLoader() { return new ContextLoader(); } /** * Return the ContextLoader used by this listener. * @return the current ContextLoader */ public ContextLoader getContextLoader() { return this.contextLoader; } /** * Close the root web application context. */ public void contextDestroyed (ServletContextEvent event) { if (this.contextLoader != null) { this.contextLoader.closeWebApplicationContext(event.getServletContext()); } } } 总的来说这个入口非常简单 , 所有实现都隐藏在 ContextLoader 类里 , 我们在下一篇的内容中讨论ContextLoader, 如果你不知道为什么这里是程序的入口 , 那么复习一下 ServletContextListener 接口和监听器的相关知识吧 ServletContext 被 Servlet 程序用来与 Web 容器通信。例如写日志,转发请求。每一个 Web 应用程序含有一个Context ,被Web 应用内的各个程序共享。因为Context 可以用来保存资源并且共享,所以我所知道的ServletContext 的最大应用是Web 缓存---- 把不经常更改的内容读入内存,所以服务器响应请求的时候就不需要进行慢速的磁盘I/O 了。 ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,如服务器启动时ServletContext 被创建,服务器关闭时 ServletContext 将要被销毁。 在JSP 文件中,application 是 ServletContext 的实例,由JSP 容器默认创建。Servlet 中调用getServletContext() 方法得到 ServletContext 的实例。 我们使用缓存的思路大概是: 1. 服务器启动时,ServletContextListener 的 contextInitialized() 方法被调用,所以在里面创建好缓存。可以从文件中或者从数据库中读取取缓存内容生成类,用 ervletContext.setAttribute() 方法将缓存类保存在ServletContext 的实例中。 2. 程序使用 ServletContext.getAttribute() 读取缓存。如果是 JSP ,使用a pplication.getAttribute() 。如果是Servlet ,使用 getServletContext().getAttribute() 。如果缓存发生变化( 如访问计数) ,你可以同时更改缓存和文件/ 数据库。或者你等 变化积累到一定程序再保存,也可以在下一步保存。 3. 服务器将要关闭时,ServletContextListener 的 contextDestroyed() 方法被调用,所以在里面保存缓存的更改。将更改后的缓存保存回文件或者数据库,更新原来的内容。 Java 代码 import User; //my own classimport DatabaseManager; // my own class import javax.servlet.ServletContext; import javax.servlet.ServletContextListener; public class MyContextListener implements ServletContextListener { private ServletContext context = null; public void contextInitialized(ServletContextEvent event) { context = event.getServletContext(); User user = DatabaseManager.getUserById(1); context.setAttribute("user1", user); } public void contextDestroyed(ServletContextEvent event) { User user = (User)context.getAttribute("user1"); DatabaseManager.updateUserData(user); this.context = null; } } import User; //my own classimport DatabaseManager; // my own class import javax.servlet.ServletContext; import javax.servlet.ServletContextListener; public class MyContextListener implements ServletContextListener { private ServletContext context = null; public void contextInitialized(ServletContextEvent event) { context = event.getServletContext(); User user = DatabaseManager.getUserById(1); context.setAttribute("user1", user); } public void contextDestroyed(ServletContextEvent event) { User user = (User)context.getAttribute("user1"); DatabaseManager.updateUserData(user); this.context = null; } } 布署 ServletContextListener 你实现(implements) 了 ServletContextListener 编译后,把它放在正确的WEB-INF/classes 目录下,更改WEB-INF 目录下的 web.xml 文件,在web-app 节点里添加 <listener> <listener-class>MyServletContextListener</listener-class> </listener>
http://v3.bootcss.com/
在开始Android开发之旅启动之前,首先要搭建环境,然后创建一个简单的HelloWorld。本文的主题如下: 1、环境搭建 1.1、JDK安装 1.2、Eclipse安装 1.3、Android SDK安装 1.4、ADT安装 1.5、创建AVD 2、HelloWorld 1、环境搭建 1.1、JDK安装 如果你还没有JDK的话,可以去这里下载,接下来的工作就是安装提示一步一步走。设置环境变量步骤如下: 我的电脑->属性->高级->环境变量->系统变量中添加以下环境变量: JAVA_HOME值为: D:/Program Files/Java/jdk1.6.0_18(你安装JDK的目录) CLASSPATH值为:.;%JAVA_HOME%/lib/tools.jar;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/bin; Path: 在开始追加 %JAVA_HOME%/bin; NOTE:前面四步设置环境变量对搭建Android开发环境不是必须的,可以跳过。 安装完成之后,可以在检查JDK是否安装成功。打开cmd窗口,输入java –version 查看JDK的版本信息。出现类似下面的画面表示安装成功了: 图1、验证JDK安装是否成功 1.2、Eclipse安装 如果你还么有Eclipse的话,可以去这里下载,下载如下图所示的Eclipse IDE for Java Developers(92M)的win 32bit版: 图2、Eclipse下载 解压之后即可使用。 1.3、Android SDK安装 在Android Developers下载android-sdk_r05-windows.zip,下载完成后解压到任意路径。 运行SDK Setup.exe,点击Available Packages。如果没有出现可安装的包,请点击Settings,选中Misc中的"Force https://..."这项,再点击Available Packages 。 选择希望安装的SDK及其文档或者其它包,点击Installation Selected、Accept All、Install Accepted,开始下载安装所选包 在用户变量中新建PATH值为:Android SDK中的tools绝对路径(本机为D:/AndroidDevelop/android-sdk-windows/tools)。 图2、设置Android SDK的环境变量 “确定”后,重新启动计算机。重启计算机以后,进入cmd命令窗口,检查SDK是不是安装成功。 运行 android –h 如果有类似以下的输出,表明安装成功: 图3、验证Android SDK是否安装成功 1.4、ADT安装 打开 Eclipse IDE,进入菜单中的 "Help" -> "Install New Software" 点击Add...按钮,弹出对话框要求输入Name和Location:Name自己随便取,Location输入http://dl-ssl.google.com/android/eclipse。如下图所示: 确定返回后,在work with后的下拉列表中选择我们刚才添加的ADT,我们会看到下面出有Developer Tools,展开它会有Android DDMS和Android Development Tool,勾选他们。 如下图所示: 然后就是按提示一步一步next。 完成之后: 选择Window > Preferences... 在左边的面板选择Android,然后在右侧点击Browse...并选中SDK路径,本机为: D:/AndroidDevelop/android-sdk-windows 点击Apply、OK。配置完成。 1.5、创建AVD 为使Android应用程序可以在模拟器上运行,必须创建AVD。 1、在Eclipse中。选择Windows > Android SDK and AVD Manager 2、点击左侧面板的Virtual Devices,再右侧点击New 3、填入Name,选择Target的API,SD Card大小任意,Skin随便选,Hardware目前保持默认值 4、点击Create AVD即可完成创建AVD 注意:如果你点击左侧面板的Virtual Devices,再右侧点击New ,而target下拉列表没有可选项时,这时候你: 点击左侧面板的Available Packages,在右侧勾选https://dl-ssl.google.com/android/repository/repository.xml,如下图所示: 然后点击Install Selected按钮,接下来就是按提示做就行了 要做这两步,原因是在1.3、Android SDK安装中没有安装一些必要的可用包(Available Packages)。 2、HelloWorld 通过File -> New -> Project 菜单,建立新项目"Android Project" 然后填写必要的参数,如下图所示:(注意这里我勾选的是Google APIs,你可以选你喜欢的,但你要创建相应的AVD) 相关参数的说明: Project Name: 包含这个项目的文件夹的名称。 Package Name: 包名,遵循JAVA规范,用包名来区分不同的类是很重要的,我用的是helloworld.test。 Activity Name: 这是项目的主类名,这个类将会是Android的Activity类的子类。一个Activity类是一个简单的启动程序和控制程序的类。它可以根据需要创建界面,但不是必须的。 Application Name: 一个易读的标题在你的应用程序上。 在"选择栏"的 "Use default location" 选项,允许你选择一个已存在的项目。 点击Finish后,点击Eclipse的Run菜单选择Run Configurations… 选择“Android Application”,点击在左上角(按钮像一张纸上有个“+”号)或者双击“Android Application”, 有个新的选项“New_configuration”(可以改为我们喜欢的名字)。 在右侧Android面板中点击Browse…,选择HelloWorld 在Target面板的Automatic中勾选相应的AVD,如果没有可用的AVD的话,你需要点击右下角的Manager…,然后新建相应的AVD。如下图所示: 然后点Run按钮即可,运行成功的话会有Android的模拟器界面,如下图所示:
su:Swith user 切换用户,切换到root用户cat: Concatenate 串联uname: Unix name 系统名称df: Disk free 空余硬盘du: Disk usage 硬盘使用率chown: Change owner 改变所有者chgrp: Change group 改变用户组ps:Process Status 进程状态tar:Tape archive 解压文件chmod: Change mode 改变模式umount: Unmount 卸载ldd:List dynamic dependencies 列出动态相依insmod:Install module 安装模块rmmod:Remove module 删除模块lsmod:List module 列表模块alias :Create your own name for a commandbash :GNU Bourne-Again Shell linux内核 grep:global regular expression printhttpd :Start Apacheipcalc :Calculate IP information for a hostping :Send ICMP ECHO_Request to network hostsreboot: Restart your computersudo:Superuser do /bin = BINaries /dev = DEVices /etc = ETCetera /lib = LIBrary /proc = PROCesses /sbin = Superuser BINaries /tmp = TeMPorary /usr = Unix Shared Resources /var = VARiable ? FIFO = First In, First Out GRUB = GRand Unified Bootloader IFS = Internal Field Seperators LILO = LInux LOader MySQL = My最初作者的名字SQL = Structured Query Language PHP = Personal Home Page Tools = PHP Hypertext Preprocessor PS = Prompt String Perl = "Pratical Extraction and Report Language" = "Pathologically Eclectic Rubbish Lister" Python Monty Python's Flying Circus Tcl = Tool Command Language Tk = ToolKit VT = Video Terminal YaST = Yet Another Setup Tool apache = "a patchy" server apt = Advanced Packaging Tool ar = archiver as = assembler bash = Bourne Again SHell bc = Basic (Better) Calculator bg = BackGround cal = CALendar cat = CATenate cd = Change Directory chgrp = CHange GRouP chmod = CHange MODe chown = CHange OWNer chsh = CHange SHell cmp = compare cobra = Common Object Request Broker Architecture comm = common cp = CoPy cpio = CoPy In and Out cpp = C Pre Processor cups = Common Unix Printing System cvs = Current Version System daemon = Disk And Execution MONitor dc = Desk Calculator dd = Disk Dump df = Disk Free diff = DIFFerence dmesg = diagnostic message du = Disk Usage ed = editor egrep = Extended GREP elf = Extensible Linking Format elm = ELectronic Mail emacs = Editor MACroS eval = EVALuate ex = EXtended exec = EXECute fd = file descriptors fg = ForeGround fgrep = Fixed GREP fmt = format fsck = File System ChecK fstab = FileSystem TABle fvwm = F*** Virtual Window Manager gawk = GNU AWK gpg = GNU Privacy Guard groff = GNU troff hal = Hardware Abstraction Layer joe = Joe's Own Editor ksh = Korn SHell lame = Lame Ain't an MP3 Encoder lex = LEXical analyser lisp = LISt Processing = Lots of Irritating Superfluous Parentheses ln = LiNk lpr = Line PRint ls = list lsof = LiSt Open Files m4 = Macro processor Version 4 man = MANual pages mawk = Mike Brennan's AWK mc = Midnight Commander mkfs = MaKe FileSystem mknod = MaKe NODe motd = Message of The Day mozilla = MOsaic GodZILLa mtab = Mount TABle mv = MoVe nano = Nano's ANOther editor nawk = New AWK nl = Number of Lines nm = names nohup = No HangUP nroff = New ROFF od = Octal Dump passwd = PASSWorD pg = pager pico = PIne's message COmposition editor pine = "Program for Internet News & Email" = "Pine is not Elm" ping = Packet InterNet Grouper pirntcap = PRINTer CAPability popd = POP Directory pr = pre printf = PRINT Formatted ps = Processes Status pty = pseudo tty pushd = PUSH Directory pwd = Print Working Directory rc = runcom = run command, shell rev = REVerse rm = ReMove rn = Read News roff = RunOFF rpm = RPM Package Manager = RedHat Package Manager rsh, rlogin, = Remote rxvt = ouR XVT sed = Stream EDitor seq = SEQuence shar = SHell ARchive slrn = S-Lang rn ssh = Secure SHell ssl = Secure Sockets Layer stty = Set TTY su = Substitute User svn = SubVersioN tar = Tape ARchive tcsh = TENEX C shell telnet = TEminaL over Network termcap = terminal capability terminfo = terminal information tr = traslate troff = Typesetter new ROFF tsort = Topological SORT tty = TeleTypewriter twm = Tom's Window Manager tz = TimeZone udev = Userspace DEV ulimit = User's LIMIT umask = User's MASK uniq = UNIQue vi = VIsual = Very Inconvenient vim = Vi IMproved wall = write all wc = Word Count wine = WINE Is Not an Emulator xargs = eXtended ARGuments xdm = X Display Manager xlfd = X Logical Font Description xmms = X Multimedia System xrdb = X Resources DataBase xwd = X Window Dump yacc = yet another compiler compiler
ps -ef|grep javakill -9 端口号cd /opt/sudytrue>nohup.outnohup eos7.5/startServer.sh &
原文:http://blog.csdn.net/wangjuan_01/article/details/51919551 步骤一:绑定域名 先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 步骤二:引入JS文件 在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js 请注意,如果你的页面启用了https,务必引入 https://res.wx.qq.com/open/js/jweixin-1.0.0.js ,否则将无法在iOS9.0以上系统中成功使用JSSDK 如需使用摇一摇周边功能,请引入 jweixin-1.1.0.js 备注:支持使用 AMD/CMD 标准模块加载方法加载 步骤三:通过config接口注入权限验证配置 所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。 wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名,见附录1 jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); 步骤四:通过ready接口处理成功验证 wx.ready(function(){ // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。 }); 步骤五:通过error接口处理失败验证 wx.error(function(res){ // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。 }); 步骤六:具体接口调用,调用之前要获取接口调用凭据,具体如下: 1.配置文件 application-common.properties 配置一些接口常量信息 [html] view plain copy #\u5FAE\u4FE1AppID AppID=wx0a5aabbccddees #\u5FAE\u4FE1AppSecret AppSecret=f1ec0d65d104589ds0opke907dslsjeln09 2.工具类ConfigHelper,读取配置文件: [java] view plain copy package com.hengxin.qianee.commons; import java.util.ResourceBundle; /** * 读取配置文件 * @author hzg * */ public class ConfigHelper { private static Object lock = new Object(); private static ConfigHelper config = null; private static ResourceBundle rb = null; private ConfigHelper(String configFileName) { rb = ResourceBundle.getBundle(configFileName); } public static ConfigHelper getInstance(String configFileName) { synchronized(lock) { if(null == config) { config = new ConfigHelper(configFileName); } } return (config); } public String getValue(String key) { return (rb.getString(key)); } } 3.获取签名信息 [java] view plain copy package com.hengxin.qianee.talent.wechat.utils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.hengxin.qianee.cache.impl.MyCache; import com.hengxin.qianee.commons.ConfigHelper; import com.hengxin.qianee.service.thirdparty.pay.llpay.conn.CustomHttpClient; public class WechatSignUtil { @Autowired MyCache cache; public static JSONObject sendGetRequest(String url){ HttpClient httpClient = CustomHttpClient.GetHttpClient(); HttpGet get = new HttpGet(url); get.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); BufferedReader br = null; try { // 发送请求,接收响应 HttpResponse resp = httpClient.execute(get); int ret = resp.getStatusLine().getStatusCode(); if(ret == HttpStatus.SC_OK){ // 响应分析 HttpEntity entity = resp.getEntity(); br = new BufferedReader(new InputStreamReader( entity.getContent(), "UTF-8")); StringBuffer responseString = new StringBuffer(); String str = br.readLine(); while (str != null) { responseString.append(str); str = br.readLine(); } return JSON.parseObject(responseString.toString()); } }catch(Exception e){ e.printStackTrace(); }finally { if (br != null) { try { br.close(); } catch (IOException e) { // do nothing } } } return new JSONObject(); } /** * 获取签名信息 * @return 返回签名等 */ public Map<String,String> getWechatSign(HttpServletRequest request,MyCache cache) throws UnsupportedEncodingException{ String appid = ConfigHelper.getInstance("config").getValue("AppID"); String appSecret = ConfigHelper.getInstance("config").getValue("AppSecret"); String url_Template_GetAccessToken ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; String url_Template_GetAccessTicket = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi"; String accessToken = cache.getString("wechatAccessToken"); if(accessToken == null){ //获取token String url_GetAccessToken = String.format(url_Template_GetAccessToken, appid,appSecret); JSONObject accessTokenMap = WechatSignUtil.sendGetRequest(url_GetAccessToken); accessToken = accessTokenMap.getString("access_token"); cache.setString("wechatAccessToken", 6000, accessToken); } String accessTicket = cache.getString("wechatAccessTicket"); if(accessTicket == null){ //获取ticket String url_GetAccessTicket = String.format(url_Template_GetAccessTicket, accessToken); JSONObject url_GetAccessTicketMap = WechatSignUtil.sendGetRequest(url_GetAccessTicket); accessTicket = url_GetAccessTicketMap.getString("ticket"); cache.setString("wechatAccessTicket", 6000, accessTicket); } // 时间戳 Long timeStamp = new Date().getTime()/1000; String url = request.getRequestURL().toString(); //随机字串 String noncestr = UUID.randomUUID().toString(); //签名 String signature = getSignature(noncestr,accessTicket,url,timeStamp); Map<String,String> result = new HashMap<String,String>(); result.put("appId", appid); result.put("timestamp", timeStamp.toString()); result.put("nonceStr", noncestr); result.put("signature", signature); return result; } /** * 生成签名 * @param nonceStr 随机字串 * @param jsapi_ticket 票据 * @param url * @param timestamp 时间戳 * @return */ private String getSignature(String nonceStr,String jsapi_ticket,String url,Long timestamp){ String template = "jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s"; String result = String.format(template, jsapi_ticket,nonceStr,timestamp,url); return org.apache.commons.codec.digest.DigestUtils.shaHex(result); } } 总结:先配置好域名,先根据appid和appSecret拼成的串发送请求获取到一个JSONObject对象,通过该对象调用getString("access_token")方法取到token; 根据token拼成的url发送一个http get请求得到JSONObject对象,通过调用该对象的.getString("ticket")方法得到ticket 根据时间戳、随机串、当然访问的url和ticket生产签名,也就是接口调用的凭据。最后jsp页面调用如下: [html] view plain copy <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <style type="text/css"> </style> <script type="text/javascript"> wx.config({ debug : false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId : "${appId}", // 必填,公众号的唯一标识 timestamp : "${timestamp}", // 必填,生成签名的时间戳 nonceStr : "${nonceStr}", // 必填,生成签名的随机串 signature : "${signature}",// 必填,签名,见附录1 jsApiList : [ 'onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone' ] }); var obj = { title : '标题', desc : '欢迎关注!', link : 'http://m.test.com', imgUrl : 'https://qianee-official.oss-cn-beijing.aliyuncs.com/data/2016-05-21%2Fe382d374-f3c5-45bb-b8cedlsjelnge', }; wx.ready(function(){ wx.onMenuShareAppMessage(obj); // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口 wx.onMenuShareTimeline(obj); // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口 wx.onMenuShareQQ(obj); // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口 wx.onMenuShareWeibo(obj); });
function A( event ){ alert( 'A' );}function B( event ){ alert( 'B' );}function C( event ){ alert( 'C' );}var $btn1 = $("#btn1");// 为btn1元素的click事件绑定事件处理函数$btn1.bind( "click.foo.bar", A );$btn1.bind( "click.test.foo", B );$btn1.bind( "click.test", C ); // 触发btn1的click事件,不限定命名空间$btn1.trigger("click"); // 触发A、B、C// 触发btn1的包含命名空间foo的click事件$btn1.trigger("click.foo"); // 触发A、B// 触发btn1的包含命名空间test的click事件$btn1.trigger("click.test"); // 触发B、C// 触发btn1的同时包含命名空间foo和test的click事件$btn1.trigger("click.foo.test"); // 触发B $(“#haorooms”).on("click.a",function(){}); $(“#haorooms”).on("click.a.bb",function(){}); $(“#haorooms”).on("dbclick.a",function(){}); $(“#haorooms”).on("mouseover.a",function(){}); $(“#haorooms”).on("mouseout.a",function(){}); 当然,我们也可以用bind进行事件绑定。我们看到上面的代码,我们可以在事件后面,以点号,加我们的名字,就是事件命名空间。所谓事件命名空间,就是事件类型后面以点语法附加一个别名,以便引用事件,如”click.a”,其中”a”就是click当前事件类型的别名,即事件命名空间。 假如我们要删除如下命名空间: $(“#haorooms”).on("click.a.bb",function(){}); 我们可以用: $(“#haorooms”).off("click.a.bb");//直接删除bb命名空间 【推荐】 $(“#haorooms”).off(".bb"); //直接删除bb命名空间 【推荐】 $(“#haorooms”).off(".a"); //删除.a命名空间下面所有的子空间【包括.a.bb .a.cc等等,.a是.bb的父级,因此.a下面的都会删掉】 $(“#haorooms”).off("click");//直接解绑click,下面的命名空间都会删除。 要注意的是: 假如我们写了如下代码: $(“#haorooms”).on("click",function(){}); $(“#haorooms”).on("click.a",function(){}); $(“#haorooms”).on("click.a.bb",function(){}); 那么我们要用trigger触发click事件,也就是触发第一个,岂不是把click.a和click.a.bb都触发了,那如何解决这个问题呢,我只想触发click,而不触发click.a及以下的命名空间? 没关系! 有如下解决办法: 如果事件类型后面附加感叹号,则表示触发不包含命名空间的特定事件类型。 假如我们只想触发click,可以这么写: $(“#haorooms”).trigger("click!") 只触发bb,可以这么写: $(“#haorooms”).trigger("click.a.bb"); 有了命名空间,可以方便我们在同一个事件上面做管理啦!!!
var RegExps = { number: /^-?((([1-9]\d*)|0)|([1-9]\d*\.\d+)|(0\.\d*[1-9]\d*))$/, // 数字,整型或浮点型 int: /^((-?[1-9]\d*)|0)$/, // 数字,整形 float: /^-?(([1-9]\d*\.\d+)|(0\.\d*[1-9]\d*))$/, // 数字,浮点型 id:/^(\d{15}$|^\d{18}$|^\d{17}(\d|X|x))$/, // 身份证号 zipcode: /^[1-9]\d{5}(?!\d)$/, // 邮政编码 qq:/^[1-9][0-9]{4,}$/, // qq号 tel:/^0\d{2,3}-\d{7,8}$/, // 固定电话 mobile: /^0?(13|14|15|17|18)[0-9]{9}$/, // 手机号 phone: /^((0\d{2,3}-\d{7,8})|(0?(13|14|15|17|18)[0-9]{9}))$/, // 固定电话或手机 url:/^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+$/, // URL地址 email:/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/, // email地址 ip:/^(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)$/, // ip地址 chinese:/^[\u4e00-\u9fa5]*$/, // 中文字符, shuzi:/^[0-9]*$/ ,//纯数字 hghtje:/^(\d{1,4}|\d{1,4}\.\d{1,6})$/ ,//海关合同审批金额 mac:/^([0-9a-fA-F]{2})(([\/\s:-][0-9a-fA-F]{2}){5})$/, //Mac地址 password:/^(?=.*[0-9].*)(?=.*[A-Z].*)(?=.*[a-z].*).{8,}$/ //密码长度验证6-15位 /^[\@A-Za-z0-9\!\#\$\%\^\&\*\.\~\_]{8,}$/ };
package的属性 1.name: 配置package元素时必须指定name属性,这是引用该包的唯一标识. 2.extends: 可选属性,指定该包继承的父包,子包可以从一个或多个父包中继承到拦截器、Action等配置,一般会继承Struts2自带的默认文件struts-default.xml配置文件。继承多个父包时逗号隔开: <package name="temp" extends="struts-default"></package> <!-- temp2 是另一个package的name的值,通过它来引用-->注:默认文件struts-default.xml配置文件,也可以是json-default.xmljson-default是继承struts-default,json这个result type是在json-default (struts2-json-plugin-2.1.8.1.jar\struts-plugin.xml)里面定义的,内容如下(省去了xml和doctype标签),从xml的package的extends可以看出。 3.namespace: 可选属性,定义带包的命名空间,默认值为“ ”。其运用原理是package的name属性作为其唯一标识,同一个命名空间内不能出现相同name值的package,否则前面定义的package会被后面的覆盖,此时改放不同的命名空间下就可以了,处理时记得是 命名空间+Action名,如: <package name="package_temp" extends="struts-default"> <action name="action_temp"> </action> </package> <package name="package_test" extends="struts-default" namespace="/book"> <action name="action_test"> </action> </package> <!-- 访问package_temp:http://localhost:8080/action_temp.action--> <!-- 访问package_test:http://localhost:8080/book/action_test.action--> 如果请求为/book/book.action,系统首先找/book命名空间里名为book的Action,如果在该命名空间里找到,则使用该Action处理请求;否则系统将到默认命名空间中查找名为book的Action,如果两个命名空间都找不到则系统出现错误。 4.abstract: 可选属性,指定该包是否是一个抽象包。抽象包的含义是该包不能包含Action的定义,值为true 、false。
Struts2 资源配置文件国际化Struts2资源文件的命名规范:basename_language_country.propertiesStruts2国际化如果系统同时存在资源文件、类文件,系统将以类文件为主,而不会调用资源文件。对于简体中文的Locale,ResourceBundle搜索资源的顺序是:(1)baseName_zh_CN.class(2)baseName_zh_CN.properties(3)baseName_zh.class(4)baseName_zh.properties(5)baseName.class(6)baseName.propertiesStruts2资源文件加载方式有3种,1.全局资源配置文件2.包资源配置文件3.Action资源配置文件查找顺序:Action资源配置文件>包资源配置文件>全局资源配置文件1.全局资源文件加载方式:struts.xml<constant name="struts.custom.i18n.resources" value="baseName"/>或struts.propertiesstruts.custom.i18n.resources=baseName2.包资源文件加载方式:文件命名规范:package_language_country.properties将包资源文件放到 Action所在的包目录中3.Action资源配置文件加载方式:文件命名规范:Action_language_country.properties将Action资源配置文件 放到Action所在的包目录中struts2标签使用资源配置文件 key:资源文件中的key path:资源文件所在的路径<s:i18n name="path"><s:text name="key"><s:param>参数</s:param></s:text></s:i18n> 原文:http://hi.baidu.com/xzl_awin/item/4053a61d92101efe9c778afe 相关文章:http://www.cnblogs.com/cookray/archive/2012/10/15/2724195.html
java中的几种对象(PO,VO,DAO,BO,POJO) 一、PO :(persistant object ),持久对象 可以看成是与数据库中的表相映射的java对象。使用Hibernate来生成PO是不错的选择。二、VO :(value object) ,值对象 通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.PO只能用在数据层,VO用在商业逻辑层和表示层。各层操作属于该层自己的数据对象,这样就可以降低各层之间的耦合,便于以后系统的维护和扩展。三、DAO :(Data Access Objects) ,数据访问对象接口DAO是Data Access Object数据访问接口,数据访问:顾名思义就是与数据库打交道。夹在业务逻辑与数据库资源中间。J2EE开发人员使用数据访问对象(DAO)设计模式把底层的数据访问逻辑和高层的商务逻辑分开.实现DAO模式能够更加专注于编写数据访问代码.DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操作和上层的商务逻辑分开.一个典型的DAO实现有下列几个组件: 1. 一个DAO工厂类; 2. 一个DAO接口; 3. 一个实现DAO接口的具体类; 4. 数据传递对象(有些时候叫做值对象). 具体的DAO类包含了从特定的数据源访问数据的逻辑。四、BO :(Business Object),业务对象层表示应用程序领域内“事物”的所有实体类。这些实体类驻留在服务器上,并利用服务类来协助完成它们的职责。五、POJO :(Plain Old Java Objects),简单的Java对象实际就是普通JavaBeans,使用POJO名称是为了避免和EJB混淆起来, 而且简称比较直接.其中有一些属性及其getter、setter方法的类,有时可以作为value object或dto(Data Transform Object)来使用.当然,如果你有一个简单的运算属性也是可以的,但不允许有业务方法,也不能携带有connection之类的方法。
子类可以赋值给超类,称之为向上转型,这个是自动的。 超类不可以赋值给子类,这个是向下转型,需要我们手动实现。 赋值给超类的子类引用在运行期间将表现出不同的特性,这就是多态。 小类型 可转换为 大类型 大类型 转小类型需要 强制转换 对于存在继承关系的强制类型转换: 子类转换为父类属于向上塑型,可以直接转换 父类转换为子类属于向下塑型,需要强制类型转换,但是不一定成功。成功的条件是这个父类是经过子类向上塑型转换来的 即 :Father father=new Son(); Son son=(Son)father; 对于不存在继承关系的强制类型转换,一般都是失败的(如果不写转换方法的话) 即: 子类可转为父类,父类不可以转为子类(如果不用强制类型转换) 1. 子类和父类含有相同的成员变量的时候,访问的是父类的成员变量 2. 子类和父类含有相同的成员方法是,访问的是子类的成员方法 3. 子类和父类含有相同的静态函数和静态方法时,访问的是父类的。 4. 父类不能访问子类特有成员和方法(强制类型转换除外) 也就是说,只有在访问成员方法的时候,才会表现出多态。 或者说: 对象多态时: 1.成员变量:(不涉及覆盖) 编译时: 参考引用变量所属的类中是否有调用的成员变量, 有, 编译通过,没有,编译失败。 运行时: 参考引用变量所属的类中是否有调用的成员变量, 并运行该类所属中的成员变量。 简单的说:编译和运行都参考等号的左边。 2.成员函数(非静态): 编译时:参考引用变量所属的类中是否有调用的成员变量, 有, 编译通过, 没有,编译失败: 运行时:参考的是对象所属的类中是否有调用的函数。 简单的说:编译看左边, 运行看右边。 3.静态函数, 变量: 编译和运行都是参考左边参数类型! 其实静态方法不存在多态, 静态方法是属于类的,我们说的是对象的多态!静态方法直接用类名调用就好了, 没必要创建对象! 静态的方法只能被静态的方法所覆盖!
很多时候我们需要在项目中读取外部属性文件,用到了System.getProperty("")方法。这个方法需要配置JVM系统属性,那么如何配置呢? 那就是使用java -D 配置系统属性。使用格式是:java -Dkey=value 比如新建一个测试类,如下: public class Test { public static void main(String[] args){ System.out.println(System.getProperty("configurePath")); } } 这段代码直接执行的话,会输出null 接下来需要配置configurePath属性了,有两种方法。第一种方法是在启动tomcat的时候配置:比如在myeclipse中,选中这个项目,然后在工具栏中选择"Run-->Run Confgurations“,然后在对话框的右边选择"Arguments,然后在VM arguments中输入-DconfigurePath=hello。如下图: 然后就会在控制台输出:hello 第二种方法是在执行java命令的时候配置:将之前的测试类导出为一个jar包,再控制台使用命令执行:java -DconfigurePath=hello -jar Test.jar也会得到hello 以下转载自网络: java -D 配置系统属性使用案例其实,在不知不觉中我们已经在使用-D的参数项,比如用下面参数来配置文件编码:-Dfile.encoding=UTF-8 再比如,用以下参数来配置dubbo的选项:java -Ddubbo.reference.com.foo.BarService.check=falsejava -Ddubbo.reference.check=falsejava -Ddubbo.consumer.check=false java -Ddubbo.registry.check=false 功能解析-D=value官网解释: Set a system property value. If value is a string that contains spaces, you must enclose the string in double quotes:在虚拟机的系统属性中设置属性名/值对,运行在此虚拟机上的应用程序可用:System.getProperty("属性名") 得到value的值。如果value中有空格,则需要用双引号将该值括起来,如:-Dname=”kazaf f”。该参数通常用于设置系统级全局变量值,如配置文件路径,保证该属性在程序中任何地方都可访问。 注意事项(1)需要设置的是JVM参数而不是program参数;(2)使用此参数的参数优先级最高,会覆盖项目中配置的此项;
1、实体查询: hql="FROM User"; List list= session.createQuery(hql).list(); for(Object obj:list){ System.out.println(obj); } 【注意】:HQL语句中关键字不区分大小写,但是实体类和对象属性要区分大小写 2、查询某个对象的某个属性 hql="SELECT name FROM User where id=1"; 方式一: Object name= session.createQuery(hql).list().get(0); System.out.println(name); 这种方式不被推荐,当如果id=0的User对象不存在是,使用get(0)会抛出异常,我们通常使用下面这种方式 方式二:通过uniqueResult()方法,该方法返回一个Object对象,如果对象不存在则返回null,如果返回值不唯一,则抛出异常 1 Object name= session.createQuery(hql).uniqueResult(); 2 System.out.println(name) 3、查询其中几列数据,返回一个数组 hql="SELECT id,name FROM User"; List list= session.createQuery(hql).list(); for(Object obj:list){ System.out.println(Arrays.toString((Object[])obj)); } list()返回的是一个List<Object>对象 4、查询其中几列数据,返回一个实体类 hql="SELECT new User(id,name) FROM User"; List<User> list= session.createQuery(hql).list(); for(User user:list){ System.out.println(user); } HQL通过new的方式可以返回一个新的实体类,比如说上面通过new User(id,name)方式将id,name返回给User,要求User必须包含一个相对应的构造函数,否则会抛出异常,同时我们还有应该给User指定一个默认的构造函数,否则使用From User也会抛出异常,因为这种方式采用的是默认构造。值得注意的是,如果使用的新构造对象,那么处理指定的属性会被赋予新值外,其它属性均为默认值。 5、WHER、GROUP BY、HAVING和ORDER综合使用 hql="SELECT age, COUNT(age) num FROM User WHERE age>10 GROUP BY age HAVING COUNT(age)>1 ORDER BY num DESC"; 在HAVING中,不能使用别名num,但在ORDER BY中可以使用别名num 6、使用占位符"?" hql="FROM User where id=?"; User user= (User)session.createQuery(hql) .setParameter(0, 2) .uniqueResult(); System.out.println(user); Hibernate和JDBC占位符的区别:在Hibernate占位符下标从0开始,在JDBC中的占位符下标从1开始 7、使用命名参数 hql="FROM User where id=:id"; User user= (User)session.createQuery(hql) .setParameter("id", 2) .uniqueResult(); System.out.println(user); 使用参数的方式,在HQL中在参数前面需要加上冒号 8、使用集合或数组参数 hql="FROM User where id IN (:ids)"; List<User> list= session.createQuery(hql) .setParameterList("ids", new Object[]{1,3,4}) .list(); for (User user : list) { System.out.println(user); } 9、使用命名方式查询 如果我们将HQL代码写在类中,那么编译后我们非常难以维护,为了后期代码的可维护行,我们需要将HQL代码写在对应类的.hbm.xml文件中,例如: <query name="queryUserRanage"> FROM User WHERE id BETWEEN ? AND ? </query> 在Java代码中 Query query=session.getNamedQuery("queryUserRanage"); List<User> list=query .setParameter(0, 10) .setParameter(1, 20) .list(); for(User user:list){ System.out.println(user); } 通常我们使用参数的方式,这样可以很直观知道每个参数的作用 <query name="queryUserRanage"> FROM User WHERE id BETWEEN :minId AND :maxId </query> 但是如果在SQL语句中存在>、<等xml中特殊字符,这些字符在xml中都有特殊的意义,所有我们不能直接这样写 【Error】 <query name="queryUserRanage"> FROM User WHERE id > :minId AND id< :maxId </query> 【Right】 <query name="queryUserRanage"> FROM User WHERE id &gt; :minId AND &lt; :maxId </query> 虽然上面的方式我们可以正确执行,但是代码显示不够友好,所有我们推荐使用下面的一种方式 <query name="queryUserRanage"> <![CDATA[FROM User WHERE id > :minId AND < :maxId]] </query> CDATA代码块说明在代码中的语句不需要转义,我们一个HQL都推荐用CDATA块来包裹着
mysql(5.5)所支持的日期时间类型有:DATETIME、 TIMESTAMP、DATE、TIME、YEAR。 几种类型比较如下: 日期时间类型 占用空间 日期格式 最小值 最大值 零值表示 DATETIME 8 bytes YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00 9999-12-31 23:59:59 0000-00-00 00:00:00 TIMESTAMP 4 bytes YYYY-MM-DD HH:MM:SS 19700101080001 2038 年的某个时刻 00000000000000 DATE 4 bytes YYYY-MM-DD 1000-01-01 9999-12-31 0000-00-00 TIME 3 bytes HH:MM:SS -838:59:59 838:59:59 00:00:00 YEAR 1 bytes YYYY 1901 2155 0000 DATETIME DATETIME 用于表示 年月日 时分秒,是 DATE 和 TIME 的组合,并且记录的年份(见上表)比较长久。如果实际应用中有这样的需求,就可以使用 DATETIME 类型。 TIMESTAMP TIMESTAMP 用于表示 年月日 时分秒,但是记录的年份(见上表)比较短暂。 TIMESTAMP 和时区相关,更能反映当前时间。当插入日期时,会先转换为本地时区后再存放;当查询日期时,会将日期转换为本地时区后再显示。所以不同时区的人看到的同一时间是 不一样的。 表中的第一个 TIMESTAMP 列自动设置为系统时间(CURRENT_TIMESTAMP)。当插入或更新一行,但没有明确给 TIMESTAMP 列赋值,也会自动设置为当前系统时间。如果表中有第二个 TIMESTAMP 列,则默认值设置为0000-00-00 00:00:00。 TIMESTAMP 的属性受 Mysql 版本和服务器 SQLMode 的影响较大。 如果记录的日期需要让不同时区的人使用,最好使用 TIMESTAMP。 DATE DATE 用于表示 年月日,如果实际应用值需要保存 年月日 就可以使用 DATE。 TIME TIME 用于表示 时分秒,如果实际应用值需要保存 时分秒 就可以使用 TIME。 YEAR YEAR 用于表示 年份,YEAR 有 2 位(最好使用4位)和 4 位格式的年。 默认是4位。如果实际应用只保存年份,那么用 1 bytes 保存 YEAR 类型完全可以。不但能够节约存储空间,还能提高表的操作效率。 --------------------------------------------------------------------------------------------------------------------------------------------------------------- 每种日期时间类型都有一个有效值范围,如果超出这个范围,在默认的SQLMode下会报错,并以零值(见上表)存储。 插入或更新时,日期时间类型允许“不严格”语法,以DATETIME为例(其他日期时间类型雷同): YYYY-MM-DD HH:MM:SS 或 YY-MM-DD HH:MM:SS 格式的字符串。任何符号都可以用作日期部分或时间部分的间隔符。例如:“14-06-18 14:54:10”、“14*06*18 14.54.10”、“14+06+18 14=54=10”是等价的。对于包含日期时间的字符串值,如果月、日、时、分、秒的值小于10,不需要指定两位数。例如:“2014-2-3 2:3:6”、“2014-02-03 02:03:06”是等价的。 YYYYMMDDHHMMSS 或 YYMMDDHHMMSS 格式的字符串。如果字符串对于日期时间类型是合法的就可以解释为日期时间类型。例如:“20140618145410” 和 “140618145410”将被解释为 “2014-06-18 14:54:10” ,但是 “20140618145480” 是不合法的(秒数不合法),将被解释为 “0000-00-00 00:00:00”。 YYYYMMDDHHMMSS 或 YYMMDDHHMMSS 格式的数字。如果该数字对日期时间类型是合法的就可以解释为日期时间类型。例如:“20140618145410” 和 “140618145410” 将被解释为 “2014-06-18 14:54:10” 。数值的长度应为6、8、12、14。如果数值长度是 8 或 14 位长,则假定为 YYYYMMDD 或 YYYYMMDDHHMMSS 格式。如果数值为 6 或 12 位长,则假定为 YYMMDD 或 YYMMDDHHMMSS 格式。
auto-import是什么意思呢? 我们经常会写这样一个HQL语句: from User u where u.name='罗灿锋'; 绝大多数时候,这样写是不会发生问题的。 hibernate在处理这个HQL时,会先将其翻译成一条数据库能够识别的sql语句。翻译的依据当然是实体与数据库表之间的映射关系了。 现在我们就给他制造一些问题,我们让hibernate同时管理两个相同名称的实体:org.mysoa.security.model.User和com.kedacom.ksoa.security.model.User。 这时,我们再将上面那条HQL给hibernate解析,他还能顺利地将其翻译成一条sql语句吗?答案当然是否定的,他不知道你要查 org.mysoa.security.model.User还是com.kedacom.ksoa.security.model.User。 所以,一条正确的HQL应该是这样的: from org.mysoa.security.model.User u where u.name='罗灿锋'; 但是,大多数时候,一个系统里不会出现同名的实体,如果要求所有HQL都这么写就不好了。所以hibernate提供一个auto-import属 性,当你不指定具体的实体时(只指定from User),他会自动找到唯一的名为User的实体映射,将其补全为org.mysoa.security.model.User。 当你的系统中确实要需要两个同名的实体时 当你的系统中确实要需要两个同名的实体时,我们需要做两件事: 将这两个同名的实体的映射文件都要设置为auto-import 所有关于这两个实体的HQL,都需要明确指定其全限定名(如org.mysoa.security.model.User) 有人要问了,只要你做了第二条就够了呀,只要你保证所有的HQL都写了全限定名,那么hibernate解析就不会出错,系统应该是可以运行的。其实不然。hibernate怎么知道你所有的HQL都写了全限定名?事实上,hibernate在系统加载过程中,如果发现有两个同名的实体,但 是有任何一个没有设置auto-import=false,他就会抛出异常并停止加载,他以这种方式来确保你的auto-import问题在系统加载时就 暴露出来,而不是延迟到真正执行一个有问题的HQL时才抛出问题。
http://blog.csdn.net/zhliro/article/details/45533911
1 基本信息 每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载。Java的类加载机制是技术体系中比较核心的部分,虽然和大部分开发人员直接打交道不多,但是对其背后的机理有一定理解有助于排查程序中出现的类加载失败等技术问题,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助。 2 Java虚拟机类加载器结构简述 2.1 JVM三种预定义类型类加载器 我们首先看一下JVM预定义的三种类型类加载器,当一个 JVM启动的时候,Java缺省开始使用如下三种类型类装入器: 启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将<Java_Runtime_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。 扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。 系统(System)类加载器:系统类加载器是由 Sun的AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。 除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器,这个将在后面单独介绍。 2.2 类加载双亲委派机制介绍和分析 在这里,需要着重说明的是,JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和扩展类加载器为例作简单分析。 图一 标准扩展类加载器继承层次图 图二系统类加载器继承层次图 通过图一和图二我们可以看出,类加载器均是继承自java.lang.ClassLoader抽象类。我们下面我们就看简要介绍一下java.lang.ClassLoader中几个最重要的方法: [java] view plain copy //加载指定名称(包括包名)的二进制类型,供用户调用的接口 public Class<?> loadClass(String name) throws ClassNotFoundException{ … } //加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是这里的resolve参数不一定真正能达到解析的效果),供继承用 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … } //findClass方法一般被loadClass方法调用去加载指定名称类,供继承用 protected Class<?> findClass(String name) throws ClassNotFoundException { … } //定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承 //(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了) protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … } 通过进一步分析标准扩展类加载器(sun.misc.Launcher$ExtClassLoader)和系统类加载器(sun.misc.Launcher$AppClassLoader)的代码以及其公共父类(java.net.URLClassLoader和java.security.SecureClassLoader)的代码可以看出,都没有覆写java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。既然这样,我们就可以通过分析java.lang.ClassLoader中的loadClass(String name)方法的代码就可以分析出虚拟机默认采用的双亲委派机制到底是什么模样: [java] view plain copy public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 首先判断该类型是否已经被加载 Class c = findLoadedClass(name); if (c == null) { //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载 try { if (parent != null) { //如果存在父类加载器,就委派给父类加载器加载 c = parent.loadClass(name, false); } else { //如果不存在父类加载器,就检查是否是由启动类加载器加载的类, //通过调用本地方法native findBootstrapClass0(String name) c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } 通过上面的代码分析,我们可以对JVM采用的双亲委派类加载机制有了更感性的认识,下面我们就接着分析一下启动类加载器、标准扩展类加载器和系统类加载器三者之间的关系。可能大家已经从各种资料上面看到了如下类似的一幅图片: 图三 类加载器默认委派关系图 上面图片给人的直观印象是系统类加载器的父类加载器是标准扩展类加载器,标准扩展类加载器的父类加载器是启动类加载器,下面我们就用代码具体测试一下: [java] view plain copy public class LoaderTest { public static void main(String[] args) { try { System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent()); System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); } catch (Exception e) { e.printStackTrace(); } } } 说明:通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器。 代码输出如下: [plain] view plain copy sun.misc.Launcher$AppClassLoader@6d06d69c sun.misc.Launcher$ExtClassLoader@70dea4e null 通过以上的代码输出,我们可以判定系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时确得到了null,就是说标准扩展类加载器本身强制设定父类加载器为null。我们还是借助于代码分析一下。 我们首先看一下java.lang.ClassLoader抽象类中默认实现的两个构造函数: [java] view plain copy protected ClassLoader() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } //默认将父类加载器设置为系统类加载器,getSystemClassLoader()获取系统类加载器 this.parent = getSystemClassLoader(); initialized = true; } protected ClassLoader(ClassLoader parent) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } //强制设置父类加载器 this.parent = parent; initialized = true; } 我们再看一下ClassLoader抽象类中parent成员的声明: [java] view plain copy // The parent class loader for delegation private ClassLoader parent; 声明为私有变量的同时并没有对外提供可供派生类访问的public或者protected设置器接口(对应的setter方法),结合前面的测试代码的输出,我们可以推断出: 1. 系统类加载器(AppClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为标准扩展类加载器(ExtClassLoader)。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。) 2. 扩展类加载器(ExtClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为null。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。) 现在我们可能会有这样的疑问:扩展类加载器(ExtClassLoader)的父类加载器被强制设置为null了,那么扩展类加载器为什么还能将加载任务委派给启动类加载器呢? 图四 标准扩展类加载器和系统类加载器成员大纲视图 图五 扩展类加载器和系统类加载器公共父类成员大纲视图 通过图四和图五可以看出,标准扩展类加载器和系统类加载器及其父类(java.net.URLClassLoader和java.security.SecureClassLoader)都没有覆写java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。有关java.lang.ClassLoader中默认的加载委派规则前面已经分析过,如果父加载器为null,则会调用本地方法进行启动类加载尝试。所以,图三中,启动类加载器、标准扩展类加载器和系统类加载器之间的委派关系事实上是仍就成立的。(在后面的用户自定义类加载器部分,还会做更深入的分析)。 2.3 类加载双亲委派示例 以上已经简要介绍了虚拟机默认使用的启动类加载器、标准扩展类加载器和系统类加载器,并以三者为例结合JDK代码对JVM默认使用的双亲委派类加载机制做了分析。下面我们就来看一个综合的例子。首先在IDE中建立一个简单的java应用工程,然后写一个简单的JavaBean如下: [java] view plain copy package classloader.test.bean; public class TestBean { public TestBean() { } } 在现有当前工程中另外建立一测试类(ClassLoaderTest.java)内容如下: 测试一: [java] view plain copy package classloader.test.bean; public class ClassLoaderTest { public static void main(String[] args) { try { //查看当前系统类路径中包含的路径条目 System.out.println(System.getProperty("java.class.path")); //调用加载当前类的类加载器(这里即为系统类加载器)加载TestBean Class typeLoaded = Class.forName("classloader.test.bean.TestBean"); //查看被加载的TestBean类型是被那个类加载器加载的 System.out.println(typeLoaded.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } } 对应的输出如下: [plain] view plain copy C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes sun.misc.Launcher$AppClassLoader@73d16e93 说明:当前类路径默认的含有的一个条目就是工程的输出目录。 测试二: 将当前工程输出目录下的TestBean.class打包进test.jar剪贴到<Java_Runtime_Home>/lib/ext目录下(现在工程输出目录下和JRE扩展目录下都有待加载类型的class文件)。再运行测试一测试代码,结果如下: [plain] view plain copy C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes sun.misc.Launcher$ExtClassLoader@15db9742 对比测试一和测试二,我们明显可以验证前面说的双亲委派机制,系统类加载器在接到加载classloader.test.bean.TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。 测试三: 将test.jar拷贝一份到<Java_Runtime_Home>/lib下,运行测试代码,输出如下: [plain] view plain copy C:\Users\JackZhou\Documents\NetBeansProjects\ClassLoaderTest\build\classes sun.misc.Launcher$ExtClassLoader@15db9742 测试三和测试二输出结果一致。那就是说,放置到<Java_Runtime_Home>/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。虚拟机出于安全等因素考虑,不会加载<Java_Runtime_Home>/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。做个进一步验证,删除<Java_Runtime_Home>/lib/ext目录下和工程输出目录下的TestBean对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException异常抛出。有关这个问题,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中设置相应断点运行测试三进行调试,会发现findBootstrapClass0()会抛出异常,然后在下面的findClass方法中被加载,当前运行的类加载器正是扩展类加载器(sun.misc.Launcher$ExtClassLoader),这一点可以通过JDT中变量视图查看验证。 3 java程序动态扩展方式 Java的连接模型允许用户运行时扩展引用程序,既可以通过当前虚拟机中预定义的加载器加载编译时已知的类或者接口,又允许用户自行定义类装载器,在运行时动态扩展用户的程序。通过用户自定义的类装载器,你的程序可以装载在编译时并不知道或者尚未存在的类或者接口,并动态连接它们并进行有选择的解析。 运行时动态扩展java应用程序有如下两个途径: 3.1 调用java.lang.Class.forName(…)加载类 这个方法其实在前面已经讨论过,在后面的问题2解答中说明了该方法调用会触发哪个类加载器开始加载任务。这里需要说明的是多参数版本的forName(…)方法: [java] view plain copy public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException 这里的initialize参数是很重要的。它表示在加载同时是否完成初始化的工作(说明:单参数版本的forName方法默认是完成初始化的)。有些场景下需要将initialize设置为true来强制加载同时完成初始化。例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题。因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用。这就要求驱动程序类必须被初始化,而不单单被加载。Class.forName的一个很常见的用法就是在加载数据库驱动的时候。如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。 3.2 用户自定义类加载器 通过前面的分析,我们可以看出,除了和本地实现密切相关的启动类加载器之外,包括标准扩展类加载器和系统类加载器在内的所有其他类加载器我们都可以当做自定义类加载器来对待,唯一区别是是否被虚拟机默认使用。前面的内容中已经对java.lang.ClassLoader抽象类中的几个重要的方法做了介绍,这里就简要叙述一下一般用户自定义类加载器的工作流程吧(可以结合后面问题解答一起看): 1、首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2; 2、委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,真实虚拟机中各种类加载器最终会呈现树状结构),如果父类加载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤3; 3、调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(…)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转而抛异常,终止加载过程(注意:这里的异常种类不止一种)。 说明:这里说的自定义类加载器是指JDK 1.2以后版本的写法,即不覆写改变java.lang.loadClass(…)已有委派逻辑情况下。 整个加载类的过程如下图: 图六 自定义类加载器加载类的过程 4 常见问题分析 4.1 由不同的类加载器加载的指定类还是相同的类型吗? 在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间。我们可以用两个自定义类加载器去加载某自定义类型(注意不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。 4.2 在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为? Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码: [java] view plain copy //java.lang.Class.java publicstatic Class<?> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader()); } //java.lang.ClassLoader.java // Returns the invoker's class loader, or null if none. static ClassLoader getCallerClassLoader() { // 获取调用类(caller)的类型 Class caller = Reflection.getCallerClass(3); // This can be null if the VM is requesting it if (caller == null) { return null; } // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader return caller.getClassLoader0(); } //java.lang.Class.java //虚拟机本地实现,获取当前类的类加载器,前面介绍的Class的getClassLoader()也使用此方法 native ClassLoader getClassLoader0(); 4.3 在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是谁? 前面讲过,在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下: [java] view plain copy //摘自java.lang.ClassLoader.java protected ClassLoader() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } this.parent = getSystemClassLoader(); initialized = true; } 我们再来看一下对应的getSystemClassLoader()方法的实现: [java] view plain copy private static synchronized void initSystemClassLoader() { //... sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); scl = l.getClassLoader(); //... } 我们可以写简单的测试代码来测试一下: [java] view plain copy System.out.println(sun.misc.Launcher.getLauncher().getClassLoader()); 本机对应输出如下: [plain] view plain copy sun.misc.Launcher$AppClassLoader@73d16e93 所以,我们现在可以相信当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。同时,我们可以得出如下结论:即使用户自定义类加载器不指定父类加载器,那么,同样可以加载如下三个地方的类: 1. <Java_Runtime_Home>/lib下的类; 2. < Java_Runtime_Home >/lib/ext下或者由系统变量java.ext.dir指定位置中的类; 3. 当前工程类路径下或者由系统变量java.class.path指定位置中的类。 4.4 在编写自定义类加载器时,如果将父类加载器强制设置为null,那么会有什么影响?如果自定义的类加载器不能加载指定类,就肯定会加载失败吗? JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器(这个问题前面已经分析过了)。同时,我们可以得出如下结论: 即使用户自定义类加载器不指定父类加载器,那么,同样可以加载到<Java_Runtime_Home>/lib下的类,但此时就不能够加载<Java_Runtime_Home>/lib/ext目录下的类了。 说明:问题3和问题4的推断结论是基于用户自定义的类加载器本身延续了java.lang.ClassLoader.loadClass(…)默认委派逻辑,如果用户对这一默认委派逻辑进行了改变,以上推断结论就不一定成立了,详见问题5。 4.5 编写自定义类加载器时,一般有哪些注意点? 1、一般尽量不要覆写已有的loadClass(...)方法中的委派逻辑 一般在JDK 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户覆写loadClass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findClass(…)逻辑。举一个例子来验证该问题: [java] view plain copy //用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑) public class WrongClassLoader extends ClassLoader { public Class<?> loadClass(String name) throws ClassNotFoundException { return this.findClass(name); } protected Class<?> findClass(String name) throws ClassNotFoundException { // 假设此处只是到工程以外的特定目录D:\library下去加载类 // 具体实现代码省略 } } 通过前面的分析我们已经知道,这个自定义类加载器WrongClassLoader的默认类加载器是系统类加载器,但是现在问题4种的结论就不成立了。大家可以简单测试一下,现在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工程类路径上的类都加载不上了。 [java] view plain copy //问题5测试代码一 public class WrongClassLoaderTest { publicstaticvoid main(String[] args) { try { WrongClassLoader loader = new WrongClassLoader(); Class classLoaded = loader.loadClass("beans.Account"); System.out.println(classLoaded.getName()); System.out.println(classLoaded.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } } 这里D:"classes"beans"Account.class是物理存在的。输出结果: [plain] view plain copy java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系统找不到指定的路径。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:106) at WrongClassLoader.findClass(WrongClassLoader.java:40) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:620) at java.lang.ClassLoader.defineClass(ClassLoader.java:400) at WrongClassLoader.findClass(WrongClassLoader.java:43) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27) Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:620) at java.lang.ClassLoader.defineClass(ClassLoader.java:400) at WrongClassLoader.findClass(WrongClassLoader.java:43) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27) 这说明,连要加载的类型的超类型java.lang.Object都加载不到了。这里列举的由于覆写loadClass()引起的逻辑错误明显是比较简单的,实际引起的逻辑错误可能复杂的多。 [java] view plain copy //问题5测试二 //用户自定义类加载器WrongClassLoader.Java(不覆写loadClass逻辑) public class WrongClassLoader extends ClassLoader { protected Class<?> findClass(String name) throws ClassNotFoundException { //假设此处只是到工程以外的特定目录D:\library下去加载类 //具体实现代码省略 } } 将自定义类加载器代码WrongClassLoader.Java做以上修改后,再运行测试代码,输出结果如下: [plain] view plain copy beans.Account WrongClassLoader@1c78e57 2、正确设置父类加载器 通过上面问题4和问题5的分析我们应该已经理解,个人觉得这是自定义用户类加载器时最重要的一点,但常常被忽略或者轻易带过。有了前面JDK代码的分析作为基础,我想现在大家都可以随便举出例子了。 3、保证findClass(String name)方法的逻辑正确性 事先尽量准确理解待定义的类加载器要完成的加载任务,确保最大程度上能够获取到对应的字节码内容。 4.6 如何在运行时判断系统类加载器能加载哪些路径下的类? 一是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到。 二是可以直接通过获取系统属性java.class.path来查看当前类路径上的条目信息 :System.getProperty("java.class.path")。 4.7 如何在运行时判断标准扩展类加载器能加载哪些路径下的类? 方法之一: [java] view plain copy import java.net.URL; import java.net.URLClassLoader; public class ClassLoaderTest { /** * @param args the command line arguments */ public static void main(String[] args) { try { URL[] extURLs = ((URLClassLoader) ClassLoader.getSystemClassLoader().getParent()).getURLs(); for (int i = 0; i < extURLs.length; i++) { System.out.println(extURLs[i]); } } catch (Exception e) { //… } } } 本机对应输出如下: [plain] view plain copy file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/access-bridge-64.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/cldrdata.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/dnsns.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/jaccess.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/jfxrt.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/localedata.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/nashorn.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunec.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunjce_provider.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunmscapi.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/sunpkcs11.jar file:/C:/Program%20Files/Java/jdk1.8.0_05/jre/lib/ext/zipfs.jar 5 开发自己的类加载器 在前面介绍类加载器的代理委派模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用defineClass来实现的;而启动类的加载过程是通过调用loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在Java虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。 方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。 类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。 在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输Java类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在Java虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。 5.1 文件系统类加载器 第一个类加载器用来加载存储在文件系统上的Java字节代码。完整的实现如下所示。 [java] view plain copy package classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; // 文件系统类加载器 public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } // 获取类的字节码 @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); // 获取类的字节数组 if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { // 读取类文件的字节 String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; // 读取类文件的字节码 while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { // 得到类文件的完全路径 return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } } 如上所示,类 FileSystemClassLoader继承自类java.lang.ClassLoader。在java.lang.ClassLoader类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现。该方法会首先调用findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。 类 FileSystemClassLoader的 findClass()方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过defineClass()方法来把这些字节代码转换成 java.lang.Class类的实例。 加载本地文件系统上的类,示例如下: [java] view plain copy package com.example; public class Sample { private Sample instance; public void setSample(Object instance) { System.out.println(instance.toString()); this.instance = (Sample) instance; } } [java] view plain copy package classloader; import java.lang.reflect.Method; public class ClassIdentity { public static void main(String[] args) { new ClassIdentity().testClassIdentity(); } public void testClassIdentity() { String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes"; FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); String className = "com.example.Sample"; try { Class<?> class1 = fscl1.loadClass(className); // 加载Sample类 Object obj1 = class1.newInstance(); // 创建对象 Class<?> class2 = fscl2.loadClass(className); Object obj2 = class2.newInstance(); Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); setSampleMethod.invoke(obj1, obj2); } catch (Exception e) { e.printStackTrace(); } } } 运行输出:com.example.Sample@7852e922 5.2 网络类加载器 下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。 类 NetworkClassLoader负责通过网络下载Java类字节代码并定义出Java类。它的实现与FileSystemClassLoader类似。 [java] view plain copy package classloader; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL; public class NetworkClassLoader extends ClassLoader { private String rootUrl; public NetworkClassLoader(String rootUrl) { // 指定URL this.rootUrl = rootUrl; } // 获取类的字节码 @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { // 从网络上读取的类的字节 String path = classNameToPath(className); try { URL url = new URL(path); InputStream ins = url.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; // 读取类文件的字节 while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { // 得到类文件的URL return rootUrl + "/" + className.replace('.', '/') + ".class"; } } 在通过NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用Java反射API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用Java反射API可以直接调用Java类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。我们使用接口的方式。示例如下: 客户端接口: [java] view plain copy package classloader; public interface Versioned { String getVersion(); } [java] view plain copy package classloader; public interface ICalculator extends Versioned { String calculate(String expression); } 网络上的不同版本的类: [java] view plain copy package com.example; import classloader.ICalculator; public class CalculatorBasic implements ICalculator { @Override public String calculate(String expression) { return expression; } @Override public String getVersion() { return "1.0"; } } [java] view plain copy package com.example; import classloader.ICalculator; public class CalculatorAdvanced implements ICalculator { @Override public String calculate(String expression) { return "Result is " + expression; } @Override public String getVersion() { return "2.0"; } } 在客户端加载网络上的类的过程: [java] view plain copy package classloader; public class CalculatorTest { public static void main(String[] args) { String url = "http://localhost:8080/ClassloaderTest/classes"; NetworkClassLoader ncl = new NetworkClassLoader(url); String basicClassName = "com.example.CalculatorBasic"; String advancedClassName = "com.example.CalculatorAdvanced"; try { Class<?> clazz = ncl.loadClass(basicClassName); // 加载一个版本的类 ICalculator calculator = (ICalculator) clazz.newInstance(); // 创建对象 System.out.println(calculator.getVersion()); clazz = ncl.loadClass(advancedClassName); // 加载另一个版本的类 calculator = (ICalculator) clazz.newInstance(); System.out.println(calculator.getVersion()); } catch (Exception e) { e.printStackTrace(); } } }
Location 对象 Location 对象 Location 对象包含有关当前 URL 的信息。 Location 对象是 Window 对象的一个部分,可通过 window.location 属性来访问。 例子 把用户带到一个新的地址 Location 对象属性 属性 描述 hash 设置或返回从井号 (#) 开始的 URL(锚)。 host 设置或返回主机名和当前 URL 的端口号。说明:大部分情况下都是使用的 80 端口作为 http 服务端口,而 80 端口都是默认省略的,因此这种情况下,location.port 不会得到任何内容。 hostname 设置或返回当前 URL 的主机名。 href 设置或返回完整的 URL。 pathname 设置或返回当前 URL 的路径部分。 port 设置或返回当前 URL 的端口号。 protocol 设置或返回当前 URL 的协议。 search 设置或返回从问号 (?) 开始的 URL(查询部分)。 Location 对象方法 属性 描述 assign() 加载新的文档。 reload() 重新加载当前文档。 replace() 用新的文档替换当前文档。 Location 对象描述 Location 对象存储在 Window 对象的 Location 属性中,表示那个窗口中当前显示的文档的 Web 地址。它的 href 属性存放的是文档的完整 URL,其他属性则分别描述了 URL 的各个部分。这些属性与 Anchor 对象(或 Area 对象)的 URL 属性非常相似。当一个 Location 对象被转换成字符串,href 属性的值被返回。这意味着你可以使用表达式 location 来替代 location.href。 不过 Anchor 对象表示的是文档中的超链接,Location 对象表示的却是浏览器当前显示的文档的 URL(或位置)。但是 Location 对象所能做的远远不止这些,它还能控制浏览器显示的文档的位置。如果把一个含有 URL 的字符串赋予 Location 对象或它的 href 属性,浏览器就会把新的 URL 所指的文档装载进来,并显示出来。 除了设置 location 或 location.href 用完整的 URL 替换当前的 URL 之外,还可以修改部分 URL,只需要给 Location 对象的其他属性赋值即可。这样做就会创建新的 URL,其中的一部分与原来的 URL 不同,浏览器会将它装载并显示出来。例如,假设设置了Location对象的 hash 属性,那么浏览器就会转移到当前文档中的一个指定的位置。同样,如果设置了 search 属性,那么浏览器就会重新装载附加了新的查询字符串的 URL。 除了 URL 属性外,Location 对象的 reload() 方法可以重新装载当前文档,replace() 可以装载一个新文档而无须为它创建一个新的历史记录,也就是说,在浏览器的历史列表中,新文档将替换当前文档。
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.cim.domain.dto.JSONPObject; import org.springframework.http.HttpOutputMessage; import org.springframework.http.converter.HttpMessageNotWritableException; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; /** * @author 郑明亮 * @time 2017年6月1日 上午11:51:57 * @description <p>自定义转换器,拼接jsonp格式数据 </p> * @modifyBy * @modifyTime * @modifyDescription<p> </p> */ public class MJFastJsonHttpMessageConverter extends FastJsonHttpMessageConverter { public static final Charset UTF8 = Charset.forName("UTF-8"); private Charset charset; private SerializerFeature[] features; public MJFastJsonHttpMessageConverter() { super(); this.charset = UTF8; this.features = new SerializerFeature[0]; } @Override protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // obj就是controller中注解为@ResponseBody的方法返回值对象 if(obj instanceof JSONPObject){ JSONPObject jsonpObject = (JSONPObject)obj; OutputStream out = outputMessage.getBody(); String text = JSON.toJSONString(jsonpObject.getJson(), this.features); String jsonpText = new StringBuilder(jsonpObject.getFunction()).append("(").append(text).append(")").toString(); byte[] bytes = jsonpText.getBytes(this.charset); out.write(bytes); }else{ super.writeInternal(obj, outputMessage); } } } 修改spring-mvc.xml 将spring-mvc.xml中原来fastjson的转换器引用类改成自定义的Converter类 <!-- 避免IE执行AJAX时,返回JSON出现下载文件 --><!-- com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter --> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <!--改成自己的自定义转换类 --> <bean id="jsonConverter" class="com.zml.common.util.MJFastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json;charset=UTF-8</value> </list> </property> <property name="features"> <array> <value>WriteMapNullValue</value> <value>WriteNullStringAsEmpty</value> <value>QuoteFieldNames</value> <value>DisableCircularReferenceDetect</value> </array> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>Controller 方法实例 注意返回类型写成Object,以及添加入参callback,根据callback是否有值来判断返回格式为json还是jsonp /** * @author 郑明亮 * @time 2017年6月1日 下午6:48:40 * @description <p> 根据条件查询mongoDB监控日志信息</p> * @modifyBy * @modifyTime * @modifyDescription<p> </p> * @param vo 日志查询条件扩展类 * @param typeList 监控日志类型,增 1 删2 改3 查4 * @param callback jsonp回调方法名称 * @return */ @RequestMapping("/queryMonitorLogs") @ResponseBody public Object queryMonitorLogs(MonitorLogVO vo,String collectionName,Integer [] typeList,String callback){ log.info("---入参:---"+vo); if (typeList != null && typeList.length > 0) { vo.setTypeList(Arrays.asList(typeList)); } Tidings<Page<MonitorLog>> tidings = new Tidings<>(); String msg = "查询异常"; String status = ERROR; Page<MonitorLog> page = null; try { page = monitorLogService.findByVO(vo,collectionName); if (page.getTotalCount() == 0) { msg = "查询成功,但未查询到数据"; status = SUCCESS_BUT_NULL; }else { msg = "查询成功"; status = SUCCESS; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } tidings.setMsg(msg); tidings.setStatus(status); tidings.setData(page); log.info("---出参:---"+tidings); if (callback != null) { return new JSONPObject(callback,tidings); } return tidings; }
非静态代码块如: [java] view plain copy { System.out.println("2"); } 以上代码块就是非静态的代码块,请注意这里的方法名、作用域、返回值、参数一概没有,非静态代码块会在每次类被调用或者被实例化时就会被执行。 静态代码块如: [java] view plain copy static { System.out.println("3"); } 上面的代码块就是静态的代码块,同样没有方法名、作用域、返回值以及参数,静态代码块类似于静态变量,不论类被调用多少次,该区域代码只在第一次时执行一次 大家可以通过下面的代码做进一步的测试 [java] view plain copy public class test1 { public static void main(String[] args) { // TODO Auto-generated method stub new test1(); new test1(); } { System.out.println("2"); } static { System.out.println("3"); } public test1(){ System.out.println("1"); } } 输出结果为 32121 从结果中发现静态代码块以及非静态代码块都会在构造函数前执行,首次访问时,静态代码块会在非静态代码块前执行 静态代码块是在类加载时自动执行的,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块
数据排序:http://www.365mini.com/page/tag/%E6%95%B0%E6%8D%AE%E6%8E%92%E5%BA%8F //冒泡排序 int[] arr={123,223,66,883,223,34,763,256,44,99,88}; for(int i=0;i<arr.length;i++){ for(int j=0;j<arr.length-1-i;j++){ if(arr[j]>arr[j+1]){ int temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } System.out.println("冒泡排序:"+Arrays.toString(arr)); //选择排序 int[] arr1={123,223,66,883,223,34,763,256,44,99,88}; for(int i=0;i<arr1.length;i++){ for(int j=i+1;j<arr1.length;j++){ if(arr1[i]>arr1[j]){ int temp=arr1[j]; arr1[j]=arr1[i]; arr1[i]=temp; } } } System.out.println("选择排序:"+Arrays.toString(arr1)); //插入排序 int[] arr2={123,223,66,883,223,34,763,256,44,99,88}; for(int i=0;i<arr2.length;i++){ for(int j=0;j<i+1;j++){ if(arr2[j]>arr2[i]){ int temp=arr2[i]; arr2[i]=arr2[j]; arr2[j]=temp; } } } System.out.println("插入排序:"+Arrays.toString(arr2)); }
浏览器会在其打开一个 HTML 文档时创建一个对应的 window 对象。但是,如果一个文档定义了一个或多个框架(即,包含一个或多个 frame 或 iframe 标签),浏览器就会为原始文档创建一个 window 对象,再为每个框架创建额外的 window 对象。这些额外的对象是原始窗口的 子窗口,可能被原始窗口中发生的事件所影响。例如,关闭原始窗口将导致关闭全部子窗口。如果想要创建新窗口(以及对应的 window 对象),可以使用像 open, showModalDialog 和 showModelessDialog 这样的方法。
jsp九大内置对象 request对象 : getParameter(String name)获取表单提交的数据 getParamegerNames() 获取客户端提交的所有参数名 getAttribute(String name)获取name 指定的属性值 getAttributeNames 获取request对象所有属性的名称集合 getSession(Boolean create) 获取HttpSession对象 response 对象:对象用于对客户端的请求作出动态的响应,向客户端发送数据 getCharacterEncoding() 返回响应用的字符编码格式 getOutputStream() 返回响应的输出流 getWriter() 返回可以向客户端输出字符的一个对象 session 对象:从一个客户打开浏览器并连接到服务器开始,到客户关闭浏览器离开这个服务器结束,整个阶段被称为一个会话。session对象可以用来保存用户的会话状态。 exception对象:用于处理Jsp页面中发生的错误和异常,可以帮助我们了解并处理页面中的错误信息 page对象:就是当前Jsp页面本书,类似于Java中的this getClass() 获取page对象的类 hashCode() 获取page对象的hash码 equal(Object obj) 判断page对象是否与参数中的obj相等 copy(Object obj)把此page对象复制到指定的Object对象中 clone() 克隆当前的page对象 pageContext对象 :用于管理对属于Jsp中特殊可见部分中已经命名对象的访问 application对象:实现了用户间数据的共享,可存放全局变量 config对象:用来获取服务器初始化配置参数 getServletContext() 获取当前的Servlet上下文 getInitParameter(String name) 获取指定的初始参数的值 getInitParameterNames()获取所有的初始参数的值 getServletName() 获取当前的Servlet名称 out 对象:用来向客户端输出各种数据 print()/println() 输出各种类型数据 clearBuffer() 清除缓冲区的数据,并将数据写入客户端 clear() 清除缓冲区的数据,但不将数据写入客户端 close() 关闭输出流 jsp内置对象对应的java对象 jsp内置对象 servlet中java对象 request HttpServletRequest response HttpServletResponse session HttpSession exception Throwable page this pageContext PageContext application ServletContext config ServletConfig out JspWriter
HttpServlet里的三个方法:service(HttpServletRequest req, HttpServletResponse resp) ,doGet(HttpServletRequest req, HttpServletResponse resp), doPost(HttpServletRequest req, HttpServletResponse res)的区别和联系: 在servlet中默认情况下,无论你是get还是post 提交过来 都会经过service()方法来处理,然后转向到doGet 或是doPost方法,我们可以查看HttpServlet 类的service方法: 我在tomcat的lib目录下,解压servlet-api.jar,然后用反编译软件把lib\javax\servlet\http下的HttpServlet.class反编译,看里头的service()方法的原代码:查看源码发现,httpservlet继承genericservlet并实现了service方法,从实现来看service起到了调度的作用。因此自定义servlet继承httpservlet不需要覆盖重写service方法,只需覆盖重写doget或者dopost方法。 注意,sun只是定义了servlet接口,而实现servlet接口的就是类似于tomcat的服务器,所以我是在tomcat的安装目录下找到实现的类。 public abstract class HttpServlet extends GenericServlet { protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if(method.equals("GET")) { long lastModified = getLastModified(req); if(lastModified == -1L) { doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader("If-Modified-Since"); if(ifModifiedSince < (lastModified / 1000L) * 1000L) { maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(304); } } } else if(method.equals("HEAD")) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if(method.equals("POST")) doPost(req, resp); else if(method.equals("PUT")) doPut(req, resp); else if(method.equals("DELETE")) doDelete(req, resp); else if(method.equals("OPTIONS")) doOptions(req, resp); else if(method.equals("TRACE")) { doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object errArgs[] = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } }} 从上面可以看出 这里的service是用来转向的,但是如果你在自己的servlet类中覆盖了service方法,比如说你的service是这样的: Java代码 1.publicvoid service(ServletRequest req, ServletResponse res) 2. throws ServletException, IOException { 3. res.getOutputStream().print( 4. "image is <img src='images/downcoin.gif'></img><br>"); 5. } 那么这时service就不是用来转向的,而是用来处理业务的,现在不论你的客户端是用pos还是get来请求此servlet 都会执行service方法也只能执行servlet方法,不会去执行doPost或是doGet方法。 比如说:你的客户端代码是: Java代码 1. <%@page contentType="text/html; charset=utf-8"%> 2. <html> 3. <head><title>选择</title></head> 4. <body> 5. 请选择你喜欢的水果:<br> 6. <form action ="Test" method = "post"> 7. <input type="checkbox" name="fruit" value ="apple" >苹果<br> 8. <input type="checkbox" name="fruit" value ="orange">桔子<br> 9. <input type="checkbox" name="fruit" value ="mango">芒果<br> 10. <input type="submit" value="提交"> 11. </form> 12. </body> 13. </html> 14. 15. 服务端servlet是:Test类 16. 17.import java.io.IOException; 18. 19.import javax.servlet.ServletException; 20.import javax.servlet.ServletOutputStream; 21.import javax.servlet.ServletRequest; 22.import javax.servlet.ServletResponse; 23.import javax.servlet.http.HttpServlet; 24.import javax.servlet.http.HttpServletRequest; 25.import javax.servlet.http.HttpServletResponse; 26. 27./** 28. * 演示service方法 29. */ 30.publicclass Testextends HttpServlet { 31. 32.publicvoid service(ServletRequest req, ServletResponse res) 33. throws ServletException, IOException { 34. res.getOutputStream().print("This is the service"); 35. 36. } 37. 38.protectedvoid doGet(HttpServletRequest request, 39. HttpServletResponse response)throws ServletException, IOException { 40. doPost(request,response); 41. 42. } 43.protectedvoid doPost(HttpServletRequest request, 44. HttpServletResponse response)throws ServletException, IOException { 45. ServletOutputStream out=response.getOutputStream(); 46. String[] args=(String[])request.getParameterValues("fruit"); 47. for(int i=0;i<args.length;i++){ 48. out.print(args[i]+"<br>"); 49. } 50. 51. } 52. } 点击提交后:页面输出结果为“This is the service“;
2. 乱码问题解决 基础知识 1)浏览器会在中文的UTF-8后加上上%得到URL编码 例如: %e8%b4%b9%e7%94%a8%e6%8a%a5%e9%94%80 2)以get的请求发送到tomcat服务器后又会以默认的(ISO8859-1)解码!!(tomcat7及以前版本是ISO8859-1) 3)所以在action 中要先以ISO8859-1解码,在一UTF-8编码得到中文字符 表单采用Post方式提交,解决乱码的方法为: request.setCharacterEncoding( myEncoding ); 表单采用Get方式提交,解决乱码的方法为: 方式一: key = new String(key.getBytes("iso8859-1"), "utf-8"); 方式二: 修改server.xml: URIEncoding="utf-8" 默认情况下,tomcat使用的的编码方式:iso8859-1 修改tomcat下的conf/server.xml文件 找到如下代码: <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />这段代码规定了Tomcat监听HTTP请求的端口号等信息。 可以在这里添加一个属性:URIEncoding,将该属性值设置为UTF-8,即可让Tomcat(默认ISO-8859-1编码)以UTF-8的编码处理get请求。 修改完成后: <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> http://localhost:8080/ItcastOA20160614/processDefinition_delete.action?key=%25E8%25B4%25B9%25E7%2594%25A8%25E6%258A%25A5%25E9%2594%2580%25E6%25B5%2581%25E7%25A8%258B 方式三(不依赖Tomcat的配置,推荐): 浏览器中两次URL编码。 服务器中自己再做一次URL解码。 原理: 如果只进行一次encodeURI,得到的是UTF-8形式的URL,服务器端通过request.getParameter()解码查询参数(通常是iso-8859-1)就会得到乱码。 如果进行两次encodeURI,第一次编码得到的是UTF-8形式的URL,第二次编码得到的依然是UTF-8形式的URL,但是在效果上相当于首先进行了一次UTF-8编码(此时已经全部转换为ASCII字符),再进行了一次iso-8859-1编码,因为对英文字符(ASCII字符)来说UTF-8编码和ISO-8859-1编码的效果相同。在服务器端,首先通过request.getParameter()自动进行第一次解码(可能是gb2312,gbk,utf-8,iso-8859-1等字符集,对结果无影响)得到ascii字符,然后再使用UTF-8进行第二次解码,通常使用java.net.URLDecoder("","UTF-8")方法。 两次编码两次解码的过程为: UTF-8编码->UTF-8(iso-8859-1)编码->iso-8859-1解码->UTF-8解码,编码和解码的过程是对称的,所以不会出现乱码。 自己 <s:a action="processDefinitionAction_delete" onclick="return delConfirm()"> <s:param name="key" value="%{@java.net.URLEncoder@encode(key, 'utf-8')}"></s:param> 删除 </s:a> %{@java.net.URLEncoder@encode(key, 'utf-8')} <s:a action="processDefinitionAction_delete" onclick="return delConfirm()"> <s:param name="key" value="%{@java.net.URLEncoder@encode(key, 'utf-8')}"> </s:param> 删除 </s:a> 此处自己先编码一次,发出请求时,浏览器又会编码一次,所以是两次编码 <script type="text/javascript"> function showProcessImage( pdId ){ // alert("原文:" + pdId); pdId = encodeURI(pdId); // alert("第一次URL编码:" + pdId); pdId = encodeURI(pdId); // alert("第二次URL编码:" + pdId); var url = "processDefinitionAction_downloadProcessImage.action?id=" + pdId + "&t=" + new Date(); window.showModalDialog(url, null, "dialogHeight:500px;dialogWidth:600px;resizable:yes"); } </script> 此处需要自己编码两次 总结:在页面encodeURI("url")后浏览器就不会给中文utf-8之后又加%,中间过程任何其他编码都是兼容英文(ASCII字符),所以在action代码里URLDecoder.decode("key","UTF-8")就可以了。
GET和POST的本质区别是什么? 使用GET,form中的数据将编码到url中,而使用POST的form中的数据则在http协议的header中传输。在使用上,当且仅当请求幂等(字面意思是请求任意次返回同样的结果,本质是请求本身不会改变服务器数据和状态)时使用GET,当请求会改变服务器数据或状态时(更新数据,上传文件),应该使用POST。区别使用GET,POST意义何在? 重复访问使用GET方法请求的页面,浏览器会使用缓存处理后续请求。使用POST方法的form提交时,浏览器机遇POST将产生永久改变的假设,将让用户进行提交确认。当编成人员正确的使用GET,POST后,浏览器会给出很好的缓存配合,时响应速度更快。在form提交阶段的差别 form提交的第一步是创建数据集,并根据ENCTYPE对数据集进行编码。ENCTYPE有两个值:multipart/form-data,application/x-www-form-urlencoded(默认值),前者可同时用于GET,POST,后者只用于POST。然后进行数据传输--对于GET方法,数据集使用content type application/x-www-form-urlencoded编码并附在url后面,在这种模式下,数据严格限制为ASCII码;对于POST,使用content type编码字符集并将其构造成消息发送。在服务器处理部分的差别 原则上,除理GET和POST请求是没有分别的。但由于数据通过不同的方法编码,需要有不同的解码机制。所以,方法变化将导致处理请求的代码变化。比如对于cgi,处理GET时通过环境变量获得参数,处理POST请求时则通过标准输入(stdin) 获得数据。从使用经验,我们有如下总结:1、get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。2、对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。两种方式的参数都可以用Request来获得。3、get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。4、get安全性非常低,post安全性较高。5、<form method="get" action="a.asp?b=b">跟<form method="get" action="a.asp">是一样的,也就是说,action页面后边带的参数列表会被忽视;而<form method="post" action="a.asp?b=b">跟<form method="post" action="a.asp">是不一样的。 另外 Get请求有如下特性:它会将数据添加到URL中,通过这种方式传递到服务器,通常利用一个问号?代表URL地址的结尾与数据参数的开端,后面的参数每一个数据参数以“名称=值”的形式出现,参数与参数之间利用一个连接符&来区分。Post请求有如下特性:数据是放在HTTP主体中的,其组织方式不只一种,有&连接方式,也有分割符方式,可隐藏参数,传递大批数据,比较方便 一、问题: 编码问题是JAVA初学者在web开发过程中经常会遇到问题,网上也有大量相关的文章介绍,但其中很多文章并没有对URL中使用了中文等非ASCII的字 符造成服务器后台程序解析出现乱码的问题作出准确的解释和说明。本文将详细介绍由于在URL中使用了中文等非ASCII的字符造成乱码的问题。 1、在URL中中文字符通常出现在以下两个地方:(1)、Query String中的参数值,比如http://search.china.alibaba.com/search/offer_search.htm?keywords=中国(2)、servlet path,比如:http://search.china.alibaba.com/selloffer/中国.html 2、出现乱码问题的原因主要是以下几方面:(1)、浏览器:我们的客户端(浏览器)本身并没有遵循URI编码的规范(http://www.w3.org/International/O-URL-code.html)。(2)、Servlet服务器:Servlet服务器的没有正确配置。(3)、开发人员并不了解Servlet的规范和API的含义。 二、基础知识:1、一个http请求经过的几个环节:浏览器(ie firefox)【get/post】------------>Servlet服务器------------------------------->浏览器显示 编码 解码成unicode,然后将显示的内容编码 解码(1) 浏览器把URL(以及post提交的内容)经过编码后发送给服务器。(2) 这里的Servlet服务器实际上指的是由Servlet服务器提供的servlet实现ServletRequestWrapper,不同应用服务器的 servlet实现不同,这些servlet的实现把这些内容解码转换为unicode,处理完毕后,然后再把结果(即网页)编码返回给浏览器。(3) 浏览器按照指定的编码显示该网页。 当对字符串进行编码和解码的时候都涉及到字符集,通常使用的字符集为ISO8859-1、GBK、UTF-8、UNICODE。 2、URL的组成:域名:端口/contextPath/servletPath/pathInfo?queryString说明: 1、ContextPath是在Servlet服务器的配置文件中指定的。对于weblogic:contextPath是在应用的weblogic.xml中配置。 <context-root>/</context-root> 对于tomcat:contextPath是在server.xml中配置。<Context path="/" docBase="D:/server/blog.war" debug="5" reloadable="true" crossContext="true"/> 对于jboos:contextPath是在应用的jboss-web.xml中配置。<jboss-web> <context-root>/</context-root></jboss-web> 2、ServletPath是在应用的web.xml中配置。<servlet-mapping> <servlet-name>Example</servlet-name> <url-pattern>/example/*</url-pattern></servlet-mapping> 2、Servlet API我们使用以下servlet API获得URL的值及参数。request.getParameter("name"); // 获得queryString的参数值(来自于get和post),其值经过Servlet服务器URL Decode过的request.getPathInfo(); // 注意:pathinfo返回的字符串是经过Servlet服务器URL Decode过的。requestURI = request.getRequestURI(); // 内容为:contextPath/servletPath/pathinfo 浏览器提交过来的原始数据,未被Servlet服务器URL Decode过。 3、开发人员必须清楚的servlet规范:(1) HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没 有说明这一点。(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。(4) POST提交的数据是作为request body的一部分。(5) 网页的Http头中ContentType("text/html; charset=GBK")的作用: (a) 告诉浏览器网页中数据是什么编码; (b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。 这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。 三、下面我们分别从浏览器和应用服务器来举例说明:URL:http://localhost:8080/example/中国?name=中国汉字 编码 二进制表示中国 UTF-8 0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67]中国 GBK 0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6]中国 ISO8859-1 0x3f,0x3f[63, 63]信息失去 (一)、浏览器1、GET方式提交,浏览器会对URL进行URL encode,然后发送给服务器。(1) 对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo是URL Encode是按照UTF-8编码,QueryString是按照GBK编码。http://localhost:8080/example/中国?name=中国实际上提交是:GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA (1) 对于中文IE,如果在高级选项中取消总以UTF-8发送,则PathInfo和QueryString是URL encode按照GBK编码。实际上提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA (3) 对于中文firefox,则pathInfo和queryString都是URL encode按照GBK编码。实际上提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA 很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。对于中文的IE和FIREFOX都是采用GBK编码QueryString。 小结:解决方案:1、URL中如果含有中文等非ASCII字符,则浏览器会对它们进行URLEncode。为了避免浏览器采用了我们不希望的编码,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode编码过的字符串%.比如:URL:http://localhost:8080/example/中国?name=中国建议:URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA 2、我们建议URL中PathInfo和QueryString采用相同的编码,这样对服务器端处理的时候会更加简单。 2、还有一个问题,我发现很多程序员并不明白URL Encode是需要指定字符集的。不明白的人可以看看这篇文档:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html 2、 POST提交 对于POST方式,表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType("text/html; charset=GBK")中指定的编码进行对表单中的数据进行编码,然后发给服务器。在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。 解决方案:1、从最简单,所需代价最小来看,我们对URL以及网页中的编码使用统一的编码对我们来说是比较合适的。如果不使用统一编码的话,我们就需要在程序中做一些编码转换的事情。这也是我们为什么看到有网络上大量的资料介绍如何对乱码进行处理,其中很多解决方案都只是一时的权宜之计,没有从根本上解决问题。 (二)、Servlet服务器 Servlet服务器实现的Servlet遇到URL和POST提交的数据中含有%的字符串,它会按照指定的字符集解码。下面两个Servlet方法返回的结果都是经过解码的:request.getParameter("name"); request.getPathInfo(); 这里所说的"指定的字符集"是在应用服务器的配置文件中配置。 (1) tomcat服务器对于tomcat服务器,该文件是server.xml<Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000" redirectPort="8443" URIEncoding="GBK"/>URIEncoding告诉服务器servlet解码URL时采用的编码。 <Connector port="8080" ... useBodyEncodingForURI="true" />useBodyEncodingForURI告诉服务器解码URL时候需要采用request body指定的编码。 (2) weblogic服务器对于weblogic服务器,该文件是weblogic.xml <input-charset> <java-charset-name>GBK</java-charset-name></input-charset> (三)浏览器显示 浏览器根据http头中的ContentType("text/html; charset=GBK"),指定的字符集来解码服务器发送过来的字节流。我们可以调用 HttpServletResponse.setContentType()设置http头的ContentType。 总结:1、URL中的PathInfo和QueryString字符串的编码和解码是由浏览器和应用服务器的配置决定的,我们的程序不能设置,不要期望用request.setCharacterEncoding()方法能设置URL中参数值解码时的字符集。所以我们建议URL中不要使用中文等非ASCII字符,如果含有非ASCII字符的话要使用URLEncode编码一下,比如:http://localhost:8080/example1/example/中国正确的写法:http://localhost:8080/example1/example/%E4%B8%AD%E5%9B%BD并且我们建议URL中不要在PathInfo和QueryString同时使用非ASCII字符,比如http://localhost:8080/example1/example/中国?name=中国原因很简单:不同浏览器对URL中PathInfo和QueryString编码时采用的字符集不同,但应用服务器对URL通常会采用相同的字符集来解码。 2、我们建议URL中的URL Encode编码的字符集和网页的contentType的字符集采用相同的字符集,这样程序的实现就很简单,不用做复杂的编码转换。
java.endorsed.dirs java.ext.dirs 用于扩展jdk的系统库,那么 -Djava.endorsed.dirs 又有什么神奇的作用呢? java提供了endorsed技术: 关于endorsed:可以的简单理解为-Djava.endorsed.dirs指定的目录面放置的jar文件,将有覆盖系统API的功能。可以牵强的理解为,将自己修改后的API打入到JVM指定的启动API中,取而代之。但是能够覆盖的类是有限制的,其中不包括java.lang包中的类。 这有什么用? 比如Java的原生api不能满足需求,假设我们需要修改 ArrayList 类,由于我们的代码都是基于ArrayList做的,那么就必需用到 Java endorsed 技术,将我们自己的ArrayList,注意包和类名和java自带的都是一样的,打包成一个jar包,放入到-Djava.endorsed.dirs指定的目录中,这样我们在使用java的ArrayList的时候就会调用的我们定制的代码中,是不是很酷!! 根据官方文档描述:如果不想添加-D参数,如果我们希望基于这个JDK下的都统一改变,那么我们可以将我们修改的jar放到: $JAVA_HOME/jre/lib/endorsed 这样基于这个JDK的所有的ArrayList都改变了!!! 注意: 能够覆盖的类是有限制的,其中不包括java.lang包中的类,比如java.lang.String这种 就不行 endorsed目录:.[jdk安装目录]./jre/lib/endorsed,不是jdk/lib/endorsed,目录中放的是Jar包,不是.java或.class文件,哪怕只重写了一个类也要打包成jar包 可以在dos模式查看修改后的效果(javac、java),在eclipse需要将运行选项中的JRE栏设置为jre(若设置为jdk将看不到效果)。 重写的类必须满足jdk中的规范,例如:自定义的ArrayList类也必须实现List等接口。 System.out.println(System.getProperty("java.endorsed.dirs"));
转自:http://www.cnblogs.com/qixiaoyizhan/ Restful风格的API是一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。 在Restful风格中,用户请求的url使用同一个url而用请求方式:get,post,delete,put...等方式对请求的处理方法进行区分,这样可以在前后台分离式的开发中使得前端开发人员不会对请求的资源地址产生混淆和大量的检查方法名的麻烦,形成一个统一的接口。 在Restful风格中,现有规定如下: GET(SELECT):从服务器查询,可以在服务器通过请求的参数区分查询的方式。 POST(CREATE):在服务器新建一个资源,调用insert操作。 PUT(UPDATE):在服务器更新资源,调用update操作。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。(目前jdk7未实现,tomcat7也不行)。 DELETE(DELETE):从服务器删除资源,调用delete语句。 了解这个风格定义以后,我们举个例子: 如果当前url是 http://localhost:8080/User 那么用户只要请求这样同一个URL就可以实现不同的增删改查操作,例如 http://localhost:8080/User?_method=get&id=1001 这样就可以通过get请求获取到数据库 user 表里面 id=1001 的用户信息 http://localhost:8080/User?_method=post&id=1001&name=zhangsan 这样可以向数据库 user 表里面插入一条记录 http://localhost:8080/User?_method=put&id=1001&name=lisi 这样可以将 user表里面 id=1001 的用户名改为lisi http://localhost:8080/User?_method=delete&id=1001 这样用于将数据库 user 表里面的id=1001 的信息删除 这样定义的规范我们就可以称之为restful风格的API接口,我们可以通过同一个url来实现各种操作。 接下来我们讲解spring-mvc中是如何实现restful风格API接口的,并且对其中出现的问题进行解决!(java web对 put 和 delete 请求的不支持问题) 首先我们搭建好spring mvc的项目接口,并按照restful风格写好控制器,这里我写了一个User控制器类和一个User "Action" -> 这里的controller 和 action的url地址是按照restful风格编写的 访问地址 /User/User 用method区分请求方法 我们的前台使用的是jquery ajax进行请求-> 有人会问了? 为什么delete 和put用的也是post的请求,这里就要说说java里面对put和delete的不支持了-> java里面原本是对put和delete请求进行过滤掉的(不知道为什么要这么做),而且在servlet里面还有doGet,doPost,doDelete,doPut的对应方法,但是不可以使用(尴尬不尴尬),同样spring mvc里面也有对应的method=RequestMethod.PUT 和Delete,但是ajax里面type写成Put、Delete是可以访问到对应的方法的,但是参数却无法传递过去,所有传递过去的参数都是null(郁闷不郁闷)!C#就不会这样,C#的API编程需要开启一下PUT和Delete就可以了,并不需要java里面这么复杂,说到这里我们解决一下这个问题-> 首先在springMVC 项目的Web.xml里面增加一个过滤器filter 1 <!-- 浏览器不支持put,delete等method,由该filter将/xxx?_method=delete转换为标准的http delete方法 --> 2 <filter> 3 <filter-name>hiddenHttpMethodFilter</filter-name> 4 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 5 </filter> 6 <filter-mapping> 7 <filter-name>hiddenHttpMethodFilter</filter-name> 8 <url-pattern>/*</url-pattern> 9 </filter-mapping> 当然有些新手不知道这段代码加在哪里,那么我就将我的web.xml一并粘贴在此处(我也搞这个半天...) 1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 5 version="3.0"> 6 7 <!-- 浏览器不支持put,delete等method,由该filter将/xxx?_method=delete转换为标准的http delete方法 --> 8 <filter> 9 <filter-name>hiddenHttpMethodFilter</filter-name> 10 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 11 </filter> 12 <filter-mapping> 13 <filter-name>hiddenHttpMethodFilter</filter-name> 14 <url-pattern>/*</url-pattern> 15 </filter-mapping> 16 17 <!--这段代码如果不用上面的的话是可以实现put的--> 18 <!--<filter> 19 <filter-name>HttpMethodPutFilter</filter-name> 20 <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class> 21 </filter> 22 <filter-mapping> 23 <filter-name>HttpMethodPutFilter</filter-name> 24 <url-pattern>/*</url-pattern> 25 </filter-mapping>--> 26 27 28 <welcome-file-list> 29 <welcome-file>/index.jsp</welcome-file> 30 </welcome-file-list> 31 <!-- Spring MVC配置 --> 32 <servlet> 33 <servlet-name>spring</servlet-name> 34 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 35 36 <!-- load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法) --> 37 <load-on-startup>1</load-on-startup> 38 </servlet> 39 40 <servlet-mapping> 41 <servlet-name>spring</servlet-name> 42 <url-pattern>/</url-pattern> 43 </servlet-mapping> 44 45 <!-- Spring配置 --> 46 <listener> 47 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 48 </listener> 49 50 <!-- 指定Spring Bean的配置文件所在目录。默认配置在WEB-INF目录下 --> 51 <context-param> 52 <param-name>contextConfigLocation</param-name> 53 <param-value>classpath:applicationContext.xml</param-value> 54 </context-param> 55 </web-app> 这里我们将过滤器配置好了,我有一段注释掉了,如果用下面这个配置文件-> 1 <!--这段代码如果不用上面的的话是可以实现put的--> 2 <filter> 3 <filter-name>HttpMethodPutFilter</filter-name> 4 <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class> 5 </filter> 6 <filter-mapping> 7 <filter-name>HttpMethodPutFilter</filter-name> 8 <url-pattern>/*</url-pattern> 9 </filter-mapping> 这个配置项如果写在这里的话是可以支持PUT请求的,但是DELETE请求依然不可以,那么我只能选择第一种方法了 1 <filter> 2 <filter-name>hiddenHttpMethodFilter</filter-name> 3 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>hiddenHttpMethodFilter</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping> 这一段的方法是用 org.springframework.web.filter.HiddenHttpMethodFilter 的内置过滤器类进行对http请求的标准化。这样让我们可以自己声明请求的方式。 配置完成这个以后,我们在ajax里面需要传递一个参数_method:"PUT" 和 _method:"DELETE",但是请求方式仍然是POST 这样配置的话,我们已经可以实现对DELETE修饰的方法进行访问,同样_method:'PUT'我们可以对PUT修饰的方法进行访问,这样我们上面定义的控制器类已经可以实现了。 本文为七小站主原创作品,转载请注明出处:http://www.cnblogs.com/qixiaoyizhan/ 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
eval(string) 参数 描述 string 必需。要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句。 复制代码 var str = '{"name": "hanzichi", "age": 10}'; var obj = eval('(' + str + ')'); console.log(obj); // Object {name: "hanzichi", age: 10} 是否注意到,向 eval() 传参时,str 变量外裹了一层小括号?为什么要这样做? 我们先来看看 eval 函数的定义以及使用。 eval() 的参数是一个字符串。如果字符串表示了一个表达式,eval() 会对表达式求值。如果参数表示了一个或多个 JavaScript 声明, 那么 eval() 会执行声明。不要调用 eval() 来为算数表达式求值; JavaScript 会自动为算数表达式求值。 简单地说,eval 函数的参数是一个字符串,如果把字符串 "noString" 化处理,那么得到的将是正常的可以运行的 JavaScript 语句。 怎么说?举个栗子,如下代码: 复制代码 var str = "alert('hello world')"; eval(str); 执行后弹出 "hello world"。我们把 str 变量 "noString" 化,粗暴点的做法就是去掉外面的引号,内部调整(转义等),然后就变成了: 复制代码 alert('hello world') very good!这是正常的可以运行的 JavaScript 语句!运行之! 再回到开始的问题,为什么 JSON 字符串要裹上小括号。如果不加,是这个样子的: 复制代码 var str = '{"name": "hanzichi", "age": 10}'; var obj = eval(str); // Uncaught SyntaxError: Unexpected token : 恩,报错了。为什么会报错?试试把 str "noString" 化,执行一下: 复制代码 {"name": "hanzichi", "age": 10}; // Uncaught SyntaxError: Unexpected token : 毫无疑问,一个 JSON 对象或者说是一个对象根本就不是能执行的 JavaScript 语句!等等,试试以下代码: 复制代码 var str = '{name: "hanzichi"}'; var obj = eval(str); console.log(obj); // hanzichi 这又是什么鬼?但是给 name 加上 "" 又报错? 复制代码 var str = '{"name": "hanzichi"}'; var obj = eval(str); // Uncaught SyntaxError: Unexpected token : console.log(obj); 好吧,快晕了,其实还是可以将 str "nostring" 化,看看是不是能正确执行的 JavaScript 语句。前者的结果是: 复制代码 {name: "hanzichi"} 这确实是一条合法的 JavaScript 语句。{} 我们不仅能在 if、for 语句等场景使用,甚至可以在任何时候,因为 ES6 之前 JavaScript 只有块级作用域,所以对于作用域什么的并不会有什么冲突。去掉 {} 后 name: "hanzichi" 也是合法的语句,一个 label 语句,label 语句在跳出嵌套的循环中非常好用,具体可以参考 label,而作为 label 语句的标记,name 是不能带引号的,标记能放在 JavaScript 代码的任何位置,用不到也没关系。 一旦一个对象有了两个 key,比如 {name: "hanzichi", age: 10},ok,两个 label 语句?将 "hanzhichi" 以及 10 分别看做是语句,但是 语句之间只能用封号连接!(表达式之间才能用逗号)。所以改成下面这样也是没有问题的: 复制代码 var str = '{name: "hanzichi"; age: 10}'; var obj = eval(str); console.log(obj); // 10 越扯越远,文章开头代码的错误的原因是找到了,为什么套个括号就能解决呢?简单来说,() 会把语句转换成表达式,称为语句表达式。括号里的代码都会被转换为表达式求值并且返回,对象字面量必须作为表达式而存在。 本文并不会大谈表达式,关于表达式,可以参考文末链接。值得记住的一点是,表达式永远有一个返回值。大部分表达式会包裹在() 内,小括号内不能为空,如果有多个表达式,用逗号隔开,也就是所谓的逗号表达式,会返回最后一个的值。 说到表达式,不得不提函数表达式,以前翻译过一篇关于立即执行函数表达式的文章,可以参考下,文末。 总结: 大家知道使用eval()函数可以将JSON格式字符串转换为对象直接量。 先看一段转换JSON字符串的代码实例: [JavaScript] 纯文本查看 复制代码运行代码 1 2 var obj=eval('({"webName":"antzone","age":2,"address":"青岛市南区"})'); console.log(obj.webName); 由上面代码的运行结果可以看出,转换获得了成功,也可以看到,在JSON格式字符串外面又嵌套了一层小括号。 下面就来介绍这层小括号的作用,需要的朋友可以做一下参考。 eval()函数会创建一个执行环境,如果没有加小括号,{"webName":"antzone","age":2,"address":"青岛市南区"}会被解释成一个复合语句(大括号是用来构建复合语句的),那么"webName":和后面的内容也无法组成一个标签语句,直接就报错了,即便不是标准的JSON字符串,如{webName:"antzone",age:2,address:"青岛市南区"},webName:和后面的内容形成一个标签语句,"antzone"是一个字符串字面量,age就是一个变量,但是跟冒号就会报错。如果外面嵌套一个小括号,小括号是分组运算符,那么{"webName":"antzone","age":2,"address":"青岛市南区"}就是一个表达式,这时会被解读为一个对象直接量,就不会有任何问题了。
JavaScript 对象字面量 JavaScript 对象字面量 在编程语言中,字面量是一种表示值的记法。例如,"Hello, World!" 在许多语言中都表示一个字符串字面量(string literal ),JavaScript也不例外。以下也是JavaScript字面量的例子,如5、true、false和null,它们分别表示一个整数、两个布尔值和一个空对象。JavaScript还支持对象和数组字面量,允许使用一种简洁而可读的记法来创建数组和对象。考虑以下语句,其中创建了一个包含两个属性的对象(firstName和lastName): 还可以使用一种等价的方法创建同样的对象: 以上赋值语句的右边是一个对象字面量(object literal)。对象字面量是一个名值对列表,每个名值对之间用逗号分隔,并用一个大括号括起。各名值对表示对象的一个属性,名和值这两部分之间用一个冒号分隔。要创建一个数组,可以创建Array对象的一个实例: 不过首选的方法是使用一个数组字面量(array literal),这是一个用逗号分隔的值列表,用中括号括起: 前面的例子展示了对象和数组字面量中可以包含其他字面量。以下是一个更复杂的示例: 赋给team变量的对象有3个属性:name、members和count。注意,''表示空串,[]是一个空数组。甚至count属性的值也是一个字面量,即函数字面量(function literal): 函数字面量如下构造:前面是一个function关键字,后面是一个函数名(可选)和参数表。然后是函数体,包围在大括号中。 以上已经介绍了字面量,下面来介绍JavaScript对象记法(JavaScript Object Notation,JSON),这是一种用于描述文件和数组的记法,由JavaScript字面量的一个子集组成。JSON在Ajax开发人员中越来越流行,因为这种格式可以用于交换数据,通常取代了XML。=========================================================================JavaScript对象字面量的例子 对象字面量: 1 //只能添加静态属性和方法 2 var myObject={ 3 propertyA: sha , 4 propertyB: feng , 5 methodA:function(){ 6 alert(this.propertyA+ +this.propertyB); 7 }, 8 methodB:function(){} 9 }10 11 myObject.methodA();12 13 14 //利用prototype属性可以添加公有属性和方法15 16 function myConstructor2(){}; //声明构造函数,可以使用对象字面量语法来向prototype属性中添加所有公有成员17 18 myConstructor2.prototype={19 propertyA: sha ,20 propertyB: feng ,21 methodA:function(){22 alert(this.propertyA+ +this.propertyB);23 },24 methodB:function(){}25 }26 27 var myconstrustor=new myConstructor2(); //声明对象28 myconstrustor.methodA();
深入探究javascript的 {} 语句块 今日学习解析json字符串,用到了一个eval()方法,解析字符串的时候为什么需要加上括号呢?摸不着头脑。原来javascript中{}语句块具有二义性,不加括号会出错,理解这种二义性对我们理解javascript代码有极大帮助。 一、{}语句块的两个含义 表示语句块 a. 在javascript中可以使用{}来括起代码,在编辑器中方便管理代码。因为javascript并没有块级作用域,所以这种写法是无害的。 { //some code... } b. 在javascript中 ,条件判断语句,循环语句,函数都需要{}语句块来整合代码 对象字面量 var box = { name:'kuoaho', age:21 } //此时{code}作为表达式,可以赋值给一个变量 //其实对象字面量就是可以生成对象值的表达式 二、那如果对象字面量不作为一个赋值表达式,会发生什么情况呢? example: {name:'kuoao'} //没有报错,但是也没有创建对象 {name:'kuohao',age} //报错 由上面可以看出对象字面量只能够作为表达式赋值,第一种写法没有错,只是javascript将它作为一个label语句解析了。 analysis: {name:'kuoao'} //{}一个语句块 // name:'kuohao',一个label语句,用于标记for循环 三、但是问题又来了…… { name:'kuohao', age:21 } //这样为什么会报错?这不是对象字面量的写法吗? 因为javascript中{}的二义性,{}不仅仅被认为是对象字面量而且还会被认为是代码块。 analysis: { name:'kuohao', age:21 } 一个代码块,两条label语句,如果没有逗号,是完全没有问题的,所以关键在于逗号,两条语句的分隔应该使用分号,所以javascript会判定这是语法错误 四、正确的写法 ({ name:'kuohao', age:21 }) //正确的写法 ()会把语句转换成表达式,称为语句表达式,对象字面量不是表达式吗?为什么还需要()来转换? 加上括号以后,就可以消除这种二义性,因为括号里的代码都会被转换为表达式求值并且返回,因此语句块也就变成了对象字面量,也可以得出,对象字面量必须作为表达式而存在
@responseBody注解的使用 1、 @responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML 数据,是json还是xml根据springmvc配置返回如下,需要注意的呢,在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。 2、 @RequestMapping("/login") @ResponseBody public User login(User user){ return user; } User字段:userName pwd 那么在前台接收到的数据为:'{"userName":"xxx","pwd":"xxx"}' 效果等同于如下代码: @RequestMapping("/login") public void login(User user, HttpServletResponse response){ response.getWriter.write(JSONObject.fromObject(user).toString()); } spring-mvc.xml配置 <!--扫描配置 --> <context:component-scan base-package="com.sudytech.**.controller" /> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <!--先进行string转换,防止响应字符串时多加 '"',顺序不能变,必须放在json之前 --> <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter" > <constructor-arg value="UTF-8"/> </bean> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="charset" value="UTF-8"/> <property name="supportedMediaTypes"> <list> <!-- 这里顺序不能反,一定先写text/html,不然ie下出现下载提示 --> <value>text/html;charset=UTF-8</value> <value>application/json;charset=UTF-8</value> </list> </property> <property name="features"> <list> <!-- 输出key时是否使用双引号 --> <value>QuoteFieldNames</value> <!-- 是否输出值为null的字段 --> <!-- <value>WriteMapNullValue</value> --> <!-- 数值字段如果为null,输出为0,而非null --> <value>WriteNullNumberAsZero</value> <!-- List字段如果为null,输出为[],而非null --> <value>WriteNullListAsEmpty</value> <!-- 字符类型字段如果为null,输出为"",而非null --> <value>WriteNullStringAsEmpty</value> <!-- Boolean字段如果为null,输出为false,而非null --> <value>WriteNullBooleanAsFalse</value> <!-- Date的日期转换器 --> <value>WriteDateUseDateFormat</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
装饰模式 装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。 装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。 在装饰模式中的角色有: ● 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。 ● 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。 ● 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。 ● 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。 源代码 抽象构件角色 public interface Component { public void sampleOperation(); } 具体构件角色 public class ConcreteComponent implements Component { @Override public void sampleOperation() { // 写相关的业务代码 } } 装饰角色 public class Decorator implements Component{ private Component component; public Decorator(Component component){ this.component = component; } @Override public void sampleOperation() { // 委派给构件 component.sampleOperation(); } } 具体装饰角色 public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } @Override public void sampleOperation() { super.sampleOperation(); // 写相关的业务代码 } } 代理模式 ● 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。 ● 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。 ● 代理(Decorator)角色:持有一个构件(ConcreteComponent)对象的实例,并定义一个与抽象构件接口一致的接口。 publicinterface Component{ publicvoid method(); } [java] view plaincopy public class ConcreteComponent implements Component{ @Override public void method() { System.out.println("the original method!"); } } [java] view plaincopy public class Proxy implements Component{ private ConcreteComponent cc; public Proxy(){ super(); this.cc= new ConcreteComponent (); } @Override public void method() { before(); cc.method(); atfer(); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); } } 测试类: [java] view plaincopy public class ProxyTest { public static void main(String[] args) { Component c= new Proxy(); c.method(); } } 两者的区别:代理是控制访问的方式/途径不需要传参数,装饰是在原有基础上增加功能需要传参数,而参数就是具体构建对象
1、Spring MVC bean的nameurl处理映射 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <bean name="/home.html" class="com.yiibai.springmvc.UserController" /> 2、Spring MVC控制器类名称处理映射 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean class="com.yiibai.springmvc.HelloController" /> <bean class="com.yiibai.springmvc.WelcomeController"/>3、Spring MVC简单URL处理程序映射 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/welcome.html">welcomeController</prop> <prop key="/helloWorld.html">helloController</prop> </props> </property> </bean> <bean id="helloController" class="com.yiibai.springmvc.HelloController" /> <bean id="welcomeController" class="com.yiibai.springmvc.WelcomeController"/> controller类要继承AbstractController
转发过程 客户首先发送一个请求到服务器端,服务器端发现匹配的servlet,并指定它去执行,当这个servlet执行完之后,它要调用getRequestDispacther()方法,把请求转发给指定的student_list.jsp,整个流程都是在服务器端完成的,而且是在同一个请求里面完成的,因此servlet和jsp共享的是同一个request,在servlet里面放的所有东西,在student_list中都能取出来,因此,student_list能把结果getAttribute()出来,getAttribute()出来后执行完把结果返回给客户端。整个过程是一个请求,一个响应。 重点:转发是服务器行为,因此也是在这个应用内转发,整个过程是一个请求一个响应,共享一个request,所以由request执行转发操作,request.getRequestDispacther("url").forward(request,response) 重定向过程 客户发送一个请求到服务器,服务器匹配servlet,这都和请求转发一样,servlet处理完之后调用了sendRedirect()这个方法,这个方法是response的方法,所以,当这个servlet处理完之后,看到response.senRedirect()方法,立即向客户端返回这个响应,响应行告诉客户端你必须要再发送一个请求,去访问student_list.jsp,紧接着客户端受到这个请求后,立刻发出一个新的请求,去请求student_list.jsp,这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了。可见,在sendRedirect()里面是两个请求,两个响应。 重点:重定向是客户端行为,也就注定可以向任何地址发送请求,客户端行为的改变是服务器所给的指示,亦即是response的行为返回,就像一个人不能两次同时踏进一个河流一样,每次请求都是新的行为,request不保留上次的内容。 两者最大区别是:运用forward方法只能重定向到同一个Web应用程序中的一个资源。而sendRedirect方法可以让你重定向到任何
1、web.xml文件配置 <!-- spring mvc --> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>*.rst</url-pattern> </servlet-mapping> contextConfigLocation 在web.xml中通过contextConfigLocation配置spring,contextConfigLocation参数定义了要装入的 Spring 配置文件。 2、spring-mvc文件 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <!--所要拦截的文件--> <context:component-scan base-package="com.sudytech.**.controller" /> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> <value>text/html;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- 配置静态资源 --> <!-- <mvc:resources location="/upload/" mapping="/upload/**" /> --> <!-- 配置jsp页面解析器 --> <!-- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/view/" p:suffix=".jsp" p:order="0" /> --> <!-- 全局异常处理机制 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="java.lang.RuntimeException">error</prop> <prop key="java.lang.Exception">error</prop> </props> </property> </bean> --> <!-- 权限拦截 --> <!-- <mvc:interceptors> --> <!-- <mvc:interceptor> --> <!-- <mvc:mapping path="/**" /> --> <!-- <bean class="com.***"></bean> --> <!-- </mvc:interceptor> --> <!-- </mvc:interceptors> --> <!-- 上传下载 --> <!-- <bean id="multipartResolver" --> <!-- class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> --> <!-- <property name="maxUploadSize" value="104857600" /> --> <!-- <property name="maxInMemorySize" value="40960"></property> --> <!-- <property name="defaultEncoding" value="utf-8" /> --> <!-- </bean> --> </beans> 3、context:component-scan 默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,如@Component(组件),@Service(服务),@Controller(控制器),@Repository(数据仓库) 我们具体看下<context:component-scan>的一些属性,以下是一个比较具体的<context:component-scan>配置 <context:component-scan base-package="com.wjx.betalot" <!-- 扫描的基本包路径 --> annotation-config="true" <!-- 是否激活属性注入注解 --> name-generator="org.springframework.context.annotation.AnnotationBeanNameGenerator" <!-- Bean的ID策略生成器 --> resource-pattern="**/*.class" <!-- 对资源进行筛选的正则表达式,这边是个大的范畴,具体细分在include-filter与exclude-filter中进行 --> scope-resolver="org.springframework.context.annotation.AnnotationScopeMetadataResolver" <!-- scope解析器 ,与scoped-proxy只能同时配置一个 --> scoped-proxy="no" <!-- scope代理,与scope-resolver只能同时配置一个 --> use-default-filters="false" <!-- 是否使用默认的过滤器,默认值true --> > <!-- 注意:若使用include-filter去定制扫描内容,要在use-default-filters="false"的情况下,不然会“失效”,被默认的过滤机制所覆盖 --> <!-- annotation是对注解进行扫描 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> <!-- assignable是对类或接口进行扫描 --> <context:include-filter type="assignable" expression="com.wjx.betalot.performer.Performer"/> <context:include-filter type="assignable" expression="com.wjx.betalot.performer.impl.Sonnet"/> <!-- 注意:在use-default-filters="false"的情况下,exclude-filter是针对include-filter里的内容进行排除 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="assignable" expression="com.wjx.betalot.performer.impl.RainPoem"/> <context:exclude-filter type="regex" expression=".service.*"/> </context:component-scan> 以上配置注释已经很详细了,当然因为这些注释,你若是想复制去验证,你得删掉注释。我们具体再说明一下这些注释: back-package:标识了<context:component-scan>元素所扫描的包,可以使用一些通配符进行配置 annotation-config:<context:component-scan>元素也完成了<context:annotation-config>元素的工作,开关就是这个属性,false则关闭属性注入注解功能 name-generator:这个属性指定你的构造型注解,注册为Bean的ID生成策略,这个生成器基于接口BeanNameGenerator实现generateBeanName方法,你可以自己写个类去自定义策略。这边,我们可不显示配置,它是默认使用org.springframework.context.annotation.AnnotationBeanNameGenerator生成器,也就是类名首字符小写的策略,如Performer类,它注册的Bean的ID为performer.并且可以自定义ID,如@Component("Joy").这边简单贴出这个默认生成器的实现。 public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instanceof AnnotatedBeanDefinition) { String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); if (StringUtils.hasText(beanName)) { // Explicit bean name found. return beanName; } } // Fallback: generate a unique default bean name. return buildDefaultBeanName(definition, registry); } Spring除了实现了AnnotationBeanNameGenerator生成器外,还有个org.springframework.beans.factory.support.DefaultBeanNameGenerator生成器,它为了防止Bean的ID重复,它的生成策略是类路径+分隔符+序号 ,如com.wjx.betalot.performer.impl.Sonnet注册为Bean的ID是com.wjx.betalot.performer.impl.Sonnet#0,这个生成器不支持自定义ID,否则抛出异常。同样贴出代码,有兴趣的可以去看下。 public static String generateBeanName( BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException { String generatedBeanName = definition.getBeanClassName(); if (generatedBeanName == null) { if (definition.getParentName() != null) { generatedBeanName = definition.getParentName() + "$child"; } else if (definition.getFactoryBeanName() != null) { generatedBeanName = definition.getFactoryBeanName() + "$created"; } } if (!StringUtils.hasText(generatedBeanName)) { throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name"); } String id = generatedBeanName; if (isInnerBean) { // Inner bean: generate identity hashcode suffix. id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition); } else { // Top-level bean: use plain class name. // Increase counter until the id is unique. int counter = -1; while (counter == -1 || registry.containsBeanDefinition(id)) { counter++; id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter; } } return id; } resource-pattern:对资源进行筛选的正则表达式,这边是个大的范畴,具体细分在include-filter与exclude-filter中进行。 scoped-proxy: scope代理,有三个值选项,no(默认值),interfaces(接口代理),targetClass(类代理),那什么时候需要用到scope代理呢,举个例子,我们知道Bean的作用域scope有singleton,prototype,request,session,那有这么一种情况,当你把一个session或者request的Bean注入到singleton的Bean中时,因为singleton的Bean在容器启动时就会创建A,而session的Bean在用户访问时才会创建B,那么当A中要注入B时,有可能B还未创建,这个时候就会出问题,那么代理的时候来了,B如果是个接口,就用interfaces代理,是个类则用targetClass代理。这个例子出处:http://www.bubuko.com/infodetail-1434289.html。 scope-resolver:这个属性跟name-generator有点类似,它是基于接口ScopeMetadataResolver的,实现resolveScopeMetadata方法,目的是为了将@Scope(value="",proxyMode=ScopedProxyMode.NO,scopeName="")的配置解析成为一个ScopeMetadata对象,Spring这里也提供了两个实现,我们一起看下。首先是org.springframework.context.annotation.AnnotationScopeMetadataResolver中, public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata = new ScopeMetadata(); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType); if (attributes != null) { metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource())); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } } return metadata; } 对比一下org.springframework.context.annotation.Jsr330ScopeMetadataResolver中的实现: public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata = new ScopeMetadata(); metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; Set<String> annTypes = annDef.getMetadata().getAnnotationTypes(); String found = null; for (String annType : annTypes) { Set<String> metaAnns = annDef.getMetadata().getMetaAnnotationTypes(annType); if (metaAnns.contains("javax.inject.Scope")) { if (found != null) { throw new IllegalStateException("Found ambiguous scope annotations on bean class [" + definition.getBeanClassName() + "]: " + found + ", " + annType); } found = annType; String scopeName = resolveScopeName(annType); if (scopeName == null) { throw new IllegalStateException( "Unsupported scope annotation - not mapped onto Spring scope name: " + annType); } metadata.setScopeName(scopeName); } } } return metadata; } ps:scope-resolver与scoped-proxy只能配置一个,配置了scope-resolver后你要使用代理,可以配置@Scope总的proxyMode属性项 use-default-filters:是否使用默认的扫描过滤。 <context:include-filter> :用来告知哪些类需要注册成Spring Bean,使用type和expression属性一起协作来定义组件扫描策略。type有以下5种 过滤器类型 描述 annotation 过滤器扫描使用注解所标注的那些类,通过expression属性指定要扫描的注释 assignable 过滤器扫描派生于expression属性所指定类型的那些类 aspectj 过滤器扫描与expression属性所指定的AspectJ表达式所匹配的那些类 custom 使用自定义的org.springframework.core.type.TypeFliter实现类,该类由expression属性指定 regex 过滤器扫描类的名称与expression属性所指定正则表示式所匹配的那些类 要注意的是:若使用include-filter去定制扫描内容,要在use-default-filters="false"的情况下,不然会“失效”,被默认的过滤机制所覆盖 <context:exclude-filter>:与<context:include-filter> 相反,用来告知哪些类不需要注册成Spring Bean,同样注意的是:在use-default-filters="false"的情况下,exclude-filter是针对include-filter里的内容进行排除。
<context-param>的作用: web.xml的配置中<context-param>配置作用1. 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener></listener> 和 <context-param></context-param> 2.紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文. 3.容器将<context-param></context-param>转化为键值对,并交给ServletContext. 4.容器创建<listener></listener>中的类实例,即创建监听. 5.在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得ServletContext = ServletContextEvent.getServletContext();context-param的值 = ServletContext.getInitParameter("context-param的键"); 6.得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.换句话说,这个时候,你对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行. 7.举例.你可能想在项目启动之前就打开数据库.那么这里就可以在<context-param>中设置数据库的连接方式,在监听类中初始化数据库的连接. 8.这个监听是自己写的一个类,除了初始化方法,它还有销毁方法.用于关闭应用前释放资源.比如说数据库连接的关闭. 如:<!-- 加载spring的配置文件 --><context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml,/WEB- INF/jason-servlet.xml</param-value></context-param><listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener> 又如: --->自定义context-param,且自定义listener来获取这些信息 <context-param> <param-name>urlrewrite</param-name> <param-value>false</param-value></context-param><context-param> <param-name>cluster</param-name> <param-value>false</param-value></context-param><context-param> <param-name>servletmapping</param-name> <param-value>*.bbscs</param-value></context-param><context-param> <param-name>poststoragemode</param-name> <param-value>1</param-value></context-param><listener> <listener-class>com.laoer.bbscs.web.servlet.SysListener</listener-class></listener> public class SysListener extends HttpServlet implements ServletContextListener { private static final Log logger = LogFactory.getLog(SysListener.class); public void contextDestroyed(ServletContextEvent sce) { //用于在容器关闭时,操作} //用于在容器开启时,操作 public void contextInitialized(ServletContextEvent sce) { String rootpath = sce.getServletContext().getRealPath("/"); System.out.println("-------------rootPath:"+rootpath); if (rootpath != null) { rootpath = rootpath.replaceAll("\\\\", "/"); } else { rootpath = "/"; } if (!rootpath.endsWith("/")) { rootpath = rootpath + "/"; } Constant.ROOTPATH = rootpath; logger.info("Application Run Path:" + rootpath); String urlrewrtie = sce.getServletContext().getInitParameter("urlrewrite"); boolean burlrewrtie = false; if (urlrewrtie != null) { burlrewrtie = Boolean.parseBoolean(urlrewrtie); } Constant.USE_URL_REWRITE = burlrewrtie; logger.info("Use Urlrewrite:" + burlrewrtie); 其它略之.... } } /*最终输出 -------------rootPath:D:\tomcat_bbs\webapps\BBSCS_8_0_3\ 2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO] Application Run Path:D:/tomcat_bbs/webapps/BBSCS_8_0_3/ 2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO] Use Urlrewrite:true 2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO] Use Cluster:false 2009-06-09 21:51:46,526 [com.laoer.bbscs.web.servlet.SysListener]-[INFO] SERVLET MAPPING:*.bbscs 2009-06-09 21:51:46,573 [com.laoer.bbscs.web.servlet.SysListener]-[INFO] Post Storage Mode:1 */ context-param和init-param区别web.xml里面可以定义两种参数:(1)application范围内的参数,存放在servletcontext中,在web.xml中配置如下:<context-param> <param-name>context/param</param-name> <param-value>avalible during application</param-value></context-param> (2)servlet范围内的参数,只能在servlet的init()方法中取得,在web.xml中配置如下:<servlet> <servlet-name>MainServlet</servlet-name> <servlet-class>com.wes.controller.MainServlet</servlet-class> <init-param> <param-name>param1</param-name> <param-value>avalible in servlet init()</param-value> </init-param> <load-on-startup>0</load-on-startup></servlet> 在servlet中可以通过代码分别取用:package com.wes.controller; import javax.servlet.ServletException;import javax.servlet.http.HttpServlet; public class MainServlet extends HttpServlet ...{ public MainServlet() ...{ super(); } public void init() throws ServletException ...{ System.out.println("下面的两个参数param1是在servlet中存放的"); System.out.println(this.getInitParameter("param1")); System.out.println("下面的参数是存放在servletcontext中的"); System.out.println(this.getServletContext().getInitParameter("context/param")); }} 第一种参数在servlet里面可以通过getServletContext().getInitParameter("context/param")得到第二种参数只能在servlet的init()方法中通过this.getInitParameter("param1")取得. 其他方法中通过httpRequest获取 request.getSession().getServletContext(); 其 他获取servletContext的方法: Servlet: this.getServletContext() this.getServletConfig().getServletContext() request.getSession().getServletContext() JSP: request.getSession().getServletContext() Filter: config.getServletContext()
classpath 和 classpath*的 区别: classpath:只会到你指定的class路径中查找找文件; classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找. 举个简单的例子,若web.xml中是这么定义的: <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:META-INF/spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 1 2 3 4 5 6 7 那么在META-INF/spring这个文件夹底下的所有applicationContext.xml都会被加载到上下文中,这些包括META-INF/spring文件夹底下的 applicationContext.xml,META-INF/spring的子文件夹的applicationContext.xml以及jar中的applicationContext.xml。 而若在web.xml中定义的是: <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:META-INF/spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 1 2 3 4 5 6 7 那么只有META-INF/spring底下的applicationContext.xml会被加载到上下文中。
Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use. The server may already be running in another process, or a system process may be using the port. To start this server you will need to stop the other process or change the port number(s).问题 工具/原料 eclipse/myeclipse tomcat 方法/步骤 在开发java web项目中,我们往往会遇到很多问题,例如启动tomcat时,报错Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use.参考下图报错提示: 出现错误,我们首先的了解错误原因,英语能力较差者,可以通过百度搜索翻译,把错误的提示英文拿去翻译,参考下图。 通过百度翻译,可以了解到大概信息,毕竟百度翻译这种技术型的文档还并不够准确。通过翻译,大致了解到造成的原因是端口号被占用了。 tomcat控制端口,用于tomcat本身的功能控制等等,默认的是8005好像看你的server.xml配置文件,至少要修改这2个端口,如果用到了其他的附加组件,也需要提供相应空间的端口。参考下图 另一种方式是结束"javaw.exe"这个进程,然后重新启动tomcat,具体查找参考下图。 关闭"javaw.exe"这个进程,再次重启tomcat,你会发现并未再报错了。