Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

里氏替换原则

发表于 2016-09-06

1、问题的由来

  我们都知道面向对象有三大特性:封装、继承、多态。所以我们在实际开发过程中,子类在继承父类后,根据多态的特性,可能是图一时方便,经常任意重写父类的方法,那么这种方式会大大增加代码出问题的几率。比如下面场景:类C实现了某项功能F1。现在需要对功能F1作修改扩展,将功能F1扩展为F,其中F由原有的功能F1和新功能F2组成。新功能F由类C的子类C1来完成,则子类C1在完成功能F的同时,有可能会导致类C的原功能F1发生故障。这时候里氏替换原则就闪亮登场了。

2、什么是里氏替换原则

  前面说过的单一职责原则,从字面意思就很好理解,但是里氏替换原则就有点让人摸不着头脑。查过资料后发现原来这项原则最早是在1988年,由麻省理工学院一位姓里的女士(Liskov)提出来的。

  英文缩写:LSP (Liskov Substitution Principle)。

  严格的定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。

  通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。

  更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//抽象父类电脑
public abstract class Computer {
public abstract void use();
}
class IBM extends Computer{
@Override
public void use() {
System.out.println("use IBM Computer.");
}
}
class HP extends Computer{
@Override
public void use() {
System.out.println("use HP Computer.");
}
}
public class Client{
public static void main(String[] args) {
Computer ibm = new IBM();
Computer hp = new HP();//引用基类的地方能透明地使用其子类的对象。
ibm.use();
hp.use();
}
}

3、四层含义

  里氏替换原则包含以下4层含义:

  • 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

  现在我们可以对以上四层含义逐个讲解。

  子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法

  在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解,事实上,子类也必须完全实现父类的抽象方法,哪怕写一个空方法,否则会编译报错。

  里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

  在面向对象的设计思想中,继承这一特性为系统的设计带来了极大的便利性,但是由之而来的也潜在着一些风险。就像开篇所提到的那一场景一样,对于那种情况最好遵循里氏替换原则,类C1继承类C时,可以添加新方法完成新增功能,尽量不要重写父类C的方法。否则可能带来难以预料的风险,比如下面一个简单的例子还原开篇的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class C {
public int func(int a, int b){
return a+b;
}
}
public class C1 extends C{
@Override
public int func(int a, int b) {
return a-b;
}
}
public class Client{
public static void main(String[] args) {
C c = new C1();
System.out.println("2+1=" + c.func(2, 1));
}
}

运行结果:

1
2+1=1

  上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。

  子类中可以增加自己特有的方法

  在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class C {
public int func(int a, int b){
return a+b;
}
}
public class C1 extends C{
public int func2(int a, int b) {
return a-b;
}
}
public class Client{
public static void main(String[] args) {
C1 c = new C1();
System.out.println("2-1=" + c.func2(2, 1));
}
}

运行结果:

1
2-1=1

  当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.HashMap;
public class Father {
public void func(HashMap m){
System.out.println("执行父类...");
}
}
import java.util.Map;
public class Son extends Father{
public void func(Map m){//方法的形参比父类的更宽松
System.out.println("执行子类...");
}
}
import java.util.HashMap;
public class Client{
public static void main(String[] args) {
Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
HashMap h = new HashMap();
f.func(h);
}
}

运行结果:

1
执行父类...

  注意Son类的func方法前面是不能加@Override注解的,因为否则会编译提示报错,因为这并不是重写(Override),而是重载(Overload),因为方法的输入参数不同。重写和重载的区别在Java面向对象详解一文中已作解释,此处不再赘述。

  当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Map;
public abstract class Father {
public abstract Map func();
}
import java.util.HashMap;
public class Son extends Father{
@Override
public HashMap func(){//方法的返回值比父类的更严格
HashMap h = new HashMap();
h.put("h", "执行子类...");
return h;
}
}
public class Client{
public static void main(String[] args) {
Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
System.out.println(f.func());
}
}

执行结果:

1
{h=执行子类...}

4、总结

  继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了一些弊端,它增加了对象之间的耦合性。因此在系统设计时,遵循里氏替换原则,尽量避免子类重写父类的方法,可以有效降低代码出错的可能性。

单一职责原则

