Java 反射(reflection)/注释(Annotation)/监听器(Listener)/装饰器(wrapper)/过滤器(Filter)一锅煮

区分学习Java和JavaWeb中的几个特性,文章摘抄自网络,不定期更新和补充中。

反射(reflection)

1.1 作用

Java反射指的是可以于运行时加载/探知和使用编译期间完全未知的类。程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用他的任意一个方法和属性。

加载完类之后, 在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射。

1.2 实现原理

反射基于如下四个类实现:

java.lang.Class;                
java.lang.reflect.Constructor; java.lang.reflect.Field;        
java.lang.reflect.Method;
java.lang.reflect.Modifier;

反射的实现分为如下三步。

1.2.1 获取Class对象

Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。常用的获取Class对象的方法有三种:

1.使用Class类的静态方法。例如:

Class.forName("java.lang.String");

2. 使用类的“Class”方法。例如:

String.class;


3.使用对象的getClass()方法。如:

String str = "aa";
Class<?> classType1 = str.getClass();

1.2.2 创建对象

通过反射来生成对象的方式有两种:
1. 使用Class对象的newInstance()方法来创建该Class对象对应类的实例(这种方式要求该Class对象的对应类有默认构造器).

Class c =Class.forName("Employee");
 
 //创建此Class 对象所表示的类的一个新实例
Objecto = c.newInstance(); //调用了Employee的无参数构造方法.


2. 先使用Class对象获取指定的Constructor对象, 再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例).

  

// 使用getConstructors获取构造器    
Constructor<?>[] constructors = classType.getConstructors();  
for (Constructor<?> m : constructors)  
{  
    System.out.println(m);  
}  
               
System.out.println();  
               
// 使用getDeclaredConstructors获取构造器     
constructors = classType.getDeclaredConstructors();  
for (Constructor<?> m : constructors)  
{  
    System.out.println(m);  
}  
   
输出:  
public com.quincy.ExtendType()  
   
public com.quincy.ExtendType()  
com.quincy.ExtendType(int,java.lang.String)

 

可以调用无参的默认构造函数和有参构造函数完成任务:

Class<?> classType = ExtendType.class;
Constructor<?> constructor1 = classType.getConstructor();
Object inst = constructor1.newInstance();
System.out.println(inst);
 
Constructor<?> constructor2 = classType.getDeclaredConstructor(int.class, String.class);
Object inst = constructor2.newInstance(1, "123");
System.out.println(inst);

1.2.3 获取属性和方法

可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值。JAVA 的Class<T>类提供了几个方法获取类的属性。

public FieldgetField(String name)

返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段

public Field[] getFields()

返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段

public FieldgetDeclaredField(Stringname)

返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段

public Field[] getDeclaredFields()

返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段

getFields和getDeclaredFields区别:

getFields返回的是申明为public的属性,包括父类中定义,getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。

通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法,Class<T>类提供了几个方法获取类的方法。

public MethodgetMethod(String name,Class<?>... parameterTypes)

返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法

public Method[] getMethods()

返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法

public MethodgetDeclaredMethod(Stringname,Class<?>... parameterTypes)

返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法

public Method[] getDeclaredMethods()

返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

 

1.2.4 调用类的方法

通过反射获取类Method对象,调用Field的Invoke方法调用函数。

Class<?> classType = ExtendType.class;  
Object inst = classType.newInstance();  
Method logMethod = classType.getDeclaredMethod("Log", String.class);  
logMethod.invoke(inst, "test");

当调用的方法为private属性时调用不成功,需要在invoke前添加setAccessible。

Class<?> classType = ExtendType.class;  
Object inst = classType.newInstance();  
Method logMethod = classType.getDeclaredMethod("Log", String.class);  
logMethod.setAccessible(true);
logMethod.invoke(inst, "test");

1.3 应用场景

反射机制最常用的场景即JDBC中数据库引擎的加载(待学习补充,目前就学了这个)。

1.4 范例

使用JDBC的时候要去下载对应的驱动程序,使用mysql,就要去mysql的官网下载,使用oracle,就去oracle的官网下载,然后把类库导入到工程中。这些驱动程序,其实就是实现了JDBC规范的类库。我使用的是mysql。

1.首先通过反射com.mysql.jdbc.Driver类,实例化该类的时候会执行该类内部的静态代码块,该代码块会在Java实现的DriverManager类中注册自己,DriverManager管理所有已经注册的驱动类,当调用DriverManager.geConnection方法时会遍历这些驱动类,并尝试去连接数据库,只要有一个能连接成功,就返回Connection对象,否则则报异常。

2.通过使用DriverManager.geConnection(url,user,password)函数,传入url,数据库用户名,数据库密码,得到数据库的Connection对象。

com.mysql.jdbc.Driver是mysql驱动类的全名,oracle驱动类的全名是oracle.jdbc.driver.OracleDriver。

连接数据库时要传入相应的url,

mysql url的格式是:jdbc:mysql://<ip>:3306/<database_name>
oracle url的格式是:jdbc:oracle:thin:<ip>:1521:<database_name>

public void TestJDBC2(){
try {
//通过反射实例化com.mysql.jdbc.Driver,
Driver driver = (Driver)Class.forName("com.mysql.jdbc.Driver").newInstance();
//得到数据库的连接对象
Connection conn =
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/notedb","root","");
System.out.println(conn);
}  catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}


