A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

关于Java中的动态代理,我相信,有很多从事Java学习和工作的朋友并不是十分清楚.所以,通过搜集资料和整理学习笔记,今天对动态代理做一个详细的总结.
什么是动态代理
动态代理技术就是用来产生一个对象的代理对象的。在开发中为什么需要为一个对象产生代理对象呢?
  举一个现实生活中的例子:歌星或者明星都有一个自己的经纪人,这个经纪人就是他们的代理人,当我们需要找明星表演时,不能直接找到该明星,只能是找明星的代理人。这个代理人会对客户的请求加以甄别控制,然后对明星的表演活动进行安排,但真正表演的还是明星.这个现实中的例子和我们在开发中是一样的,我们在开发中之所以要产生一个对象的代理对象,主要用于拦截对真实业务对象的访问,做出一些控制操作。那么代理对象应该具有什么方法呢?代理对象应该具有和目标对象相同的方法
所以在这里明确代理对象的两个概念:
    1、代理对象存在的价值主要用于拦截对真实业务对象的访问。
    2、代理对象应该具有和目标对象(真实业务对象)相同的方法。
动态代理技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会动态代理技术,那么在学习Spring这些框架时是学不明白的。
java中的代理
要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个"java.lang.reflect.Proxy"类,通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象,如下所示:

newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。所以我们只需要调用newProxyInstance方法就可以得到某一个对象的代理对象了。

举例
下面结合现实中的情景,创建一个明星类,再创建一个代理类,生成明星的代理对象.
1、定义对象的行为接口

1 package cn.gacl.proxy;
2
3 /**
4 * @ClassName: Person
5 * @Description: 定义对象的行为
6 * @author: 孤傲苍狼
7 * @date: 2014-9-14 下午9:44:22
8 *
9 */
10 public interface Person {
11
12     /**
13     * @Method: sing
14     * @Description: 唱歌
15     * @Anthor:孤傲苍狼
16     *
17     * @param name
18     * @return
19     */
20     String sing(String name);
21     /**
22     * @Method: sing
23     * @Description: 跳舞
24     * @Anthor:孤傲苍狼
25     *
26     * @param name
27     * @return
28     */
29     String dance(String name);
30 }

  2、定义目标业务对象类

1 package cn.gacl.proxy;
2
3 /**
4 * @ClassName: LiuDeHua
5 * @Description: 刘德华实现Person接口,那么刘德华会唱歌和跳舞了
6 * @author: 孤傲苍狼
7 * @date: 2014-9-14 下午9:22:24
8 *
9 */
10 public class LiuDeHua implements Person {
11
12     public String sing(String name){
13         System.out.println("刘德华唱"+name+"歌!!");
14         return "歌唱完了,谢谢大家!";
15     }
16     
17     public String dance(String name){
18         System.out.println("刘德华跳"+name+"舞!!");
19         return "舞跳完了,多谢各位观众!";
20     }
21 }

  3、创建生成代理对象的代理类

1 package cn.gacl.proxy;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Proxy;
6
7 /**
8 * @ClassName: LiuDeHuaProxy
9 * @Description: 这个代理类负责生成刘德华的代理人
10 * @author: 孤傲苍狼
11 * @date: 2014-9-14 下午9:50:02
12 *
13 */
14 public class LiuDeHuaProxy {
15
16     //设计一个类变量记住代理类要代理的目标对象
17     private Person ldh = new LiuDeHua();
18     
19     /**
20     * 设计一个方法生成代理对象
21     * @Method: getProxy
22     * @Description: 这个方法返回刘德华的代理对象:Person person = LiuDeHuaProxy.getProxy();//得到一个代理对象
23     * @Anthor:孤傲苍狼
24     *
25     * @return 某个对象的代理对象
26     */
27     public Person getProxy() {
28         //使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某个对象的代理对象
29         return (Person) Proxy.newProxyInstance(LiuDeHuaProxy.class
30                 .getClassLoader(), ldh.getClass().getInterfaces(),
31                 new InvocationHandler() {
32                     /**
33                      * InvocationHandler接口只定义了一个invoke方法,因此对于这样的接口,我们不用单独去定义一个类来实现该接口,
34                      * 而是直接使用一个匿名内部类来实现该接口,new InvocationHandler() {}就是针对InvocationHandler接口的匿名实现类
35                      */
36                     /**
37                      * 在invoke方法编码指定返回的代理对象干的工作
38                      * proxy : 把代理对象自己传递进来
39                      * method:把代理对象当前调用的方法传递进来
40                      * args:把方法参数传递进来
41                      *
42                      * 当调用代理对象的person.sing("冰雨");或者 person.dance("江南style");方法时,
43                      * 实际上执行的都是invoke方法里面的代码,
44                      * 因此我们可以在invoke方法中使用method.getName()就可以知道当前调用的是代理对象的哪个方法
45                      */
46                     @Override
47                     public Object invoke(Object proxy, Method method,
48                             Object[] args) throws Throwable {
49                         //如果调用的是代理对象的sing方法
50                         if (method.getName().equals("sing")) {
51                             System.out.println("我是他的经纪人,要找他唱歌得先给十万块钱!!");
52                             //已经给钱了,经纪人自己不会唱歌,就只能找刘德华去唱歌!
53                             return method.invoke(ldh, args); //代理对象调用真实目标对象的sing方法去处理用户请求
54                         }
55                         //如果调用的是代理对象的dance方法
56                         if (method.getName().equals("dance")) {
57                             System.out.println("我是他的经纪人,要找他跳舞得先给二十万块钱!!");
58                             //已经给钱了,经纪人自己不会唱歌,就只能找刘德华去跳舞!
59                             return method.invoke(ldh, args);//代理对象调用真实目标对象的dance方法去处理用户请求
60                         }
61
62                         return null;
63                     }
64                 });
65     }
66 }

  测试代码:

1 package cn.gacl.proxy;
2
3 public class ProxyTest {
4     
5     public static void main(String[] args) {
6         
7         LiuDeHuaProxy proxy = new LiuDeHuaProxy();
8         //获得代理对象
9         Person p = proxy.getProxy();
10         //调用代理对象的sing方法
11         String retValue = p.sing("冰雨");
12         System.out.println(retValue);
13         //调用代理对象的dance方法
14         String value = p.dance("江南style");
15         System.out.println(value);
16     }
17 }

  

  Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
  由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。


实际应用
在字符过滤器中使用动态代理解决中文乱码
  在平时的JavaWeb项目开发中,我们一般会写一个CharacterEncodingFilter(字符过滤器)来解决整个JavaWeb应用的中文乱码问题,如下所示:

1 package me.gacl.web.filter;
2
3 import java.io.IOException;
4
5 import javax.servlet.Filter;
6 import javax.servlet.FilterChain;
7 import javax.servlet.FilterConfig;
8 import javax.servlet.ServletException;
9 import javax.servlet.ServletRequest;
10 import javax.servlet.ServletResponse;
11
12 /**
13 * @ClassName: CharacterEncodingFilter
14 * @Description: 解决中文乱码的字符过滤器
15 * @author: 孤傲苍狼
16 * @date: 2014-9-14 下午10:38:12
17 *
18 */
19 public class CharacterEncodingFilter implements Filter {
20
21     @Override
22     public void init(FilterConfig filterConfig) throws ServletException {
23
24     }
25
26     @Override
27     public void doFilter(ServletRequest request, ServletResponse response,
28             FilterChain chain) throws IOException, ServletException {
29         //解决以Post方式提交的中文乱码问题
30         request.setCharacterEncoding("UTF-8");
31         response.setCharacterEncoding("UTF-8");
32         response.setContentType("text/html;charset=UTF-8");
33         chain.doFilter(request, response);
34     }
35
36     @Override
37     public void destroy() {
38
39     }
40 }

  但是这种写法是没有办法解决以get方式提交中文参数时的乱码问题的,我们可以用如下的代码来证明上述的解决中文乱码过滤器只对以post方式提交中文参数时有效,而对于以get方式提交中文参数时无效
  jsp测试页面如下:

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

  处理请求的ServletDemo1代码如下:

1 package me.gacl.web.controller;
2
3 import java.io.IOException;
4 import java.io.PrintWriter;
5
6 import javax.servlet.ServletException;
7 import javax.servlet.http.HttpServlet;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10
11 public class ServletDemo1 extends HttpServlet {
12
13     public void doGet(HttpServletRequest request, HttpServletResponse response)
14             throws ServletException, IOException {
15         // 接收参数
16         String username = request.getParameter("username");
17         // 获取请求方式
18         String method = request.getMethod();
19         // 获取输出流
20         PrintWriter out = response.getWriter();
21         out.write("请求的方式:" + method);
22         out.write("<br/>");
23         out.write("接收到的参数:" + username);
24     }
25
26     public void doPost(HttpServletRequest request, HttpServletResponse response)
27             throws ServletException, IOException {
28         doGet(request, response);
29     }
30 }

  在web.xml中注册上述的CharacterEncodingFilter和ServletDemo1

1  <filter>
2       <filter-name>CharacterEncodingFilter</filter-name>
3       <filter-class>me.gacl.web.filter.CharacterEncodingFilter</filter-class>
4   </filter>
5   
6   <filter-mapping>
7       <filter-name>CharacterEncodingFilter</filter-name>
8       <url-pattern>/*</url-pattern>
9   </filter-mapping>
10   
11   <servlet>
12     <servlet-name>ServletDemo1</servlet-name>
13     <servlet-class>me.gacl.web.controller.ServletDemo1</servlet-class>
14   </servlet>
15
16   <servlet-mapping>
17     <servlet-name>ServletDemo1</servlet-name>
18     <url-pattern>/servlet/ServletDemo1</url-pattern>
19   </servlet-mapping>   

 
  
  上述的过滤器不能解决以get方式提交中文参数的乱码问题,下面使用动态代理技术改造上述的过滤器,使之能够解决以get方式提交中文参数的乱码问题,改造后的过滤器代码如下:

1 package me.gacl.web.filter;
2
3 import java.io.IOException;
4 import java.lang.reflect.InvocationHandler;
5 import java.lang.reflect.Method;
6 import java.lang.reflect.Proxy;
7
8 import javax.servlet.Filter;
9 import javax.servlet.FilterChain;
10 import javax.servlet.FilterConfig;
11 import javax.servlet.ServletException;
12 import javax.servlet.ServletRequest;
13 import javax.servlet.ServletResponse;
14 import javax.servlet.http.HttpServletRequest;
15 import javax.servlet.http.HttpServletResponse;
16
17 /**
18 * @ClassName: CharacterEncodingFilter
19 * @Description: 解决中文乱码的字符过滤器
20 * @author: 孤傲苍狼
21 * @date: 2014-9-14 下午10:38:12
22 *
23 */
24 public class CharacterEncodingFilter implements Filter {
25
26     @Override
27     public void init(FilterConfig filterConfig) throws ServletException {
28
29     }
30
31     @Override
32     public void doFilter(ServletRequest req, ServletResponse resp,
33             FilterChain chain) throws IOException, ServletException {
34         
35         final HttpServletRequest request = (HttpServletRequest) req;
36         HttpServletResponse response = (HttpServletResponse) resp;
37         //解决以Post方式提交的中文乱码问题
38         request.setCharacterEncoding("UTF-8");
39         response.setCharacterEncoding("UTF-8");
40         response.setContentType("text/html;charset=UTF-8");
41         //获取获取HttpServletRequest对象的代理对象
42         ServletRequest requestProxy = getHttpServletRequestProxy(request);
43         /**
44          * 传入代理对象requestProxy给doFilter方法,
45          * 这样用户在使用request对象时实际上使用的是HttpServletRequest对象的代理对象requestProxy
46          */
47         chain.doFilter(requestProxy, response);
48     }
49
50     
51     /**
52     * @Method: getHttpServletRequestProxy
53     * @Description: 获取HttpServletRequest对象的代理对象
54     * @Anthor:孤傲苍狼
55     *
56     * @param request
57     * @return HttpServletRequest对象的代理对象
58     */
59     private ServletRequest getHttpServletRequestProxy(final HttpServletRequest request){
60         ServletRequest proxy  = (ServletRequest) Proxy.newProxyInstance(
61                 CharacterEncodingFilter.class.getClassLoader(),
62                 request.getClass().getInterfaces(),
63                 new InvocationHandler(){
64                     @Override
65                     public Object invoke(Object proxy, Method method, Object[] args)
66                             throws Throwable {
67                         //如果请求方式是get并且调用的是getParameter方法
68                         if (request.getMethod().equalsIgnoreCase("get") && method.getName().equals("getParameter")) {
69                             //调用getParameter方法获取参数的值
70                             String value = (String) method.invoke(request, args);
71                             if(value==null){
72                                 return null;
73                             }
74                             //解决以get方式提交的中文乱码问题
75                             return new String(value.getBytes("iso8859-1"),"UTF-8");
76                         }else {
77                             //直接调用相应的方法进行处理
78                             return method.invoke(request, args);
79                         }
80                     }
81                 });
82         //返回HttpServletRequest对象的代理对象
83         return proxy;
84     }
85     
86     @Override
87     public void destroy() {
88
89     }
90 }

  我们在过滤器中使用动态代理技术生成一个HttpServletRequest对象的代理对象requestProxy,然后把代理对象requestProxy进行chain.doFilter(requestProxy, response)传递给用户使用,这样用户实际上使用的就是HttpServletRequest对象的代理对象requestProxy。然而这一过程对于用户来说是透明的,用户是不知道自己使用的HttpServletRequest对象是一个代理对象requestProxy,由于代理对象requestProxy和目标对象HttpServletRequest具有相同的方法,当用户调用getParameter方法接收中文参数时,实际上调用的就是代理对象requestProxy的invoke方法,因此我们就可以在invoke方法中就判断当前的请求方式以及用户正在调用的方法,如果判断当前的请求方式是get方式并且用户正在调用的是getParameter方法,那么我们就可以手动处理get方式提交中文参数的中文乱码问题了。


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马