发表于 2016-09-06

  前言:据说设计模式是区别程序员与软件设计师的标准之一。其实在编程学习初期就接触过设计模式,但是都没有写过多少代码是领悟不到设计模式真正的威力和必要性的。现在自认为也实践过不少段时间了,是时候总结一下设计模式。不知谁说过没有写过十万行以上代码别谈设计模式,虽然略显夸张,但是还是很有道理的。只有自己亲身经历过一些编写设计垃圾代码,才会深刻理解设计模式真正的意义所在。设计模式都是前辈大牛们在无数次实践中总结出来的,我辈自然要站在巨人肩膀上,实行拿来主义并消化使用之。

1、问题的由来

  初学者在编程的时候可能一开始会有这样的经历,使用一个类来实现很多的功能,新添加的甚至不相关的功能都放在一个类里来实现,煮成了一锅大杂烩,往往使得某个类包罗万象,无所不能。可能刚开始实现功能比较简单,这样做不会引发什么特别大的问题。但是随着项目复杂度的提升,各种不相关的实现代码耦合在一起,一旦有功能的更改或增删,修改的代码很可能会导致其他功能的正常运行。这种编程方式显然是不可取的,也就是违背了所谓的单一职责原则。

2、什么是单一职责原则?

  单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。SRP原则的解释是:There should never be more than one reason for a class to change。定义很简单,即不能存在多于一个导致类变更的原因。简单的说就是一个类只负责一项职责。

  在软件设计中,秉承着“高内聚,低耦合”的思想,让一个类仅负责一项职责,如果一个类有多于一项的职责,那么就代表这个类耦合性变高了,这些职责耦合在了一起,这是比较脆弱的设计。因为一旦某一项职责发生了改变,需要去更改代码,那么有可能会引起其他职责改变。所谓牵一发而动全身,这显然是我们所不愿意看到的,所以我们会把这个类分拆开来,由两个类来分别维护这两个职责,这样当一个职责发生改变,需要修改时,不会影响到另一个职责。

  需要说明的是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

3、关于职责

  看到上面所述,或许有人会说这么简单谁不知道。的确,很多程序员即使没有学过设计模式,不知道单一职责原则,在编程的时候,在设计软件时也会有意识的遵循这一原则。因为谁都不希望修改一个地方会引发另外一个地方出现问题,而避免这种问题的最好处理方式就是设计时遵循单一职责原则。但是,我认为单一职责原则的难点是在于职责范围的认定。关于职责的认定是一个仁者见仁智者见智的话题,在实际开发中也会引起程序员之间的争论。有的人认为这些功能方法的实现目的很相似,必须要放在一个类中,有的人认为方法差别很大,必须要分拆成多个类,在多个类里面来实现。

  还有职责的扩散问题。软件一开发完上线后并不是一成不变的,随着社会的进步,需求的变更,软件的功能可能要做些维护更改,有时候会遇到职责扩散。所谓的职责扩散就是因为某种原因,职责R被分化为粒度更细的R1和R2。

  比如类C只负责一个职责R,这是符合单一职责原则的。但是后来需要把职责R拆分为职责R1和职责R2,那么这时候是否需要死守着单一职责原则,把类C也拆开为C1和C2。接着如果R1又需要细化为R11和R12呢……

  我们必须要意识到,一味的遵守单一职责原则,不停的分拆类所付出的开销是很大的。这时候就涉及到平衡的问题,平衡单一职责原则与修改造成的开销。我的观点是如果一个方法逻辑不复杂的情况下,可以修改方法实现,否则要拆分为两个方法,遵循方法级别的单一职责原则;如果一个类方法不多的情况下,可以只增加方法,而不用分拆为多个类,否则要拆分为多个类,遵循类级别的单一职责原则。

4、遵循单一职责原则的优点

  • 降低了类的复杂度。一个类只负责一项职责比负责多项职责要简单得多。
  • 提高了代码的可读性。一个类简单了,可读性自然就提高了。
  • 提高了系统的可维护性。代码的可读性高了,并且修改一项职责对其他职责影响降低了,可维护性自然就提高了。
  • 变更引起的风险变低了。单一职责最大的优点就是修改一个功能,对其他功能的影响显著降低。

Servlet Listener

发表于 2016-09-06

Listener 监听器

1、Listener的定义与作用

