设计模式之代理模式

代理模式

概述

问题:

  • 代理模式是什么?
  • 使用代理模式有什么好处?
  • 如何使用代理模式?

代理模式:为一个对象(即是目标对象)提供一个替身(即是代理对象),来控制对该对象的访问。即通过代理对象来访问目标对象。

这样做的好处是在目标对象实现的基础上,增强额外的操作,即扩展了目标对象的功能。

被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

代理模式有不同的形式,主要有三种:静态代理、动态代理(JDK代理、接口代理)和cglib代理。

静态代理

静态代理的基本介绍

静态代理在使用时,需要定义接口或父类,被代理对象(目标对象)与代理对象一起实现相同的接口或者继承相同的父类。

应用实例

具体要求:

  • (1)定义一个TeacherDaoInterface接口,该类定义了方法名。
  • (2)目标对象TeacherDao类实现接口TeacherDaoInterface。
  • (3)使用静态代理方式,需要在代理对象TeacherDaoProxy中也实现TeacherDaoInterface中的方法,并可以对方法进行一些增强。(注:在该代理对象类中要调用目标对象的方法,所以可以通过构造器传参数来实现
  • (4)在客户端类或测试类中调用的时候,通过调用代理对象的方法来调用目标对象。(注:实例化代理对象类,来调用该对象的方法

注:代理对象与目标对象的方法要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。

接口类TeacherDaoInterface.java

/**
 * 接口,定义方法,让目标对象与代理对象实现该接口的方法
 */
public interface TeacherDaoInterface {
    void teach(); // 该接口有一个teach()方法,表示授课
}

实现接口的目标对象类TeacherDao.java

/**
 * 目标对象,实现TeacherDaoInterface接口的所有方法
 */
public class TeacherDao implements TeacherDaoInterface {
    @Override
    public void teach() {
        System.out.println(" 老师授课中	。。。。。");
    }
}

实现接口的代理对象类TeacherDaoProxy.java

/**
 * 代理对象,实现TeacherDaoInterface接口的所有方法,并对代理对象的方法进行一定程度上增强
 */
public class TeacherDaoProxy implements TeacherDaoInterface {
    private TeacherDaoInterface target; // 目标对象,通过接口来聚合

    //构造器
    public TeacherDaoProxy(TeacherDaoInterface target) {
        this.target = target;
    }

    // 增强方法的功能
    // 但需要调用目标对象TeacherDao.java中的teach()方法,该如何调用呢?就是通过TeacherDaoInterface.java来作中间媒介进行调用
    @Override
    public void teach() {
        System.out.println("开始代理,完成某些操作。。。。。 ");//也可以调用些方法
        target.teach();
        System.out.println("提交。。。。。");//也可以调用些方法
    }
}

测试类Test.java来测试功能的实现

public class Test {
    public static void main(String[] args) {
        //创建目标对象(被代理对象)
        TeacherDao teacherDao = new TeacherDao();

        //创建代理对象, 同时将被代理对象传递给代理对象
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);

        //通过代理对象,调用到被代理对象的方法
        //即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
        teacherDaoProxy.teach();

    }
}
/**
 * 打印结果:
 * 开始代理,完成某些操作。。。。。
 * 老师授课中	。。。。。
 * 提交。。。。。
 */

URL图

静态代理的使用总结:

  • 第一步:创建一个接口类,定义要实现的方法。
  • 第二步:创建目标对象类,实现接口中的方法。
  • 第三步:创建代理对象类,也实现接口中的方法,不过要增强目标对象的方法因此需要调用目标对象的方法,所以使用构造器传入参数。
  • 第四步:创建测试类,实例化代理对象来调用目标对象的方法。

静态代理的优缺点

优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。

缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。并且一旦接口增加方法,目标对象与代理对象类都要维护。

动态代理

动态代理的基本介绍

代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。

代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象。

动态代理也叫做:JDK 代理、接口代理。

JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:

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

应用实例

将上面的需求改为动态代理。

接口类TeacherInterface.java

/**
 * 接口,定义方法,让目标对象与代理对象实现该接口的方法
 */
public interface TeacherInterface {
    /**
     * 教师授课
     */
    void teach();

    /**
     * 教师让某一位学生回答问题
     *
     * @param name 学生名字
     * @return 学生回答的内容
     */
    String ask(String name);
}

实现接口的目标类对象TeacherTarget.java

/**
 * 目标对象,实现接口方法
 */
public class TeacherTarget implements TeacherInterface {
    @Override
    public void teach() {
        System.out.println("教师授课中。。。。。。");
    }

    @Override
    public String ask(String name) {
        System.out.println("教师请[" + name + "]起来回答问题。。。。。。");
        return "回答一";
    }
}

生成代理对象的类TeacherProxyFactory.java

/**
 * 工厂类,可以不断动态生成代理对象
 */
public class TeacherProxyFactory {
    // 维护一个目标对象Object
    private Object target;

    // 但要调用该对象的方法,所以需要通过构造器传入参数
    public TeacherProxyFactory(Object target) {
        this.target = target;
    }

    // 给目标对象,生成一个代理对象
    // 调用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成代理对象
    // 参数说明:
    //      ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
    //      Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
    //      InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
    // 这三个参数是固定写法,简述如下:
    //      1. 类加载器:目标对象.getClass().getClassLoader()
    //      2. 接口数组:目标对象.getClass().getInterfaces()
    //      3. 处理器:new InvocationHandler()
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            /*
                代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1. proxy:代理对象
                        2. method:代理对象调用的方法,被封装为的对象,这里即是TeacherInterface.java中的方法。
                        3. args:代理对象调用的方法时,传递的实际参数,这里即是ask(name)方法中的name参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // System.out.println(method.getName()); // method.getName() 可以被调用方法的方法名,例如:ask、teach等
                // System.out.println(args[0]);// 即被调用方法中的参数
                // 判断是否是ask方法被调用了
                if (method.getName().equals("ask")) {
                    // 1.可以增强方法参数
                    String name = (String) args[0];
                    String newName = name + ", 王五, 赵六";
                    // 2.反射机制调用目标对象的方法
                    System.out.println("JDK代理开始。。。");
                    String returnValue = (String) method.invoke(target, newName);// 调用方法后的返回结果,这里是调用ask()方法后返回的值
                    System.out.println("JDK代理提交。。。");
                    // 3.可以增强返回值
                    return returnValue + ";补充回答二。";
                }
                // 如果不是ask方法而是其他方法,则调用对应目标对象的方法
                Object returnValue = method.invoke(target, args);
                return returnValue;
            }
        });
    }
}

测试类Test.java

public class Test {
    public static void main(String[] args) {
        // 创建目标对象
        TeacherInterface target = new TeacherTarget();
        // 给目标对象创建代理对象
        TeacherInterface proxy = (TeacherInterface) new TeacherProxyFactory(target).getProxyInstance();
//        proxy.teach();
        // 通过代理对象,调用目标对象的方法
        String result = proxy.ask("李四");
        System.out.println(result);
    }
}
/**
 * 打印结果:
 * 教师请[李四, 王五, 赵六]起来回答问题。。。。。。
 * 回答一;补充回答二。
 */

UML图

动态代理总结:

  • 第一步:创建接口,写需要实现的方法名。
  • 第二步:创建目标类对象,实现接口中的方法。
  • 第三步:创建能够动态生成代理对象类,通过构造器能够传入目标对象,写一个getProxyInstance()方法,返回值为Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成代理对象。
  • 第四步:利用代理对象生成类来生成代理对象,然后调用目标对象的方法进行测试。

cglib代理

cglib代理的基本介绍

  • 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是 Cglib 代理
  • Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib 代理归属到动态代理。
  • Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截。
  • 在 AOP 编程中如何选择代理模式:
    • 目标对象需要实现接口,用 JDK 代理
    • 目标对象不需要实现接口,用 Cglib 代理
  • Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类。

cglib代理的使用注意事项

  • 需要引入cglib的jar包。

  • 注意代理的类不能为final修饰。
  • 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

应用实例

将上面的需求改为cglib代理。

目标对象类TeacherTarget.java

/**
 * 目标对象,实现接口方法
 */
public class TeacherTarget implements TeacherInterface {
    @Override
    public void teach() {
        System.out.println("教师授课中。。。。。。");
    }

    @Override
    public String ask(String name) {
        System.out.println("教师请[" + name + "]起来回答问题。。。。。。");
        return "回答一";
    }
}

生成代理对象类TeacherProxyFactory.java

public class TeacherProxyFactory implements MethodInterceptor {
    private Object target;// 该对象是目标类对象

    //构造器,传入一个被代理的对象
    public TeacherProxyFactory(Object target){
        this.target=target;
    }

    //返回一个代理对象: 是target对象的代理对象
    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }

    /**
     * 重写intercept方法,会调用目标对象的方法
     * @param obj 指被代理的对象
     * @param method 指被调用的方法
     * @param args 该方法调用时所需要的参数
     * @param methodProxy JDK的java.lang.reflect.Method类的代理类,可以实现对源对象方法的调用
     * @return 返回该方法被调用的返回值
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws InvocationTargetException, IllegalAccessException {
        if(method.getName().equals("ask")){
            // 1.参数增强
            String name= (String) args[0];
            String newName=name+",李四,王五";
            // 2.调用目标对象的方法,通过反射机制,返回的是该方法的返回值
            System.out.println("cglib代理开始。。。");
            Object returnValue = method.invoke(target, newName);
            System.out.println("cglib代理提交。。。");
            // 3.返回值增强
            returnValue+="补充答案二。";
            return returnValue;
        }
        return method.invoke(target,args);
    }
}

测试类Test.java

public class Test {
    public static void main(String[] args) {
        // 创建目标对象
        TeacherTarget target = new TeacherTarget();
        // 给目标对象创建代理对象
        TeacherTarget proxy = (TeacherTarget) new TeacherProxyFactory(target).getProxyInstance();
//        proxy.teach();
        // 通过代理对象,调用目标对象的方法
        String result = proxy.ask("李四");// 调用方法
        System.out.println(result);// 打印返回值
    }
}
/**
 * 打印结果:
 * cglib代理开始。。。
 * 教师请[李四,李四,王五]起来回答问题。。。。。。
 * cglib代理提交。。。
 * 回答一补充答案二。
 */

UML图

cglib代理总结:

  • 第一步:创建目标对象类,是要被代理的对象。
  • 第二步:创建生成代理对象类,该类用来生成代理对象,并且重写intercept()方法,是对被代理方法的增强。
  • 第三步:测试使用代理对象,通过代理对象调用目标对象的方法。

实例

需求:过滤敏感词。

动态代理实现

DemoServlet.java

@WebServlet("/demo")
public class DemoServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String msg = req.getParameter("msg");
        System.out.println(msg);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
}

过滤器类SensitiveWordsFilter.java

/**
 * 敏感词汇过滤器
 */
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
    private List<String> list = new ArrayList<String>();//敏感词汇集合

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        list.addAll(Arrays.asList("坏蛋", "混蛋", "傻瓜", "蠢货"));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest req=(ServletRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 增强getParameter方法
                // 判断是否是getParameter方法
                if(method.getName().equals("getParameter")){
                    //增强返回值
                    //获取返回值
                    String returnVal = (String)method.invoke(request, args);
                    if(returnVal!=null){
                        for (String s : list) {
                            if(returnVal.contains(s)){
                                returnVal=returnVal.replaceAll(s,"***");
                            }
                        }
                    }
                    return returnVal;
                }
                return method.invoke(request,args);
            }
        });
    }

    @Override
    public void destroy() {

    }
}

