关于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方式提交中文参数的中文乱码问题了。
|
|