从上面可以看出,我们把数据库的驱动名和url写死在了程序中,那么更换数据库的时候还是需要更改程序代码的。

我们可以将信息写在配置普文件中,程序运行时读取配置信息,然后通过配置信息连接数据库,那么程序和数据库的耦合度就很低了,可以随时更换数据库。

 

1.在程序目录下新建jdbc.properties配置文件,并写入连接数据库有关的信息。

2.读取配置信息,获取连接数据库所需要的属性值。

3.通过JDBC连接数据库。


参考:http://blog.csdn.net/yongjian1092/article/details/7364451

http://www.cnblogs.com/justPassBy/p/5296271.html

 

 

注释(Annotation)

 

2.1 作用

Annotation (注解) 表示的是能够添加到Java源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据和程序元素进行关联。

Annotation 被称为注解,在Java开发中是相当常见的,通过注解,我们可以简化代码提高开发效率。例如Override Annotation,这个应该算是在开发过程中使用最多的注解了。

Annotation的作用可以分为三种:

a.标记作用,用于告诉编译器一些信息

b.编译时动态处理,如动态生成代码

c.运行时动态处理,如得到注解信息

这三个作用对应着后面自定义Annotation时说的@Retention三种值分别表示的Annotation

2.2 实现原理

Annotation分为三种:

a.标准 Annotation

包括 Override, Deprecated, SuppressWarnings,标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护)SuppressWarnings来忽略某项 Warning

b.元 Annotation

@Retention, @Target, @Inherited, @Documented,元 Annotation 是指用来定义 Annotation 的 Annotation。

—@Documented:是否会保存到 Javadoc 文档中

—@Retention:保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, Deprecated, SuppressWarnings

—@Target:可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有。

—@Inherited: 是否可以被继承,默认为 false

c.自定义 Annotation

自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation,这里只是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation。

Annotation的框架如下:

 

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
 
public class Main {
	public void TestJDBC(){
		//将配置文件变成输入流
		InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
		Properties properties = new Properties();
		try {
			//从流中获取配置信息到Properties对象
			properties.load(in);
			//读取配置文件
			String driverClass = properties.getProperty("driver");
			String jdbcUrl = properties.getProperty("jdbcUrl");
			String user = properties.getProperty("user");
			String password = properties.getProperty("password");
			//注册驱动
			Driver driver = (Driver)Class.forName(driverClass).newInstance();
			Connection conn = DriverManager.getConnection(jdbcUrl,user,password);
			System.out.println(conn);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Main m = new Main();
		m.TestJDBC2();
	}
 
}

 

从中,我们可以看出:

(01) 1个Annotation 和 1个RetentionPolicy关联。

       可以理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性。

(02) 1个Annotation 和 1~n个ElementType关联。

       可以理解为:对于每1个Annotation对象,可以有若干个ElementType属性。

(03) Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override等等。

       Annotation 的每一个实现类,都“和1个RetentionPolicy关联”并且“和1~n个ElementType关联”。

Annotation定义的语法如下:

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}

说明:

上面的作用是定义一个Annotation,它的名字是MyAnnotation1。定义了MyAnnotation1之后,我们可以在代码中通过“@MyAnnotation1”来使用它。

其它的,@Documented, @Target, @Retention, @interface都是来修饰MyAnnotation1的。下面分别说说它们的含义:

 

 

(01) @interface

       使用@interface定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation。

       定义Annotation时,@interface是必须的。

       注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节都由编译器完成。通过@interface定义注解后,该注解不能继承其他的注解或接口。

 

(02) @Documented 

       类和方法的Annotation在缺省情况下是不出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中。

       定义Annotation时,@Documented可有可无;若没有定义,则Annotation不会出现在javadoc中。

 

(03) @Target(ElementType.TYPE)

      前面我们说过,ElementType 是Annotation的类型属性。而@Target的作用,就是来指定Annotation的类型属性。

      @Target(ElementType.TYPE) 的意思就是指定该Annotation的类型是ElementType.TYPE。这就意味着,MyAnnotation1是来修饰“类、接口(包括注释类型)或枚举声明”的注解。

      定义Annotation时,@Target可有可无。若有@Target,则该Annotation只能用于它所指定的地方;若没有@Target,则该Annotation可以用于任何地方。

 

(04) @Retention(RetentionPolicy.RUNTIME)

      前面我们说过,RetentionPolicy 是Annotation的策略属性,而@Retention的作用,就是指定Annotation的策略属性。

      @Retention(RetentionPolicy.RUNTIME) 的意思就是指定该Annotation的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将该Annotation信息保留在.class文件中,并且能被虚拟机读取。

      定义Annotation时,@Retention可有可无。若没有@Retention,则默认是RetentionPolicy.CLASS。

Java源代码被编译时,编译器的一个插件annotation处理器则会处理这些annotation。处理器可以产生报告信息,或者创建附加的Java源文件或资源。如果annotation本身被加上了RententionPolicy的运行时类,则Java编译器则会将annotation的元数据存储到class文件中。然后,Java虚拟机或其他的程序可以查找这些元数据并做相应的处理。

 