监听器Listener就是在application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。

Listener是Servlet的监听器,可以监听客户端的请求,服务端的操作等。

2、Listener的分类与使用

主要有以下三类:

2.1、ServletContext监听

  • ServletContextListener:用于对Servlet整个上下文进行监听(创建、销毁)。
1
2
3
4
public void contextInitialized(ServletContextEvent sce);//上下文初始化
public void contextDestroyed(ServletContextEvent sce);//上下文销毁
public ServletContext getServletContext();//ServletContextEvent事件:取得一个ServletContext(application)对象
  • ServletContextAttributeListener:对Servlet上下文属性的监听(增删改属性)。
1
2
3
4
5
6
7
public void attributeAdded(ServletContextAttributeEvent scab);//增加属性
public void attributeRemoved(ServletContextAttributeEvent scab);//属性删除
public void attributeRepalced(ServletContextAttributeEvent scab);//属性替换(第二次设置同一属性)
//ServletContextAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值

2.2、Session监听

Session属于http协议下的内容,接口位于javax.servlet.http.*包下。

  • HttpSessionListener接口:对Session的整体状态的监听。
1
2
3
4
5
public void sessionCreated(HttpSessionEvent se);//session创建
public void sessionDestroyed(HttpSessionEvent se);//session销毁
//HttpSessionEvent事件:
public HttpSession getSession();//取得当前操作的session
  • HttpSessionAttributeListener接口:对session的属性监听。
1
2
3
4
5
6
7
8
public void attributeAdded(HttpSessionBindingEvent se);//增加属性
public void attributeRemoved(HttpSessionBindingEvent se);//删除属性
public void attributeReplaced(HttpSessionBindingEvent se);//替换属性
//HttpSessionBindingEvent事件:
public String getName();//取得属性的名称
public Object getValue();//取得属性的值
public HttpSession getSession();//取得当前的session

session的销毁有两种情况:

  • session超时,web.xml配置:
1
2
3
<session-config>
<session-timeout>120</session-timeout><!--session120分钟后超时销毁-->
</session-config>
  • 手工使session失效
1
2
//使session失效方法。session.invalidate();
public void invalidate();

2.3、Request监听

  • ServletRequestListener:用于对Request请求进行监听(创建、销毁)。
1
2
3
4
5
6
public void requestInitialized(ServletRequestEvent sre);//request初始化
public void requestDestroyed(ServletRequestEvent sre);//request销毁
//ServletRequestEvent事件:
public ServletRequest getServletRequest();//取得一个ServletRequest对象
public ServletContext getServletContext();//取得一个ServletContext(application)对象
  • ServletRequestAttributeListener:对Request属性的监听(增删改属性)。
1
2
3
4
5
6
7
public void attributeAdded(ServletRequestAttributeEvent srae);//增加属性
public void attributeRemoved(ServletRequestAttributeEvent srae);//属性删除
public void attributeReplaced(ServletRequestAttributeEvent srae);//属性替换(第二次设置同一属性)
//ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值

3、在web.xml中配置

Listener配置信息必须在Filter和Servlet配置之前,Listener的初始化(ServletContentListener初始化)比Servlet和Filter都优先,而销毁比Servlet和Filter都慢。

1
2
3
<listener>
<listener-class>com.listener.class</listener-class>
</listener>

4、Listener应用实例

4.1、Spring使用ContextLoaderListener加载ApplicationContext配置信息

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。

ContextLoaderListener如何查找ApplicationContext.xml的配置位置以及配置多个xml:如果在web.xml中不写任何参数配置信息,默认的路径是”/WEB-INF/applicationContext.xml”,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml(在MyEclipse中把xml文件放置在src目录下)。如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数。

1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-*.xml</param-value><!-- 采用的是通配符方式,查找WEB-INF/spring目录下xml文件。如有多个xml文件,以“,”分隔。 -->
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

4.2、Spring使用Log4jConfigListener配置Log4j日志