浏览器输入地址:http://localhost:8080/demo?msg=你是个大傻瓜

控制台输出

静态代理实现

还可以使用静态代理实现,即创建一个代理对象类实现HttpServletRequest接口中的方法,需要实现该接口的所有方法,所以代码较多,不推荐。

public class ServletRequestProxy implements HttpServletRequest {
    private HttpServletRequest target;

    public ServletRequestProxy(HttpServletRequest target) {
        this.target = target;
    }


    @Override
    public String getAuthType() {
        return target.getAuthType();
    }

    @Override
    public Cookie[] getCookies() {
        return target.getCookies();
    }

    @Override
    public long getDateHeader(String s) {
        return target.getDateHeader(s);
    }

    @Override
    public String getHeader(String s) {
        return target.getHeader(s);
    }

    @Override
    public Enumeration<String> getHeaders(String s) {
        return target.getHeaders(s);
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return target.getHeaderNames();
    }

    @Override
    public int getIntHeader(String s) {
        return target.getIntHeader(s);
    }

    @Override
    public String getMethod() {
        return target.getMethod();
    }

    @Override
    public String getPathInfo() {
        return target.getPathInfo();
    }

    @Override
    public String getPathTranslated() {
        return target.getPathTranslated();
    }

    @Override
    public String getContextPath() {
        return target.getContextPath();
    }

