利用闲暇时间多刚刚结束的项目进行总结,这是一个普惠系统。支持业务员进件、审核、流转,同时支持管理员对员工、权限等进行管理。项目采用dubbo框架,前台用了freemarker+bootstrap。这里开始分析系统架构,从web.xml开始。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>Manage Server</display-name>
<!--在tomcat下部署两个或多个项目时,web.xml文件中最好定义webAppRootKey参数,如果不定义,将会缺省为“webapp.root”-->
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>Manage-Server.root</param-value>
</context-param>
<!-- 添加日志监听器 -->
<!--logbackConfigLocation以及下面的监听是logback相关配置-->
<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>classpath:logback.xml</param-value>
</context-param>
<listener>
<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
</listener>
<!--contextConfigLocation参数定义了需要装入spring的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/applicationContext-*.xml
classpath:spring/dubbo-consumer-parent.xml
</param-value>
</context-param>
<!-- Spring 刷新Introspector防止内存泄露 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<listener>
<listener-class>
com.myph.manage.common.listener.CustomContextLoaderListener
</listener-class>
</listener>
<filter>
<filter-name>SetCharacterEncoding</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SetCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>*.htm</url-pattern>
</filter-mapping>
<filter>
<filter-name>sessionFilter</filter-name>
<filter-class>com.myph.common.filter.SessionFilter</filter-class>
<init-param>
<!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>sessionFilter</filter-name>
<url-pattern>*.htm</url-pattern>
</filter-mapping>
<filter>
<filter-name>AntiXssFilter</filter-name>
<filter-class>com.myph.manage.common.util.AntiXssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AntiXssFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.html</url-pattern>
<url-pattern>*.htm</url-pattern>
<url-pattern>/</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>ERROR</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/spring-app-mvc.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>420</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>login.htm</welcome-file>
</welcome-file-list>
<!-- Error Page定义 -->
<error-page>
<error-code>404</error-code>
<location>/error/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.html</location>
</error-page>
</web-app>
WEB工程加载web.xml过程
web工程在启动时,会先读取web.xml配置文件中的配置,当这一步正确完成时,项目才能被正常启动起来。
1、启动项目时,首先会读取<listener>
节点和<context-param>
节点。
2、创建一个ServletContext(servlet上下文),这个web项目的所有部分都共享这个上下文。
3、容器将<context-param>
转换键值对,并交给servletContext。
4、容器将创建<listener>
中的类实例,创建监听。
web.xml中,各个元素的加载顺序是一定的,与各元素即使在文件中顺序无关。
servletContext–context-param–listener–filter–servlet。
需要注意的是,同一元素的加载是按顺序加载的,例如filter与filter-mapping,必须先定义filter,才能正确解析对应名字的filter-mapping。
webAppRootKey
web项目配置webAppRootKey 获得根目录。
Spring通过 org.springframework.web.util.WebAppRootListener 这个监听器来压入项目路径。但是如果在web.xml中已经配置了 ch.qos.logback.ext.spring.web.LogbackConfigListener这个监听器,则不需要配置WebAppRootListener了。因为Log4jConfigListener已经包含了WebAppRootListener的功能。
contextConfigLocation
contextConfigLocation参数定义了需要装入spring的配置文件。
IntrospectorCleanupListener
在服务器运行过程中,Spring不停的运行的计划任务和OpenSessionInViewFilter,使得Tomcat反复加载对象而产生框架并用时可能产生的内存泄漏,则使用IntrospectorCleanupListener作为相应的解决办法。
spring中的提供了一个名为org.springframework.web.util.IntrospectorCleanupListener的监听器。它主要负责处理由 JavaBeans Introspector的使用而引起的缓冲泄露。spring中对它的描述如下:它是一个在web应用关闭的时候,清除JavaBeans Introspector的监听器.web.xml中注册这个listener.可以保证在web 应用关闭的时候释放与掉这个web 应用相关的class loader 和由它管理的类如果你使用了JavaBeans Introspector来分析应用中的类,Introspector 缓冲中会保留这些类的引用.结果在你的应用关闭的时候,这些类以及web 应用相关的class loader没有被垃圾回收.不幸的是,清除Introspector的唯一方式是刷新整个缓冲.这是因为我们没法判断哪些是属于你的应用的引用.所以删除被缓冲的introspection会导致把这台电脑上的所有应用的introspection都删掉.需要注意的是,spring 托管的bean不需要使用这个监听器.因为spring它自己的introspection所使用的缓冲在分析完一个类之后会被马上从javaBeans Introspector缓冲中清除掉.应用程序中的类从来不直接使用JavaBeans Introspector.所以他们一般不会导致内部查看资源泄露.但是一些类库和框架往往会产生这个问题。
CustomContextLoaderListener
这是我们项目自己实现的监听,由于项目采用dubbo框架,dubbo默认使用log4j作为日志输出,而我们项目是采用logback来输出日志。
因此通过监听,设置切换成slf4j。
public class CustomContextLoaderListener extends ContextLoaderListener {
static{
//设置dubbo使用slf4j来记录日志
System.setProperty("dubbo.application.logger","slf4j");
}
}
CharacterEncodingFilter
这是spring MVC提供的字符集过滤器,防止乱码。
shiroFilter
项目中用了shiro来管理权限,这里用了代理来注入spring bean,先filter中加入DelegatingFilterProxy类,”targetFilterLifecycle”指明作用于filter的所有生命周期。
原理是,DelegatingFilterProxy类是一个代理类,所有的请求都会首先发到这个filter代理,然后再按照”filter-name”委派到spring中的这个bean。
关于shiro,稍后再总结。
在Spring中配置的bean的name要和web.xml中的<filter-name>
一样,在项目中是这样配置的:
<!--shiro过滤器配置,bean的id值须与web中的filter-name的值相同-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.htm"/>
<property name="successUrl" value="/index.htm"/> <!-- 登陆成功之后跳转的页面 -->
<property name="unauthorizedUrl" value="/unauthorized.htm"/>
<property name="filters">
<map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="concurrentlogin" value-ref="concurrentLoginControlFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/commom/** = anon
/unauthorized.htm = anon
/login.htm=anon
/sendLoginSmsCode.htm=anon
/dologin.htm=anon
/**/*.htm=authc,concurrentlogin
</value>
</property>
</bean>
此外,spring bean实现了Filter接口,但默认情况下,是由spring容器来管理其生命周期的(不是由tomcat这种服务器容器来管理)。如果设置”targetFilterLifecycle”为True,则spring来管理Filter.init()和Filter.destroy();若为false,则这两个方法失效。
sessionFilter
用于检查用户是否登录了系统,如果未登录,则重定向到指的登录页面。
http://blog.csdn.net/buster2014/article/details/42082141
这里看了这篇文章,觉得我们的这个过滤器没有起作用。
AntiXssFilter
这是我们项目自己定义的校验html、sql特殊字符的过滤器。
核心代码:
/**
* 判断是否包含html特殊字符 getIncludeHtmlSpecialCharsFlag
*/
public static boolean getIncludeHtmlSpecialCharsFlag(String s) throws UnsupportedEncodingException {
boolean res = false;
s = replaceSpecialChars(s);
if ( // XSS黑名单
s.indexOf("javascript:") != -1 || s.indexOf("document.cookie") != -1 || s.indexOf("<script") != -1
|| s.indexOf("<iframe") != -1 || s.indexOf("\"><script") != -1 || s.indexOf("<style") != -1
|| s.indexOf("<img") != -1 || s.indexOf("onclick=") != -1 || s.indexOf("\"><style") != -1
|| s.indexOf(")//") != -1 || s.indexOf("\">") != -1 || s.indexOf("<body") != -1
|| s.indexOf("/xss/") != -1 || s.indexOf("onfocus") != -1
|| s.indexOf("alert") != -1 // || s.indexOf(";") != -1
|| s.indexOf("fromcharcode") != -1 || s.indexOf("eval") != -1 || s.indexOf("<a") != -1
|| s.indexOf("cookie") != -1 || s.indexOf("document.write") != -1 || s.indexOf(">@import ") != -1) {
res = true;
}
return res;
}
/**
* 判断是否包含sql特殊字符 getIncludeSqlSpecialCharsFlag
*/
public static boolean getIncludeSqlSpecialCharsFlag(String str) {
// 过滤掉的sql关键字,可以手动添加
String badStr = "'|and |exec |execute |insert |select |delete |update |count |trim|"
+ "char|declare|sitename|net user|xp_cmdshell|like |create |drop |"
+ "table |from |grant |use |group_concat|column_name|"
+ "information_schema.columns|table_schema|union |where |order |by |"
+ "chr|mid|master|truncate |or ";
String[] badStrs = badStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
return true;
}
}
return false;
}
load-on-startup
1)load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
2)它的值必须是一个整数,表示servlet应该被载入的顺序
3)当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
4)当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
5)正数的值越小,该servlet的优先级越高,应用启动时就越先加载。
6)当值相同时,容器就会自己选择顺序来加载。
所以,<load-on-startup>x</load-on-startup>
,中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间。