当然除了annotation处理器可以处理annotation外,我们也可以使用反射自己来处理annotation。Java SE 5有一个名为AnnotatedElement的接口,Java的反射对象类Class,Constructor,Field,Method以及Package都实现了这个接口。这个接口用来表示当前运行在Java虚拟机中的被加上了annotation的程序元素。通过这个接口可以使用反射读取annotation。AnnotatedElement接口可以访问被加上RUNTIME标记的annotation,相应的方法有getAnnotation,getAnnotations,isAnnotationPresent。由于Annotation类型被编译和存储在二进制文件中就像class一样,所以可以像查询普通的Java对象一样查询这些方法返回的Annotation。

2.3 应用场景

Annotation被广泛用于各种框架和库中Junit/Spring/Hibernate等。

2.4 示例

自定义注解分为注解定义和注解处理器的定义两部分,RUNMTIME类型的注解处理器在JavaWeb中也可配合Filter来进行请求的预处理。

开发用于配置Servlet的相关注解(Servlet2.5使用注解模拟Servlet3.0的WebServlet注解)

1、开发WebServlet注解,用于标注处理请求的Servlet类

package me.gacl.annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * 自定义WebServlet注解,模拟Servlet3.0的WebServlet注解
 * @Target 注解的属性值表明了 @WebServlet注解只能用于类或接口定义声明的前面,
 * @WebServlet注解有一个必填的属性 value 。
 * 调用方式为: @WebServlet(value="/xxxx") ,
 * 因语法规定如果属性名为 value 且只填 value属性值时,可以省略 value属性名,即也可以写作:@WebServlet("/xxxx")
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {
    //Servlet的访问URL
    String value();
    //Servlet的访问URL
    String[] urlPatterns() default {""};
    //Servlet的描述
    String description() default "";
    //Servlet的显示名称
    String displayName() default "";
    //Servlet的名称
    String name() default "";
    //Servlet的init参数
    WebInitParam[] initParams() default {};
}


Servlet在web.xml中的配置信息使用WebServlet注解来表示,使用注解后,只需要在相应Servlet 类的前面使用类似@WebServlet("/servlet/LoginServlet") 注解就可以达到和上述 web.xml 文件中配置信息一样的目的。注解@WebServlet中的属性值"/servlet/LoginServlet"表示了web.xml 配置文件中 <servlet-mapping> 元素的子元素 <url-pattern> 里的值。通过这样的注解能简化在 XML 文件中配置 Servlet 信息,整个配置文件将会非常简洁干净,开发人员的工作也将大大减少。

2、开发WebInitParam注解,用于配置Servlet初始化时使用的参数

package me.gacl.annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
* @ClassName: WebInitParam
* @Description: 定义Servlet的初始化参数注解
* @author: 孤傲苍狼
* @date: 2014-10-1 下午3:25:53
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebInitParam {
    //参数名
    String paramName() default "";
    //参数的值
    String paramValue() default "";
}


3、编写处理注解的处理器

 

上面简要地介绍了注解的定义声明与使用方式,注解在后台需要一个处理器才能起作用,所以还得针对上面的注解编写处理器,在这里我们使用Filter作为注解的处理器,编写一个AnnotationHandleFilter,代码如下:

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.WebInitParam;
import me.gacl.annotation.WebServlet;
import me.gacl.util.ScanClassUtil;
 
/**
* @ClassName: AnnotationHandleFilter
* @Description: 使用Filter作为注解的处理器
* @author: 孤傲苍狼
* @date: 2014-11-12 下午10:15:19
*
*/
public class AnnotationHandleFilter implements Filter {
 
    private ServletContext servletContext = null;
    
    /* 过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("---AnnotationHandleFilter过滤器初始化开始---");
        servletContext = filterConfig.getServletContext();
        Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
        //获取web.xml中配置的要扫描的包
        String basePackage = filterConfig.getInitParameter("basePackage");
        //如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
        if (basePackage.indexOf(",")>0) {
            //按逗号进行分隔
            String[] packageNameArr = basePackage.split(",");
            for (String packageName : packageNameArr) {
                addServletClassToServletContext(packageName,classMap);
            }
        }else {
            addServletClassToServletContext(basePackage,classMap);
        }
        System.out.println("----AnnotationHandleFilter过滤器初始化结束---");
    }
    
    /**
    * @Method: addServletClassToServletContext
    * @Description:添加ServletClass到ServletContext中
    * @Anthor:孤傲苍狼
    *
    * @param packageName
    * @param classMap
    */
    private void addServletClassToServletContext(String packageName,Map<String, Class<?>> classMap){
        Set<Class<?>> setClasses =  ScanClassUtil.getClasses(packageName);
        for (Class<?> clazz :setClasses) {
            if (clazz.isAnnotationPresent(WebServlet.class)) {
                //获取WebServlet这个Annotation的实例
                WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
                //获取Annotation的实例的value属性的值
                String annotationAttrValue = annotationInstance.value();
                if (!annotationAttrValue.equals("")) {
                    classMap.put(annotationAttrValue, clazz);
                }
                //获取Annotation的实例的urlPatterns属性的值
                String[] urlPatterns = annotationInstance.urlPatterns();
                for (String urlPattern : urlPatterns) {
                    classMap.put(urlPattern, clazz);
                }
                servletContext.setAttribute("servletClassMap", classMap);
                System.out.println("annotationAttrValue:"+annotationAttrValue);
                String targetClassName = annotationAttrValue.substring(annotationAttrValue.lastIndexOf("/")+1);
                System.out.println("targetClassName:"+targetClassName);
                System.out.println(clazz);
            }
        }
    }
 
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        System.out.println("---进入注解处理过滤器---");
        //将ServletRequest强制转换成HttpServletRequest
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        Map<String, Class<?>> classMap = (Map<String, Class<?>>) servletContext.getAttribute("servletClassMap");
        //获取contextPath
        String contextPath = req.getContextPath();
        //获取用户请求的URI资源
        String uri = req.getRequestURI();
        //如果没有指明要调用Servlet类中的哪个方法
        if (uri.indexOf("!")==-1) {
            //获取用户使用的请求方式
            String reqMethod = req.getMethod();
            //获取要请求的servlet路径
            String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("."));
            //获取要使用的类
            Class<?> clazz = classMap.get(requestServletName);
            //创建类的实例
            Object obj = null;
            try {
                obj = clazz.newInstance();
            } catch (InstantiationException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            }
            Method targetMethod = null;
            if (reqMethod.equalsIgnoreCase("get")) {
                try {
                     targetMethod = clazz.getDeclaredMethod("doGet",HttpServletRequest.class,HttpServletResponse.class);
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }else {
                try {
                    targetMethod = clazz.getDeclaredMethod("doPost",HttpServletRequest.class,HttpServletResponse.class);
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
            
            try {
                //调用对象的方法进行处理
                targetMethod.invoke(obj,req,res);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }else {
            //获取要请求的servlet路径
            String requestServletName = uri.substring(contextPath.length(),uri.lastIndexOf("!"));
            //获取要调用的servlet的方法
            String invokeMethodName = uri.substring(uri.lastIndexOf("!")+1,uri.lastIndexOf("."));
        
            //获取要使用的类
            Class<?> clazz = classMap.get(requestServletName);
            //创建类的实例
            Object obj = null;
            try {
                obj = clazz.newInstance();
            } catch (InstantiationException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            }
            //获得clazz类定义的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            //获取WebServlet这个Annotation的实例
            WebServlet annotationInstance = clazz.getAnnotation(WebServlet.class);
            //获取注解上配置的初始化参数数组
            WebInitParam[] initParamArr = annotationInstance.initParams();
            Map<String, String> initParamMap = new HashMap<String, String>();
            for (WebInitParam initParam : initParamArr) {
                initParamMap.put(initParam.paramName(), initParam.paramValue());
            }
            //遍历clazz类中的方法
            for (Method method : methods) {
                //该方法的返回类型
                Class<?> retType = method.getReturnType();
                //获得方法名
                String methodName = method.getName();
                //打印方法修饰符
                System.out.print(Modifier.toString(method.getModifiers()));
                System.out.print(" "+retType.getName() + " " + methodName +"(");
                //获得一个方法参数数组(getparameterTypes用于返回一个描述参数类型的Class对象数组)
                Class<?>[] paramTypes = method.getParameterTypes();
                for(int j = 0 ; j < paramTypes.length ; j++){
                     //如果有多个参数,中间则用逗号隔开,否则直接打印参数
                    if (j > 0){
                        System.out.print(",");
                    }  
                    System.out.print(paramTypes[j].getName());
                }
                System.out.println(");");
                if (method.getName().equalsIgnoreCase("init")) {
                    try {
                        //调用Servlet的初始化方法
                        method.invoke(obj, initParamMap);
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
            //获取WebServlet这个Annotation的实例
            System.out.println("invokeMethodName:"+invokeMethodName);
            try {
                try {
                    //利用反射获取方法实例,方法的签名必须符合:
                    //public void 方法名(HttpServletRequest request, HttpServletResponse response)
                    //例如:public void loginHandle(HttpServletRequest request, HttpServletResponse response)
                    Method targetMethod = clazz.getDeclaredMethod(invokeMethodName,HttpServletRequest.class,HttpServletResponse.class);
                    //调用对象的方法进行处理
                    targetMethod.invoke(obj,req,res);
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
 
    public void destroy() {
 
    }
}


AnnotationHandleFilter过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类,然后将类存储到一个Map集合中,再将Map集合存储到servletContext对象中。

 

web.xml文件中配置AnnotationHandleFilter过滤器和需要扫描的包:

<filter>
<description>注解处理过滤器</description>
<filter-name>AnnotationHandleFilter</filter-name>
        <filter-class>me.gacl.web.filter.AnnotationHandleFilter</filter-class>
        <init-param>
            <description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
            <param-name>basePackage</param-name>
            <param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
            <!-- <param-value>me.gacl.web.controller</param-value> -->
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>AnnotationHandleFilter</filter-name>
        <!-- 拦截后缀是.do的请求 -->
        <url-pattern>*.do</url-pattern>
    </filter-mapping>


AnnotationHandleFilter过滤器初始化方法init(FilterConfig filterConfig)使用到了一个用于扫描某个包下面的类的工具类ScanClassUtil,ScanClassUtil的代码如下:

package me.gacl.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
 
public class ScanClassUtil {
 
    /**
     * 从包package中获取所有的Class
     *
     * @param pack
     * @return
     */
    public static Set<Class<?>> getClasses(String pack) {
 
        // 第一个class类的集合
        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageName = pack;
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(
                    packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    System.err.println("file类型的扫描");
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath,
                            recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定义一个JarFile
                    System.err.println("jar类型的扫描");
                    JarFile jar;
                    try {
                        // 获取jar
                        jar = ((JarURLConnection) url.openConnection())
                                .getJarFile();
                        // 从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代
                        while (entries.hasMoreElements()) {
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的
                            if (name.charAt(0) == '/') {
                                // 获取后面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包
                                if (idx != -1) {
                                    // 获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx)
                                            .replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包
                                if ((idx != -1) || recursive) {
                                    // 如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class")
                                            && !entry.isDirectory()) {
                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(
                                                packageName.length() + 1, name
                                                        .length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class
                                                    .forName(packageName + '.'
                                                            + className));
                                        } catch (ClassNotFoundException e) {
                                            // log
                                            // .error("添加用户自定义视图类错误 找不到此类的.class文件");
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        // log.error("在扫描用户定义视图时从jar包获取文件出错");
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        return classes;
    }
    
    /**
     * 以文件的形式来获取包下的所有Class
     *
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    public static void findAndAddClassesInPackageByFile(String packageName,
            String packagePath, final boolean recursive, Set<Class<?>> classes) {
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
                return (recursive && file.isDirectory())
                        || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : dirfiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "."
                        + file.getName(), file.getAbsolutePath(), recursive,
                        classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0,
                        file.getName().length() - 6);
                try {
                    // 添加到集合中去
                    //classes.add(Class.forName(packageName + '.' + className));
                     //经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));  
                    } catch (ClassNotFoundException e) {
                    // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
                    e.printStackTrace();
                }
            }
        }
    }
}


由此实现了基于注解的方式,特别注意基于注解的开发重点在与filter结合时的注解处理器。

监听器(Listener)

3.1 作用

        监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。

3.2 实现原理

3.2.1 Java Listener

可以按照严格的事件处理模型来设计一个对象,这个对象就可以被别的对象监听,事件处理模型涉及到三个组件:事件源、事件对象、事件监听器。Listener的实现基于回调函数实现。

3.2.2 JavaWeb Listener

listener是通过观察者设计模式进行实现的。观察者模式又叫发布订阅模式或者监听器模式。在该模式中有两个角色:观察者和被观察者(通常也叫做主题)。观察者在主题里面注册自己感兴趣的事件,当这个事件发生时,主题会通过回调接口的方式通知观察者。

对于JavaWeb里面的监听器,Servlet规范定义了一些列的Listener接口类,通过接口类的方式将事件暴露给应用程序,应用程序如果想监听其感兴趣的事件,那么不必去直接注册对应的事件,而是编写自己的listener实现相应的接口类,并将自己的listener注册到servlet容器。当程序关心的事件发生时,servlet容器会通知listener,回调listener里面的方法。这里自定义的listener就是观察者,servlet容器就是主题。

Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为ServletContext,HttpSession和ServletRequest这三个域对象,Servlet规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型:

1. 监听域对象自身的创建和销毁的事件监听器。

2. 监听域对象中的属性的增加和删除的事件监听器。

3. 监听绑定到HttpSession域中的某个对象的状态的事件监听器。

 

3.3 应用场景

应用在桌面开发和JavaWeb中。

3.4 范例

Java桌面开发中使用Listener监听消息示例如下:

package me.gacl.listener.demo;
 
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
 
public class Demo1 {
 
    /**
     *java的事件监听机制
     *1、事件监听涉及到三个组件:事件源、事件对象、事件监听器
     *2、当事件源上发生某一个动作时,它会调用事件监听器的一个方法,并在调用该方法时把事件对象传递进去,
     *    开发人员在监听器中通过事件对象,就可以拿到事件源,从而对事件源进行操作。
     */
    public static void main(String[] args) {
        
        Frame f = new Frame();
        f.setSize(400, 400);
        f.setVisible(true);
        
        //注册事件监听器
        f.addWindowListener(new WindowListener(){
 
            public void windowActivated(WindowEvent e) {
                
            }
 
            public void windowClosed(WindowEvent e) {
                
            }
 
            /**
             * 当window窗体关闭时就会WindowListener这个监听器监听到,
             * 监听器就会调用windowClosing方法处理window窗体关闭时的动作
             */
            public void windowClosing(WindowEvent e) {
                //通过事件对象e来获取事件源对象
                Frame f = (Frame) e.getSource();
                System.out.println(f+"窗体正在关闭");
                f.dispose();
            }
 
            public void windowDeactivated(WindowEvent e) {
                
            }
 
            public void windowDeiconified(WindowEvent e) {
                
            }
 
            public void windowIconified(WindowEvent e) {
                
            }
 
            public void windowOpened(WindowEvent e) {
                
            }
        });
    }
}


我们平时做开发的时候,我们是写监听器去监听其他对象,那么我们如果想设计一个对象,让这个对象可以被别的对象监听又该怎么做呢,可以按照严格的事件处理模型来设计一个对象,这个对象就可以被别的对象监听,事件处理模型涉及到三个组件:事件源、事件对象、事件监听器。

  下面我们来按照事件处理模型来设计一个Person对象具体代码如下:

package me.gacl.observer;
 
/**
* @ClassName: Person(事件源)
* @Description: 设计一个Person类作为事件源,这个类的对象的行为(比如吃饭、跑步)可以被其他的对象监听
* @author: 孤傲苍狼
* @date: 2014-9-9 下午9:26:06
*
*/  
public class Person {
    /**
    * @Field: listener
    *          在Person类中定义一个PersonListener变量来记住传递进来的监听器
    */
    private PersonListener listener;
 
    /**
    * @Method: eat
    * @Description: 设计Person的行为:吃
    * @Anthor:孤傲苍狼
    *
    */
    public void eat() {
        if (listener != null) {
            /**
             * 调用监听器的doeat方法监听Person类对象eat(吃)这个动作,将事件对象Event传递给doeat方法,
             * 事件对象封装了事件源,new Event(this)中的this代表的就是事件源
             */
            listener.doeat(new Event(this));
        }
    }
 
    /**
    * @Method: run
    * @Description: 设计Person的行为:跑
    * @Anthor:孤傲苍狼
    *
    */
    public void run() {
        if (listener != null) {
            /**
             * 调用监听器的dorun方法监听Person类对象run(跑)这个动作,将事件对象Event传递给doeat方法,
             * 事件对象封装了事件源,new Event(this)中的this代表的就是事件源
             */
            listener.dorun(new Event(this));
        }
    }
 
    /**
    * @Method: registerListener
    * @Description: 这个方法是用来注册对Person类对象的行为进行监听的监听器
    * @Anthor:孤傲苍狼
    *
    * @param listener
    */
    public void registerListener(PersonListener listener) {
        this.listener = listener;
    }
}
 
/**
* @ClassName: PersonListener(事件监听器)
* @Description: 设计Person类(事件源)的监听器接口
* @author: 孤傲苍狼
* @date: 2014-9-9 下午9:28:06
*
*/
interface PersonListener {
 
    /**
    * @Method: doeat
    * @Description: 这个方法是用来监听Person对象eat(吃)这个行为动作,
    *                 当实现类实现doeat方法时就可以监听到Person类对象eat(吃)这个动作
    * @Anthor:孤傲苍狼
    *
    * @param e
    */
    void doeat(Event e);
 
    /**
    * @Method: dorun
    * @Description: 这个方法是用来监听Person对象run(跑)这个行为动作,
    *                 当实现类实现dorun方法时就可以监听到Person类对象run(跑)这个动作
    * @Anthor:孤傲苍狼
    *
    * @param e
    */
    void dorun(Event e);
 
}
 
/**
* @ClassName: Event(事件对象)
* @Description:设计事件类,用来封装事件源
* @author: 孤傲苍狼
* @date: 2014-9-9 下午9:37:56
*
*/
class Event {
 
    /**
    * @Field: source
    *          事件源(Person就是事件源)
    */
    private Person source;
 
    public Event() {
        
    }
 
    public Event(Person source) {
        this.source = source;
    }
 
    public Person getSource() {
        return source;
    }
 
    public void setSource(Person source) {
        this.source = source;
    }
}


经过这样的设计之后,Peron类的对象就是可以被其他对象监听了。测试代码如下:

package me.gacl.observer;
 
public class PersonTest {
 
    /**
     * @Method: main
     * @Description: 测试Person类
     * @Anthor:孤傲苍狼
     *
     * @param args
     */
    public static void main(String[] args) {
        //
        Person p = new Person();
        //注册监听p对象行为的监听器
        p.registerListener(new PersonListener() {
            //监听p吃东西这个行为
            public void doeat(Event e) {
                Person p = e.getSource();
                System.out.println(p + "在吃东西");
            }
            //监听p跑步这个行为
            public void dorun(Event e) {
                Person p = e.getSource();
                System.out.println(p + "在跑步");
            }
        });
        //p在吃东西
        p.eat();
        //p在跑步
        p.run();
    }
}


JavaWeb

监听ServletContext域对象的创建和销毁

ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。实现了ServletContextListener接口的类都可以对ServletContext对象的创建和销毁进行监听。

ServletContext对象被创建时,激发contextInitialized (ServletContextEvent sce)方法。  

ServletContext对象被销毁时,激发contextDestroyed(ServletContextEvent sce)方法。  

ServletContext域对象创建和销毁时机:

  创建:服务器启动针对每一个Web应用创建ServletContext

  销毁:服务器关闭前先关闭代表每一个web应用的ServletContext

范例:编写一个MyServletContextListener类,实现ServletContextListener接口,监听ServletContext对象的创建和销毁

1、编写监听器,代码如下:

package me.gacl.web.listener;
 
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
 
/**
* @ClassName: MyServletContextListener
* @Description: MyServletContextListener类实现了ServletContextListener接口,
*                 因此可以对ServletContext对象的创建和销毁这两个动作进行监听。
* @author: 孤傲苍狼
* @date: 2014-9-9 下午10:26:16
*
*/
public class MyServletContextListener implements ServletContextListener {
 
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext对象创建");
    }
 
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext对象销毁");
    }
}


2、在web.xml文件中注册监听器

  我们在上面的中讲到,要想监听事件源,那么必须将监听器注册到事件源上才能够实现对事件源的行为动作进行监听,在JavaWeb中,监听的注册是在web.xml文件中进行配置的,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
    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_3_0.xsd">
  <display-name></display-name>    
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- 注册针对ServletContext对象进行监听的监听器 -->
  <listener>
      <description>ServletContextListener监听器</description>
      <!--实现了ServletContextListener接口的监听器类 -->
      <listener-class>me.gacl.web.listener.MyServletContextListener</listener-class>
  </listener>
  
</web-app>


经过这两个步骤,我们就完成了监听器的编写和注册,Web服务器在启动时,就会自动把在web.xml中配置的监听器注册到ServletContext对象上,这样开发好的MyServletContextListener监听器就可以对ServletContext对象进行监听了。
转载:http://www.cnblogs.com/xdp-gacl/p/3961929.html

装饰器(wrapper)

4.1 作用

Wrapper在Java中指八个和基本数据类型对应的类(Wrapper Class),有些地方也翻译为外覆类或数据类型类。

对于包装类说,这些类的用途主要包含两种:

                   a、作为和基本数据类型对应的类类型存在,方便涉及到对象的操作。

                   b、包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法

Wrapper在Java中也指设计模式——装饰器,装饰模式(Decorator)也叫包装器模式(Wrapper)。GOF在《设计模式》一书中给出的定义为:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。

4.2 实现原理

对于装饰模式作如下解释:

装饰模式的角色

抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。

具体构件角色(Concrete Component):定义将要接收附加责任的类。

装饰角色(Decorator):持有一个构件(Component)对象的引用,并定义一个与抽象构件接口一致的接口。

具体装饰角色(Concrete Decorator):负责给构件对象“贴上”附加的责任。

对于一个接口Component的定义如下:

public interface Component
{
    public void doSomething();
 
}

接口的具体实现如下:

public class ConcreteComponent implements Component
{
 
    @Override
    public void doSomething()
    {
        System.out.println("功能A");
    }
 
}

装饰角色:

public class Decorator implements Component
{
    private Component component;
 
    public Decorator(Component component)
    {
        this.component = component;
    }
 
    @Override
    public void doSomething()
    {
 
        component.doSomething();
    }
 
}

其中包含了构件角色的引用,方法调用中利用构件角色的方法。

具体装饰角色(两个):

public class ConcreteDecorator1 extends Decorator
{
    public ConcreteDecorator1(Component component)
    {
        super(component);
    }
    
    @Override
    public void doSomething()
    {
        super.doSomething();
        
        this.doAnotherThing();
    }
    
    private void doAnotherThing()
    {
        System.out.println("功能B");
    }
 
}
public class ConcreteDecorator2 extends Decorator
{
    public ConcreteDecorator2(Component component)
    {
        super(component);
    }
    @Override
    public void doSomething()
    {
        super.doSomething();
        
        this.doAnotherThing();
    }
    
    private void doAnotherThing()
    {
        System.out.println("功能C");
    }
 
}

使用测试如下:

public class Client
{
    public static void main(String[] args)
    {
        Component component = new ConcreteComponent();
        
        Component component1 = new ConcreteDecorator1(component);
        
        component1.doSomething();
        System.out.println("-----------");
        
        Component component2 = new ConcreteDecorator2(component1);
        
        component2.doSomething();
    }
 
}

 

4.3 应用场景

 Java I/O

4.4 示例

Java的IO操作也是装饰模式的集中体现:


package cn.gacl.test;
 
import java.io.*;
 
public class TestBufferStream {
    public static void main(String args[]) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:/java/TestFileInputStream.java");
            // 在FileInputStream节点流的外面套接一层处理流BufferedInputStream
            BufferedInputStream bis = new BufferedInputStream(fis);
            int c = 0;
            System.out.println((char) bis.read());
            System.out.println((char) bis.read());
            bis.mark(100);// 在第100个字符处做一个标记
            for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
                System.out.print((char) c);
            }
            System.out.println();
            bis.reset();// 重新回到原来标记的地方
            for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
                System.out.print((char) c);
            }
            bis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
}
package cn.galc.test;
 
import java.io.*;
 
public class TestFileOutputStream {
    public static void main(String args[]) {
        int b = 0;
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream("D:\\Java\\MyEclipse 10\\Workspaces\\AnnotationTest\\src\\cn\\galc\\test\\MyMouseAdapter.java");
            out = new FileOutputStream("D:/java/TestFileOutputStream1.java");
            // 指明要写入数据的文件,如果指定的路径中不存在TestFileOutputStream1.java这样的文件,则系统会自动创建一个
            while ((b = in.read()) != -1) {
                out.write(b);
                // 调用write(int c)方法把读取到的字符全部写入到指定文件中去
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            System.out.println("文件读取失败");
            System.exit(-1);// 非正常退出
        } catch (IOException e1) {
            System.out.println("文件复制失败!");
            System.exit(-1);
        }
        System.out
                .println("TestFileInputStream.java文件里面的内容已经成功复制到文件TestFileOutStream1.java里面");
    }
}


转载:http://www.cnblogs.com/xdp-gacl/p/3634409.html

过滤器(Filter)

5.1 作用

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

5.2 实现原理

Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。

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

Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。

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

String getFilterName():得到filter的名称。

String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.

Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。

public ServletContext getServletContext():返回Servlet上下文对象的引用。

 

5.3 应用场景

 

5.4 范例

Filter的部署分为两个步骤:

 

1、注册Filter

2、映射Filter

package me.gacl.web.filter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.WebInitParam;
import me.gacl.annotation.WebServlet;
import me.gacl.util.ScanClassUtil;
 
/**
* @ClassName: AnnotationHandleFilter
* @Description: 使用Filter作为注解的处理器
* @author: 孤傲苍狼
* @date: 2014-11-12 下午10:15:19
*
*/
public class FilterDemo01 implements Filter {
 
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) sResponse;
        response.addHeader("x-frame-options","SAMEORIGIN"); 
    }
 
    public void destroy() {
 
    }
}



在web. xml中配置过滤器:
 
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
    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_3_0.xsd">
  <display-name></display-name>    
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <!--配置过滤器-->
  <filter>
      <filter-name>FilterDemo01</filter-name>
      <filter-class>me.gacl.web.filter.FilterDemo01</filter-class>
  </filter>
  
  <!--映射过滤器-->
  <filter-mapping>
      <filter-name>FilterDemo01</filter-name>
      <!--“/*”表示拦截所有的请求 -->
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  
</web-app>


Filter的注册中
<description>用于添加描述信息,该元素的内容可为空,<description>可以不配置。
<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>元素用于指定过滤器的完整的限定类名。
<init-param>元素用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么<init-param>元素可以不配置。
Filter的映射中:
<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 对资源的多种调用方式进行拦截。如下:
 
 
在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
Wrapper模式设计的Filter增强request的功能范例如下:
package me.gacl.web.filter;
 
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.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
 
/**
* @ClassName: CharacterEncodingFilter
* @Description: 此过滤器用来解决解决get、post请求方式下的中文乱码问题
* @author: 孤傲苍狼
* @date: 2014-8-31 下午11:09:37
*
*/
public class CharacterEncodingFilter implements Filter {
 
    private FilterConfig filterConfig = null;
    //设置默认的字符编码
    private String defaultCharset = "UTF-8";
 
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //得到在web.xml中配置的字符编码
        String charset = filterConfig.getInitParameter("charset");
        if(charset==null){
            charset = defaultCharset;
        }
        request.setCharacterEncoding(charset);
        response.setCharacterEncoding(charset);
        response.setContentType("text/html;charset="+charset);
        
        MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
        chain.doFilter(requestWrapper, response);
    }
 
    public void init(FilterConfig filterConfig) throws ServletException {
        //得到过滤器的初始化配置信息
        this.filterConfig = filterConfig;
    }
    
    public void destroy() {
 
    }
}
 
/**
* @ClassName: MyCharacterEncodingRequest
* @Description: Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,
* (HttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request对象的对应方法)
* 以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
* 所以当需要增强request对象时,只需要写一个类继承HttpServletRequestWrapper类,然后在重写需要增强的方法即可
* @author: 孤傲苍狼
* @date: 2014-9-2 下午10:42:57
*     1.实现与被增强对象相同的接口
    2、定义一个变量记住被增强对象
    3、定义一个构造函数,接收被增强对象
    4、覆盖需要增强的方法
    5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法
*/
class MyCharacterEncodingRequest extends HttpServletRequestWrapper{
    //定义一个变量记住被增强对象(request对象是需要被增强的对象)
    private HttpServletRequest request;
    //定义一个构造函数,接收被增强对象
    public MyCharacterEncodingRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    /* 覆盖需要增强的getParameter方法
     * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
     */
    @Override
    public String getParameter(String name) {
        try{
            //获取参数的值
            String value= this.request.getParameter(name);
            if(value==null){
                return null;
            }
            //如果不是以get方式提交数据的,就直接返回获取到的值
            if(!this.request.getMethod().equalsIgnoreCase("get")) {
                return value;
            }else{
                //如果是以get方式提交数据的,就对获取到的值进行转码处理
                value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
                return value;
            }
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}


在web.xml文件中配置CharacterEncodingFilter
<!--配置字符过滤器,解决get、post请求方式下的中文乱码问题-->
  <filter>
      <filter-name>CharacterEncodingFilter</filter-name>
      <filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class>
  </filter>
  
  <filter-mapping>
      <filter-name>CharacterEncodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>


编写jsp测试页面,如下

<%@ page language="java" pageEncoding="UTF-8"%>
<%--引入jstl标签库 --%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML>
<html>
  <head>
    <title>使用字符过滤器解决解决get、post请求方式下的中文乱码问题</title>
  </head>
  
  <body>
       <%--使用c:url标签构建url,构建好的url存储在servletDemo1变量中--%>
       <c:url value="/servlet/ServletDemo1" scope="page" var="servletDemo1">
           <%--构建的url的附带的中文参数 ,参数名是:username,值是:孤傲苍狼--%>
           <c:param name="username" value="孤傲苍狼"></c:param>
       </c:url>
      <%--使用get的方式访问 --%>
       <a href="${servletDemo1}">超链接(get方式请求)</a>
       <hr/>
       <%--使用post方式提交表单 --%>
       <form action="${pageContext.request.contextPath}/servlet/ServletDemo1" method="post">
           用户名:<input type="text" name="username" value="孤傲苍狼" />
           <input type="submit" value="post方式提交">
       </form>
       
  </body>
</html>


 
转载:http://www.cnblogs.com/xdp-gacl/p/3952405.html



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值