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

2018-01-10 08:56:00
六月
来源:
https://www.2cto.com/kf/201708/665618.html
转贴 1356

Java 反射(reflection)/注释(Annotation)/监听器(Listener)/装饰器(wrapper)/过滤器(Filter)一锅煮。区分学习Java和JavaWeb中的几个特性。

二 注释(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的框架如下:

?
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
importjava.io.IOException;
importjava.io.InputStream;
importjava.sql.Connection;
importjava.sql.Driver;
importjava.sql.DriverManager;
importjava.sql.SQLException;
importjava.util.Properties;
  
publicclassMain {
    publicvoidTestJDBC(){
        //将配置文件变成输入流
        InputStream in = getClass().getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties =newProperties();
        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();
        }
    }
     
    publicstaticvoidmain(String[] args) {
        Main m =newMain();
        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定义的语法如下:

?
1
2
3
4
5
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceMyAnnotation1 {
}

说明:

上面的作用是定义一个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类

?
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
packageme.gacl.annotation;
  
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.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@interfaceWebServlet {
    //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 配置文件中  元素的子元素  里的值。通过这样的注解能简化在 XML 文件中配置 Servlet 信息,整个配置文件将会非常简洁干净,开发人员的工作也将大大减少。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
packageme.gacl.annotation;
  
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
  
/**
* @ClassName: WebInitParam
* @Description: 定义Servlet的初始化参数注解
* @author: 孤傲苍狼
* @date: 2014-10-1 下午3:25:53
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public@interfaceWebInitParam {
    //参数名
    String paramName()default"";
    //参数的值
    String paramValue()default"";
}

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

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

?
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
importjava.io.IOException;
importjava.lang.reflect.InvocationTargetException;
importjava.lang.reflect.Method;
importjava.lang.reflect.Modifier;
importjava.util.HashMap;
importjava.util.Map;
importjava.util.Set;
importjavax.servlet.Filter;
importjavax.servlet.FilterChain;
importjavax.servlet.FilterConfig;
importjavax.servlet.ServletContext;
importjavax.servlet.ServletException;
importjavax.servlet.ServletRequest;
importjavax.servlet.ServletResponse;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importme.gacl.annotation.WebInitParam;
importme.gacl.annotation.WebServlet;
importme.gacl.util.ScanClassUtil;
  
/**
* @ClassName: AnnotationHandleFilter
* @Description: 使用Filter作为注解的处理器
* @author: 孤傲苍狼
* @date: 2014-11-12 下午10:15:19
*
*/
publicclassAnnotationHandleFilterimplementsFilter {
  
    privateServletContext servletContext =null;
     
    /* 过滤器初始化时扫描指定的包下面使用了WebServlet注解的那些类
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    publicvoidinit(FilterConfig filterConfig)throwsServletException {
        System.out.println("---AnnotationHandleFilter过滤器初始化开始---");
        servletContext = filterConfig.getServletContext();
        Map<string,>> classMap =newHashMap<string,>>();
        //获取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
    */
    privatevoidaddServletClassToServletContext(String packageName,Map<string,>> 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);
            }
        }
    }
  
    publicvoiddoFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)throwsIOException, ServletException {
        System.out.println("---进入注解处理过滤器---");
        //将ServletRequest强制转换成HttpServletRequest
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        Map<string,>> classMap = (Map<string,>>) 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 =newHashMap<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(intj =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的初始化方法
                &
发表评论
评论通过审核后显示。