[转载]Java三种代理模式

代理介绍

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

可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。
在这里插入图片描述

简单理解代理

很简单举个例子:

  • 现在我是一个明星,拥有很多粉丝。粉丝希望我唱歌给他们听,但是如果都是我来接应他们,我岂不是很忙….于是乎,我就去找了个经纪人。这个经纪人就代表了我。当粉丝想要我唱歌的时候,应该是找经纪人,告诉经纪人想让我唱歌。
  • 现在我越来越红了,不是粉丝想要我唱歌,我就唱了。我要收费了。但是呢,作为一个公众人物,不可能是我自己说:我要收10000万,我才会去唱歌。于是这就让经纪人对粉丝说:只有10000万,我才会唱歌
  • 无论外界是想要我干什么,都要经过我的经纪人。我的经纪人也会在其中考虑收费、推脱它们的请求

经纪人就是代理,实际上台唱歌、表演的还是我

静态代理

直接使用例子来说明吧…现在我有一个IUserDao的接口,拥有save方法()

// 接口
public interface IUserDao {
    void save();
}

UserDao实现该接口,重写save()方法

public class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("-----已经保存数据!!!------");
    }
}

现在,我想要在save()方法保存数据前开启事务、保存数据之后关闭事务…(当然啦,直接再上面写不就行了吗…业务方法少的时候,确实没毛病…)

    public void save() {

        System.out.println("开启事务");
        System.out.println("-----已经保存数据!!!------");
        System.out.println("关闭事务");
    }

但是呢,现在如果我有好多好多个业务方法都需要开启事务、关闭事务呢?

public void save() {
        System.out.println("开启事务");
        System.out.println("-----已经保存数据!!!------");
        System.out.println("关闭事务");
    }
    public void delete() {
        System.out.println("开启事务");
        System.out.println("-----已经保存数据!!!------");
        System.out.println("关闭事务");
    }
    public void update() {
        System.out.println("开启事务");
        System.out.println("-----已经保存数据!!!------");
        System.out.println("关闭事务");
    }
    public void login() {
        System.out.println("开启事务");
        System.out.println("-----已经保存数据!!!------");
        System.out.println("关闭事务");
    }

我们发现就有了很多很多的重复代码了…我们要做的就是:当用户调用UserDao方法的时候,找的是代理对象、而代理帮我在解决这么繁琐的代码

于是呢,我们就请了一个代理了

  • 这个代理要和userDao有相同的方法…没有相同的方法的话,用户怎么调用啊??
  • 代理只是对userDao进行增强,真正做事的还是userDao

因此,我们的代理就要实现IUserDao接口,这样的话,代理就跟userDao有相同的方法了。

public class UserDaoProxy implements IUserDao{
    // 接收保存目标对象【真正做事的还是UserDao】,因此需要维护userDao的引用
    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    @Override
    public void save() {
        System.out.println("开始事务...");
        target.save();          // 执行目标对象的方法
        System.out.println("提交事务...");
    }
}

外界并不是直接去找UserDao,而是要通过代理才能找到userDao

    public static void main(String[] args) {
        // 目标对象
        IUserDao target = new UserDao();
        // 代理
        IUserDao proxy = new UserDaoProxy(target);
        proxy.save();  // 执行的是,代理的方法
    }

这样一来,我们在UserDao中就不用写那么傻逼的代码了…傻逼的事情都交给代理去干了…

动态代理

为什么要用动态代理?

我们首先来看一下静态代理的不足

  • 如果接口改了,代理的也要跟着改,很烦!
  • 因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,类太多。

动态代理比静态代理好的地方

  • 代理对象,不需要实现接口【就不会有太多的代理类了】
  • 代理对象的生成,是利用JDKAPI, 动态地在内存中构建代理对象(需要我们指定创建 代理对象/目标对象 实现的接口的类型;)

动态代理快速入门

Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,该方法需要三个参数:
在这里插入图片描述

  • 参数一:生成代理对象使用哪个类装载器【一般我们使用的是代理类的装载器】
  • 参数二:生成哪个对象的代理对象,通过接口指定【指定要代理类的接口】
  • 参数三:生成的代理对象的方法里干什么事【实现handler接口,我们想怎么实现就怎么实现】

在编写动态代理之前,要明确两个概念:

  • 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口】
  • 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。
  • 使用JDK动态代理必须要有接口【参数二需要接口】