Spring使用Log4jConfigListener(实现了ServletContextListener接口)的好处:

  • 动态的改变记录级别和策略,不需要重启Web应用。
  • 把log文件定在 /WEB-INF/logs/ 而不需要写绝对路径。因为系统把web目录的路径压入一个叫webapp.root的系统变量。这样写log文件路径时不用写绝对路径了。
  • 可以把log4j.properties和其他properties一起放在/WEB-INF/ ,而不是Class-Path。
  • 设置log4jRefreshInterval时间,开一条watchdog线程每隔段时间扫描一下配置文件的变化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>project.root</param-value><!-- 用于定位log文件输出位置在web应用根目录下,log4j配置文件中写输出位置:log4j.appender.FILE.File=${project.root}/logs/project.log -->
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value><!-- 载入log4j配置文件 -->
</context-param>
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value><!--Spring刷新Log4j配置文件的间隔60秒,单位为millisecond-->
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

Servlet Filter

发表于 2016-09-06

Filter 过滤器

1. 简介

Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

它主要用于对用户请求进行预处理,也可以对HttpServletResponse 进行后处理。使用Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给Servlet 进行处理并生成响应,最后Filter 再对服务器响应进行后处理。

  Filter功能:

  • 在HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest 。 根据需要检查 HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
  • 在HttpServletResponse 到达客户端之前,拦截HttpServletResponse 。 根据需要检查 HttpServletResponse ,也可以修改HttpServletResponse头和数据。

2. 如何实现拦截

Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

  • 调用目标资源之前,让一段代码执行。
  • 是否调用目标资源(即是否让用户访问web资源)。

web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。

3. Filter开发两步走

    1. 编写java类实现Filter接口,并实现其doFilter方法。
    1. 在 web.xml 文件中使用和元素对编写的filter类进行注册,并设置它所能拦截的资源。

web.xml配置各节点介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>元素用于指定过滤器的完整的限定类名。
<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。
在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。
<filter-mapping>元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
<filter-name>子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字
<url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
<servlet-name>指定过滤器所拦截的Servlet名称。
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。
<dispatcher> 子元素可以设置的值及其意义:
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

4. Filter链

在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。

web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

5. Filter的生命周期

1
public void init(FilterConfig filterConfig) throws ServletException;//初始化

和编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

1
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;//拦截请求

这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。

1
public void destroy();//销毁

Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前该方法被调用。该方法在Filter的生命周期中仅执行一次,在这个方法中,可以释放过滤器使用的资源。

6. FilterConfig接口

用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得以下内容:

1
2
3
4
String getFilterName();//得到filter的名称。
String getInitParameter(String name);//返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
Enumeration getInitParameterNames();//返回过滤器的所有初始化参数的名字的枚举集合。
public ServletContext getServletContext();//返回Servlet上下文对象的引用。

7. Filter使用案例

1、使用Filter验证用户登录安全控制

先在web.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<filter>
<filter-name>SessionFilter</filter-name>
<filter-class>com.action.login.SessionFilter</filter-class>
<init-param>
<param-name>logonStrings</param-name><!-- 对登录页面不进行过滤 -->
<param-value>/project/index.jsp;login.do</param-value>
</init-param>
<init-param>
<param-name>includeStrings</param-name><!-- 只对指定过滤参数后缀进行过滤 -->
<param-value>.do;.jsp</param-value>
</init-param>
<init-param>
<param-name>redirectPath</param-name><!-- 未通过跳转到登录界面 -->
<param-value>/index.jsp</param-value>
</init-param>
<init-param>
<param-name>disabletestfilter</param-name><!-- Y:过滤无效 -->
<param-value>N</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SessionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

接着编写FilterServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* 判断用户是否登录,未登录则退出系统
*/
public class SessionFilter implements Filter {
public FilterConfig config;
public void destroy() {
this.config = null;
}
public static boolean isContains(String container, String[] regx) {
boolean result = false;
for (int i = 0; i < regx.length; i++) {
if (container.indexOf(regx[i]) != -1) {
return true;
}
}
return result;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest hrequest = (HttpServletRequest)request;
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response);
String logonStrings = config.getInitParameter("logonStrings"); // 登录登陆页面
String includeStrings = config.getInitParameter("includeStrings"); // 过滤资源后缀参数
String redirectPath = hrequest.getContextPath() + config.getInitParameter("redirectPath");// 没有登陆转向页面
String disabletestfilter = config.getInitParameter("disabletestfilter");// 过滤器是否有效
if (disabletestfilter.toUpperCase().equals("Y")) { // 过滤无效
chain.doFilter(request, response);
return;
}
String[] logonList = logonStrings.split(";");
String[] includeList = includeStrings.split(";");
if (!this.isContains(hrequest.getRequestURI(), includeList)) {// 只对指定过滤参数后缀进行过滤
chain.doFilter(request, response);
return;
}
if (this.isContains(hrequest.getRequestURI(), logonList)) {// 对登录页面不进行过滤
chain.doFilter(request, response);
return;
}
String user = ( String ) hrequest.getSession().getAttribute("useronly");//判断用户是否登录
if (user == null) {
wrapper.sendRedirect(redirectPath);
return;
}else {
chain.doFilter(request, response);
return;
}
}
public void init(FilterConfig filterConfig) throws ServletException {
config = filterConfig;
}
}