    @Override
    public String getQueryString() {
        return target.getQueryString();
    }

    @Override
    public String getRemoteUser() {
        return target.getRemoteUser();
    }

    @Override
    public boolean isUserInRole(String s) {
        return target.isUserInRole(s);
    }

    @Override
    public Principal getUserPrincipal() {
        return target.getUserPrincipal();
    }

    @Override
    public String getRequestedSessionId() {
        return target.getRequestedSessionId();
    }

    @Override
    public String getRequestURI() {
        return target.getRequestURI();
    }

    @Override
    public StringBuffer getRequestURL() {
        return target.getRequestURL();
    }

    @Override
    public String getServletPath() {
        return target.getServletPath();
    }

    @Override
    public HttpSession getSession(boolean b) {
        return target.getSession(b);
    }

    @Override
    public HttpSession getSession() {
        return target.getSession();
    }

    @Override
    public String changeSessionId() {
        return target.changeSessionId();
    }

    @Override
    public boolean isRequestedSessionIdValid() {
        return target.isRequestedSessionIdValid();
    }

    @Override
    public boolean isRequestedSessionIdFromCookie() {
        return target.isRequestedSessionIdFromCookie();
    }

    @Override
    public boolean isRequestedSessionIdFromURL() {
        return target.isRequestedSessionIdFromURL();
    }