对象
小明是一个明星,拥有唱歌和跳舞的方法。实现了人的接口

public class XiaoMing implements Person {
    @Override
    public void sing(String name) {
        System.out.println("小明唱" + name);
    }
    @Override
    public void dance(String name) {
        System.out.println("小明跳" + name);
    }
}

接口

public interface Person {
    void sing(String name);
    void dance(String name);
}

代理类

public class XiaoMingProxy {
    //代理只是一个中介,实际干活的还是小明,于是需要在代理类上维护小明这个变量
    XiaoMing xiaoMing = new XiaoMing();
    //返回代理对象
    public Person getProxy() {
        /**
         * 参数一:代理类的类加载器
         * 参数二:被代理对象的接口
         * 参数三:InvocationHandler实现类
         */
        return (Person)Proxy.newProxyInstance(XiaoMingProxy.class.getClassLoader(), xiaoMing.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * proxy : 把代理对象自己传递进来
             * method:把代理对象当前调用的方法传递进来
             * args:把方法参数传递进来
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //如果别人想要让小明唱歌
                if (method.getName().equals("sing")) {
                    System.out.println("给1000万来再唱");
                    //实际上唱歌的还是小明
                    method.invoke(xiaoMing, args);
                }
                return null;
            }
        });
    }
}

测试类

    public static void main(String[] args) {
        //外界通过代理才能让小明唱歌
        XiaoMingProxy xiaoMingProxy = new XiaoMingProxy();
        Person proxy = xiaoMingProxy.getProxy();
        proxy.sing("我爱你");
    }

动态代理应用

我们之前写中文过滤器的时候,需要使用包装设计模式来设计一个request类。如果不是Servlet提供了实现类给我们,我们使用包装设计模式会出现麻烦

现在我们学习了动态代理了,动态代理就是拦截直接访问对象,可以给对象进行增强的一项技能

中文过滤器
public void doFilter(final ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        response.setContentType("text/html;charset=UTF-8");
        request.setCharacterEncoding("UTF-8");
        
        //放出去的是代理对象
        chain.doFilter((ServletRequest) Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //判断是不是getParameter方法
                if (!method.getName().equals("getParameter")) {
                    //不是就使用request调用
                   return method.invoke(request, args);
                }
                //判断是否是get类型的
                if (!request.getMethod().equalsIgnoreCase("get")) {
                   return method.invoke(request, args);
                }
                //执行到这里,只能是get类型的getParameter方法了。
                String value = (String) method.invoke(request, args);
                if (value == null) {
                    return null;
                }
                return new String(value.getBytes("ISO8859-1"), "UTF-8");
            }
        }), response);
    }

cglib代理

由于静态代理需要实现目标对象的相同接口,那么可能会导致代理类会非常非常多….不好维护—->因此出现了动态代理

动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理……—–>因此出现了cglib代理

cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能

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

编写cglib代理

接下来我们就讲讲怎么写cglib代理:

  • 需要引入cglib – jar文件,但是spring的核心包中已经包括了cglib功能,所以直接引入spring-core-3.2.5.jar即可。
  • 引入功能包后,就可以在内存中动态构建子类
  • 代理的类不能为final,否则报错【在内存中构建子类来做扩展,当然不能为final,有final就不能继承了】
  • 目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

UserDao

public class UserDao{

    public void save() {
        System.out.println("保存");
    }
}

代理工厂

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyFactory implements MethodInterceptor{

    private Object target;//维护一个目标对象
    public ProxyFactory(Object target) {
        this.target = target;
    }    
    //为目标对象生成代理对象
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开启事务");
        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("关闭事务");
        return returnValue;
    }
}

测试

public class TestProxy {

    @Test
    public void testCglibProxy(){
        //目标对象
        UserDao userDao= new UserDao();
        System.out.println(userDao.getClass());
        //代理对象
        UserDao proxy = (UserDao)new ProxyFactory(userDao).getProxyInstance();
        //执行代理对象方法
        System.out.println(proxy.getClass());
        proxy.save();
    }
}

在这里插入图片描述
使用cglib就是为了弥补动态代理的不足【动态代理的目标对象一定要实现接口】

注意:

cglib就是为了弥补动态代理的不足【动态代理的目标对象一定要实现接口】,cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。

转载来源

代理模式【介绍、静态代理、动态代理、入门、应用】
Spring第五篇【cglib、手动实现AOP编程】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值