这样既可完成对用户所有请求,均要经过这个Filter进行验证用户登录。

2、防止中文乱码过滤器

项目使用spring框架时。当前台JSP页面和JAVA代码中使用了不同的字符集进行编码的时候就会出现表单提交的数据或者上传/下载中文名称文件出现乱码的问题,那就可以使用这个过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<filter>
<filter-name>Spring character encoding filter</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><!--true:无论request是否指定了字符集,都是encoding; false:如果request已指定一个字符集,则不使用encoding-->
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Spring character encoding filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Storm

发表于 2016-08-30

Storm 入门教程

topologies topology: 由用户编写的Storm集群中的业务处理逻辑
deamon: 守护进程
worker process: 工作进程
stream: 指Storm中的数据流
tuple: 指stream中的最小单元数据
primitive: 基件 指storm topology 的组成部分,比如 bolt(螺栓) 和 spout(喷嘴)

Storm 集群里的各种组件

从表面上看一个 Storm 集群 与 一个 Hadoop 集群相似,然而在 Hadoop 上运行 “MapReduce jobs”, 在 Storm 上运行 “topologies”, 但是 “jobs” 和 “topologies” 是非常不同的– 一个关键的不同是 MapReduce job 最终会结束,而一个 topology 是永远在等待消息并处理(直到你杀掉它)。

一个 Storm 集群中有两种节点(node):主节点和工作节点(指Storm集群中不同角色的服务器节点),主节点运行一个叫 “Nimbus” 的守护进程(daemon)跟 Hadoop 的 “任务跟踪器”(Jobtracker)类似。Nimbus 负责向集群中分发代码, 向各机器分配任务,以及监测故障。

每一个工作节点运行一个名叫 “Supervisor” 的守护进程。Supervisor 监听 Nimbus 指派到这个这台机器的任务,根据 Numbus 的指派信息来停止或启动工作进程(worker process) ,每一个 worker process 执行一个topology的子集,一个运行中的topology由跨越多个主机的多个 worker process 组成。

Storm cluster

在 Nimbus 和 Supervisors 之间的所有协调调度通过一个 Zookeeper 集群来完成。另外,Nimbus 守护进程和 Supervisor 守护进程都是快速失败 (fail-fast)和无状态的;所有的状态保存在 Zookeeper 或者本地磁盘中。这意味着你可以 kill -9 Nimbus 或者 Supervisors 他们会自动恢复,就像什么都没发生过一样。这种设计让 Storm 集群变的不可思议的稳定。

Topologies

在Strom上做实时计算, 需要创建 “Topology”,一个 topology 是一个计算过程的描述,一个 topology 中的每一个节点包含处理逻辑,节点之间的连接表明了数据在节点之间是如何传递的。

运行一个topology是很简单的。首先,将你所有的代码和依赖都打包到一个单独的jar包中,然后运行像下面这样的命令:

1
storm jar all-my-code.jar backtype.storm.MyTopology arg1 arg2

这样会传递arg1和 arg2参数给backtype.storm.MyTopology类,这个类的 main 方法定义topology 并将它提交到 Nimbus。Strom jar 负责连接 Nimbus 并上传jar包.

由于 topology 的定义本来就是 Thrift 结构,并且 Nimbus 是一个 Thrift 服务, 所以可以使用任何编程语言来创建和提交 topology。上面的方法是使用基于 JVM 的编程语言来完成的最简单的方法,参考Running topologies on a production cluster 来获取更多的关于开启和停止 topology 的方法。

阅读全文 »
1…222324…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces