java中Proxy(代理与动态代理)

1、代理的概念

  动态代理技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会动态代理技术,那么在学习Spring这些框架时是学不明白的。css

  动态代理技术就是用来产生一个对象的代理对象的。在开发中为何须要为一个对象产生代理对象呢?
  举一个现实生活中的例子:歌星或者明星都有一个本身的经纪人,这个经纪人就是他们的代理人,当咱们须要找明星表演时,不能直接找到该明星,只能是找明星的代理人。好比刘德华在现实生活中很是有名,会唱歌,会跳舞,会拍戏,刘德华在没有出名以前,咱们能够直接找他唱歌,跳舞,拍戏,刘德华出名以后,他干的第一件事就是找一个经纪人,这个经纪人就是刘德华的代理人(代理),当咱们须要找刘德华表演时,不能直接找到刘德华了(刘德华说,你找我代理人商谈具体事宜吧!),只能是找刘德华的代理人,所以刘德华这个代理人存在的价值就是拦截咱们对刘德华的直接访问!
  这个现实中的例子和咱们在开发中是同样的,咱们在开发中之因此要产生一个对象的代理对象,主要用于拦截对真实业务对象的访问。那么代理对象应该具备什么方法呢?代理对象应该具备和目标对象相同的方法html

  因此在这里明确代理对象的两个概念:
    一、代理对象存在的价值主要用于拦截对真实业务对象的访问
    二、代理对象应该具备和目标对象(真实业务对象)相同的方法。刘德华(真实业务对象)会唱歌,会跳舞,会拍戏,咱们如今不能直接找他唱歌,跳舞,拍戏了,只能找他的代理人(代理对象)唱歌,跳舞,拍戏,一我的要想成为刘德华的代理人,那么他必须具备和刘德华同样的行为(会唱歌,会跳舞,会拍戏),刘德华有什么方法,他(代理人)就要有什么方法,咱们找刘德华的代理人唱歌,跳舞,拍戏,可是代理人不是真的懂得唱歌,跳舞,拍戏的,真正懂得唱歌,跳舞,拍戏的是刘德华,在现实中的例子就是咱们要找刘德华唱歌,跳舞,拍戏,那么只能先找他的经纪人,交钱给他的经纪人,而后经纪人再让刘德华去唱歌,跳舞,拍戏。java

2、java中的代理

2.一、"java.lang.reflect.Proxy"类介绍

  如今要生成某一个对象的代理对象,这个代理对象一般也要编写一个类来生成,因此首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5以后提供了一个"java.lang.reflect.Proxy"类,经过"Proxy"类提供的一个newProxyInstance方法用来建立一个对象的代理对象,以下所示:web

1 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

  newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪一个类装载器,Class<?>[] interfaces用来指明生成哪一个对象的代理对象,经过接口指定,InvocationHandler h用来指明产生的这个代理对象要作什么事情。因此咱们只须要调用newProxyInstance方法就能够获得某一个对象的代理对象了。浏览器

2.二、编写生成代理对象的类

  在java中规定,要想产生一个对象的代理对象,那么这个对象必需要有一个接口,因此咱们第一步就是设计这个对象的接口,在接口中定义这个对象所具备的行为(方法)服务器

  一、定义对象的行为接口app

复制代码
 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 }
复制代码

  二、定义目标业务对象类框架

复制代码
 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 }
复制代码

  三、建立生成代理对象的代理类jsp

复制代码
 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 }
复制代码

  测试代码:ide

复制代码
 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方法的方法)、方法的参数传递进来。

3、动态代理应用

  在动态代理技术里,因为无论用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这至关于invoke方法拦截到了代理对象的方法调用)。而且,开发人员经过invoke方法的参数,还能够在拦截的同时,知道用户调用的是什么方法,所以利用这两个特性,就能够实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

3.一、在字符过滤器中使用动态代理解决中文乱码

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

  测试结果以下所示:

  

 3.二、在字符过滤器中使用动态代理压缩服务器响应的内容后再输出到客户端

  压缩过滤器的代码以下:

复制代码
  1 package me.gacl.web.filter;
  2 
  3 import java.io.ByteArrayOutputStream;
  4 import java.io.IOException;
  5 import java.io.OutputStreamWriter;
  6 import java.io.PrintWriter;
  7 import java.lang.reflect.InvocationHandler;
  8 import java.lang.reflect.Method;
  9 import java.lang.reflect.Proxy;
 10 import java.util.zip.GZIPOutputStream;
 11 
 12 import javax.servlet.Filter;
 13 import javax.servlet.FilterChain;
 14 import javax.servlet.FilterConfig;
 15 import javax.servlet.ServletException;
 16 import javax.servlet.ServletOutputStream;
 17 import javax.servlet.ServletRequest;
 18 import javax.servlet.ServletResponse;
 19 import javax.servlet.http.HttpServletRequest;
 20 import javax.servlet.http.HttpServletResponse;
 21 
 22 /**
 23 * @ClassName: GzipFilter
 24 * @Description: 压缩过滤器,将web应用中的文本都通过压缩后再输出到浏览器
 25 * @author: 孤傲苍狼
 26 * @date: 2014-9-15 下午9:35:36
 27 *
 28 */ 
 29 public class GzipFilter implements Filter {
 30 
 31     @Override
 32     public void init(FilterConfig filterConfig) throws ServletException {
 33 
 34     }
 35 
 36     @Override
 37     public void doFilter(ServletRequest req, ServletResponse resp,
 38             FilterChain chain) throws IOException, ServletException {
 39         
 40         final HttpServletRequest request = (HttpServletRequest) req;
 41         final HttpServletResponse response = (HttpServletResponse) resp;
 42         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
 43         final PrintWriter pw = new PrintWriter(new OutputStreamWriter(bout,"UTF-8"));
 44         
 45         chain.doFilter(request, getHttpServletResponseProxy(response, bout, pw));
 46         pw.close();
 47         //拿到目标资源的输出
 48         byte result[] = bout.toByteArray();
 49         System.out.println("原始大小:" + result.length);
 50         
 51         ByteArrayOutputStream bout2 = new ByteArrayOutputStream();
 52         GZIPOutputStream gout = new GZIPOutputStream(bout2);
 53         gout.write(result);
 54         gout.close();
 55         
 56         //拿到目标资源输出的压缩数据
 57         byte gzip[] = bout2.toByteArray();
 58         System.out.println("压缩大小:" + gzip.length);
 59         
 60         response.setHeader("content-encoding", "gzip");
 61         response.setContentLength(gzip.length);
 62         response.getOutputStream().write(gzip);
 63     }
 64 
 65     /**
 66     * @Method: getHttpServletResponseProxy
 67     * @Description: 获取HttpServletResponse对象的代理对象
 68     * @Anthor:孤傲苍狼
 69     *
 70     * @param response
 71     * @param bout
 72     * @param pw
 73     * @return HttpServletResponse对象的代理对象
 74     */ 
 75     private ServletResponse getHttpServletResponseProxy(
 76             final HttpServletResponse response,
 77             final ByteArrayOutputStream bout, 
 78             final PrintWriter pw) {
 79         
 80         return (ServletResponse) Proxy.newProxyInstance(GzipFilter.class.getClassLoader(), 
 81                 response.getClass().getInterfaces(), 
 82                 new InvocationHandler(){
 83                     @Override
 84                     public Object invoke(Object proxy, Method method, Object[] args)
 85                             throws Throwable {
 86                         if(method.getName().equals("getWriter")){
 87                             return pw; 
 88                         }else if(method.getName().equals("getOutputStream")){
 89                             return new MyServletOutputStream(bout);
 90                         }else{
 91                             return method.invoke(response, args);
 92                         }
 93                     }
 94                 });
 95     }
 96 
 97     @Override
 98     public void destroy() {
 99 
100     }
101 
102     class MyServletOutputStream extends ServletOutputStream{
103         
104         private ByteArrayOutputStream  bout = null;
105         public MyServletOutputStream(ByteArrayOutputStream  bout){
106             this.bout = bout;
107         }
108         @Override
109         public void write(int b) throws IOException {
110             bout.write(b);
111         }
112         
113     }
114 }
复制代码

  在web.xml中注册上述的GzipFilter

复制代码
 1  <filter>
 2       <description>配置压缩过滤器</description>
 3       <filter-name>GzipFilter</filter-name>
 4       <filter-class>me.gacl.web.filter.GzipFilter</filter-class>
 5   </filter>
 6   
 7   <!--jsp文件的输出的内容都通过压缩过滤器压缩后才输出 -->
 8   <filter-mapping>
 9       <filter-name>GzipFilter</filter-name>
10       <url-pattern>*.jsp</url-pattern>
11       <!-- 配置过滤器的拦截方式-->
12       <!-- 对于在Servlet中经过
13           request.getRequestDispatcher("jsp页面路径").forward(request, response) 
14       方式访问的Jsp页面的要进行拦截 -->
15       <dispatcher>FORWARD</dispatcher>
16       <!--对于直接以URL方式访问的jsp页面进行拦截,过滤器的拦截方式默认就是REQUEST-->
17       <dispatcher>REQUEST</dispatcher>
18   </filter-mapping>
19   <!--js文件的输出的内容都通过压缩过滤器压缩后才输出 -->
20   <filter-mapping>
21       <filter-name>GzipFilter</filter-name>
22       <url-pattern>*.js</url-pattern>
23   </filter-mapping>
24   <!--css文件的输出的内容都通过压缩过滤器压缩后才输出 -->
25   <filter-mapping>
26       <filter-name>GzipFilter</filter-name>
27       <url-pattern>*.css</url-pattern>
28   </filter-mapping>
29   <!--html文件的输出的内容都通过压缩过滤器压缩后才输出 -->
30   <filter-mapping>
31       <filter-name>GzipFilter</filter-name>
32       <url-pattern>*.html</url-pattern>
33   </filter-mapping>
复制代码