    @Override
    public boolean isRequestedSessionIdFromUrl() {
        return target.isRequestedSessionIdFromUrl();
    }

    @Override
    public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
        return target.authenticate(httpServletResponse);
    }

    @Override
    public void login(String s, String s1) throws ServletException {
        target.login(s, s1);
    }

    @Override
    public void logout() throws ServletException {
        target.logout();
    }

    @Override
    public Collection<Part> getParts() throws IOException, ServletException {
        return target.getParts();
    }

    @Override
    public Part getPart(String s) throws IOException, ServletException {
        return target.getPart(s);
    }

    @Override
    public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {
        return target.upgrade(aClass);
    }

    @Override
    public Object getAttribute(String s) {
        return target.getAttribute(s);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return target.getAttributeNames();
    }

    @Override
    public String getCharacterEncoding() {
        return target.getCharacterEncoding();
    }

    @Override
    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
        target.setCharacterEncoding(s);
    }

    @Override
    public int getContentLength() {
        return target.getContentLength();
    }

    @Override
    public long getContentLengthLong() {
        return target.getContentLengthLong();
    }

    @Override
    public String getContentType() {
        return target.getContentType();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return target.getInputStream();
    }

    @Override
    public String getParameter(String s) {
        List<String> list = new ArrayList<String>();//敏感词汇集合
        list.addAll(Arrays.asList("坏蛋", "混蛋", "傻瓜", "蠢货"));
        String returnVal = target.getParameter(s);
        if (returnVal != null) {
            for (String str : list) {
                if (returnVal.contains(str)) {
                    returnVal = returnVal.replaceAll(str, "***");
                }
            }
        }
        return returnVal;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return target.getParameterNames();
    }

    @Override
    public String[] getParameterValues(String s) {
        return target.getParameterValues(s);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return target.getParameterMap();
    }

    @Override
    public String getProtocol() {
        return target.getProtocol();
    }

    @Override
    public String getScheme() {
        return target.getScheme();
    }

    @Override
    public String getServerName() {
        return target.getServerName();
    }

    @Override
    public int getServerPort() {
        return target.getServerPort();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return target.getReader();
    }

    @Override
    public String getRemoteAddr() {
        return target.getRemoteAddr();
    }

    @Override
    public String getRemoteHost() {
        return target.getRemoteHost();
    }

    @Override
    public void setAttribute(String s, Object o) {
        target.setAttribute(s, o);
    }

    @Override
    public void removeAttribute(String s) {
        target.removeAttribute(s);
    }

    @Override
    public Locale getLocale() {
        return target.getLocale();
    }

    @Override
    public Enumeration<Locale> getLocales() {
        return target.getLocales();
    }

    @Override
    public boolean isSecure() {
        return target.isSecure();
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String s) {
        return target.getRequestDispatcher(s);
    }

    @Override
    public String getRealPath(String s) {
        return target.getRealPath(s);
    }

    @Override
    public int getRemotePort() {
        return target.getRemotePort();
    }

    @Override
    public String getLocalName() {
        return target.getLocalName();
    }

    @Override
    public String getLocalAddr() {
        return target.getLocalAddr();
    }

    @Override
    public int getLocalPort() {
        return target.getLocalPort();
    }

    @Override
    public ServletContext getServletContext() {
        return target.getServletContext();
    }

    @Override
    public AsyncContext startAsync() throws IllegalStateException {
        return target.startAsync();
    }

    @Override
    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
        return target.startAsync(servletRequest, servletResponse);
    }

    @Override
    public boolean isAsyncStarted() {
        return target.isAsyncStarted();
    }

    @Override
    public boolean isAsyncSupported() {
        return target.isAsyncSupported();
    }

    @Override
    public AsyncContext getAsyncContext() {
        return target.getAsyncContext();
    }

    @Override
    public DispatcherType getDispatcherType() {
        return target.getDispatcherType();
    }
}

而SensitiveWordsFilter.java为如下:

/**
 * 敏感词汇过滤器
 */
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
    private List<String> list = new ArrayList<String>();//敏感词汇集合

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        list.addAll(Arrays.asList("坏蛋", "混蛋", "傻瓜", "蠢货"));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        // 静态代理
        ServletRequestProxy requestProxy = new ServletRequestProxy((HttpServletRequest) request);
        filterChain.doFilter(requestProxy, response);
    }

    @Override
    public void destroy() {

    }
}

效果同样。

至于cglib代理,暂时无法实现。

该实例源码地址:GitHub的Demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值