java基础-代理(静态代理、动态代理、cglib代理)

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法.

1.  静态代理

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

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

接口:

package com.h3c.demo.proxy;

public interface Person {

    String sing(String name);

    String dance(String name);
}

目标对象:

package com.h3c.demo.proxy;

public class WangHong implements  Person {
    @Override
    public String sing(String name) {
        System.out.println("网红唱"+name+"歌!!");
        return "唱歌之后!";
    }

    @Override
    public String dance(String name) {
        System.out.println("网红跳"+name+"舞!!");
        return "跳舞之后!";
    }
}

代理对象:

package com.h3c.demo.proxy;

public class WanHongStaticProxy implements  Person{

    private WangHong wangHong;

    public WanHongStaticProxy(WangHong wangHong) {
        this.wangHong = wangHong;
    }

    @Override
    public String sing(String name) {
        System.out.println("唱歌之前");
        return wangHong.sing(name);
    }

    @Override
    public String dance(String name) {
        System.out.println("跳舞之前");
        return wangHong.dance(name);
    }
}

测试类:

        //测试静态代理
        WangHong wangHong = new WangHong();
        WanHongStaticProxy wanHongStaticProxy = new WanHongStaticProxy(wangHong);
        wanHongStaticProxy.sing("盗将行");
        wanHongStaticProxy.dance("爵士");

控制台结果:

唱歌之前
网红唱盗将行歌!!
跳舞之前
网红跳爵士舞!!

静态代理总结:

1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.

2.缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

2.  动态代理

动态代理有以下特点:

1.代理对象,不需要实现接口

2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

3.动态代理也叫做:JDK代理,接口代理

JDK中生成代理对象的API

代理类所在包:java.lang.reflect.Proxy

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

static Object newProxyInstance(ClassLoader loader,

Class[] interfaces,InvocationHandler h )

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的

Class[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型

InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

代理对象:

package com.h3c.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class WangHongProxy {

    private Person wangHong = new WangHong();

    public Person getProxy() {
        //使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某个对象的代理对象
        return (Person) Proxy.newProxyInstance(WangHongProxy.class.getClassLoader(), wangHong.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //如果调用的是代理对象的sing方法
                        if (method.getName().equals("sing")) {
                            System.out.println("唱歌之前请求");
                            //代理对象调用真实目标对象的sing方法去处理用户请求
                            return method.invoke(wangHong, args);
                            }
                        //如果调用的是代理对象的dance方法
                        if (method.getName().equals("dance")) {
                            System.out.println("跳舞之前请求");
                            //代理对象调用真实目标对象的dance方法去处理用户请求
                            return method.invoke(wangHong, args);
                            }
                        return null;
                    }
                });
    }
}

测试类:

        WangHongProxy proxy = new WangHongProxy();
        //获得代理对象
        Person p = proxy.getProxy();
        //调用代理对象的sing方法
        String retValue = p.sing("不舍");
        System.out.println(retValue);
        //调用代理对象的dance方法
        String value = p.dance("孔雀舞");
        System.out.println(value);

控制台结果:

网红唱不舍歌!
唱歌之后
跳舞之前请求
网红跳孔雀舞舞!
跳舞之后

总结:

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

3.  cglib代理

上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理.

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.

Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)

Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法:

1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.

2.引入功能包后,就可以在内存中动态构建子类

3.代理的类不能为final,否则报错

4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

目标对象类:

package com.h3c.demo.proxy;

public class WangHongCglib {

    public String sing(String name) {
        System.out.println("网红唱"+name+"歌!");
        return "唱歌之后";
    }

    public String dance(String name) {
        System.out.println("网红跳"+name+"舞!");
        return "跳舞之后";
    }
}

cglib工厂类:

package com.h3c.demo.proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactoryCglib implements MethodInterceptor {

    //维护目标对象
    private Object target;

    public ProxyFactoryCglib(Object target) {
        this.target = target;
    }

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();

    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始处理");

        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);

        System.out.println("处理结束");

        return returnValue;
    }
}

测试类:

//目标对象
WangHongCglib target = new WangHongCglib();
//代理对象
WangHongCglib proxyInstance = (WangHongCglib) new ProxyFactoryCglib(target).getProxyInstance();
//执行代理对象的方法
proxyInstance.sing("借我");
proxyInstance.dance("街舞");

控制台结果:

开始处理
网红唱借我歌!
处理结束
开始处理
网红跳街舞舞!
处理结束

在Spring的AOP编程中:

如果加入容器的目标对象有实现接口,用JDK代理.

如果目标对象没有实现接口,用Cglib代理.

4.  Java动态代理用例之解决get请求中文乱码

package com.h3c.demo.proxy;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 解决中文乱码的字符过滤器
 */
public class TestProxyEncoding implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //解决以Post方式提交的中文乱码问题
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("text/html;charset=UTF-8");
        //获取获取HttpServletRequest对象的代理对象
        ServletRequest requestProxy = getHttpServletRequestProxy(request);
        /**
         * 传入代理对象requestProxy给doFilter方法,
         * 这样用户在使用request对象时实际上使用的是HttpServletRequest对象的代理对象requestProxy
        */
        filterChain.doFilter(requestProxy, response);
    }

    @Override
    public void destroy() {

    }

    private ServletRequest getHttpServletRequestProxy(final HttpServletRequest request) {
        ServletRequest proxy  = (ServletRequest) Proxy.newProxyInstance(TestProxyEncoding.class.getClassLoader(), request.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //如果请求方式是get并且调用的是getParameter方法
                        if (request.getMethod().equalsIgnoreCase("get") && method.getName().equals("getParameter")) {
                            //调用getParameter方法获取参数的值
                            String invoke = (String) method.invoke(request, args);
                            if (invoke == null) {
                                return  null;
                            }
                            //解决以get方式提交的中文乱码问题
                            return new String(invoke.getBytes("iso8859-1"),"UTF-8");
                        } else {
                            //直接调用相应的方法进行处理
                            return method.invoke(request, args);
                        }

                    }
                });
        return proxy;
    }


}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值