设计模式学习笔记——代理模式

代理模式

1. 代理模式定义

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

换句话说:当我们要扩展一个类,使其功能更加丰富时,我们可以不直接修改这个类,而是创建代理对象,使用代理对象中调用这个类并添加而外的功能,这样就可以不修改这个类而增加功能,常用于权限控制,日志记录等
举个例子:
我们在项目中经常在操作数据库前后用到日志来记录操作信息,而反复的日志代码不仅破坏了代码的美观程度,还会因为重复代码损失相当多时间,如下代码所示:

package com.wojiushiwo;
import java.util.Date;
/**
 * @author 我就是我500
 * @date 2020-02-20 15:01
 * @describe
 **/
public class ProxyTest {
    
    
    public static void main(String[] args) {
    
    
        UserDAO userDao =new UserDaoImpl();
        //删除用户1并做好日志处理
        System.out.println("开始请求删除用户1,时间:"+new Date());
        if(userDao.deleteUser(1))
        {
    
    
            System.out.println("用户1删除成功,请求时间:"+new Date());
        }
        else
        {
    
    
            System.out.println("用户1删除失败,请求时间:"+new Date());
        }
        //删除用户2并做好日志处理
        System.out.println("开始请求删除用户2,时间:"+new Date());
        if(userDao.deleteUser(2))
        {
    
    
            System.out.println("用户2删除成功,请求时间:"+new Date());
        }
        else
        {
    
    
            System.out.println("用户2删除失败,请求时间:"+new Date());
        }
        //删除用户3并做好日志处理
        System.out.println("开始请求删除用户3,时间:"+new Date());
        if(userDao.deleteUser(3))
        {
    
    
            System.out.println("用户2删除成功,请求时间:"+new Date());
        }
        else
        {
    
    
            System.out.println("用户2删除失败,请求时间:"+new Date());
        }
    }
}
//DAO接口
interface UserDAO
{
    
    
    boolean deleteUser(int id);
}

//DAO实现类
class UserDaoImpl implements UserDAO
{
    
    
    //模拟删除一个用户
    @Override
    public boolean deleteUser(int id) {
    
    
        System.out.println("数据库操作......");
        return true;
    }
}

执行结果
这时虽然可以执行,但是重复代码相当对,这个时候我们就使用到了代理模式,可以将以上重复代码写入代理类中,使用代理类对Dao类进行自定的日志输出处理!

2. 静态代理模式

静态代理就是手动创建代理对象,好处是简单,坏处就是每一个要扩展的类都需要一个代理对象,增加工作量。
静态代理要求:
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一
实现相同的接口或者是继承相同父类

示例:将上方代码改为静态代理模式

package com.wojiushiwo;
import java.util.Date;
/**
 * @author 我就是我500
 * @date 2020-02-20 15:01
 * @describe
 **/
public class ProxyTest {
    
    
    public static void main(String[] args) {
    
    
        //创建目标对象
        UserDAO userDao =new UserDaoImpl();
        //将目标对象传给代理对象
        UserDAO proxy =new UserDaoProxy(userDao);
        proxy.deleteUser(1);
        proxy.deleteUser(2);
        proxy.deleteUser(3);
         //这时,我们只是调用了基础的方法,就可以连同输出语句一起调用
    }
}
//DAO接口
interface UserDAO
{
    
    
    boolean deleteUser(int id);
}
//DAO实现类
class UserDaoImpl implements UserDAO
{
    
    
    //模拟删除一个用户
    @Override
    public boolean deleteUser(int id) {
    
    
        System.out.println("数据库操作......");
        return true;
    }
}
//代理类
class UserDaoProxy implements UserDAO
{
    
    
    UserDAO userDAO;
    @Override
    public boolean deleteUser(int id) {
    
    
        System.out.println("开始请求删除用户"+id+",时间:"+new Date());
        boolean result = userDAO.deleteUser(id);
        if(result)
        {
    
    
            System.out.println("用户"+id+"删除成功,请求时间:"+new Date());
        }
        else
        {
    
    
            System.out.println("用户"+id+"删除失败,请求时间:"+new Date());
        }
        return result;
    }

    public UserDaoProxy(UserDAO userDAO)
    {
    
    
        this.userDAO=userDAO;
    }
}

执行结果:
执行结果
这是我们可以看到,我们只需定义代理对象,然后将代理对象中进行原有功能的增强(增加日志信息),即可在调用时自动执行!
在此案例中,我们的目标对象(UserDaoImpl)对数据库进行操作,定义UserDaoProxy代理类对目标对象进行代理,并实现UserDAO接口,这样我们在新建目标对象只需要使用代理对象对目标对象进行处理,因为实现统一接口,我们拿到的代理对象也是UserDao类型,这是我们不再使用原对象,而是使用代理对象,使用方法与之前完全相同,又因为多态性,使用时可以自动调用代理对象中已经加入了日志处理的代码,这样就可以实现更加简洁的代码!

Java经典静态代理模式应用:
Java线程操作时,需要实现Runnable接口,并使用Thread类进行操作,实际上,这就是代理模式的一个典型应用!
Thread类中包含了线程启动的一些底层方法,而这些方法不需要手动调用,只需要使用Thread方法的Start方法即可自动生成一个新的线程并执行Run方法!

package com.wojiushiwo;
import com.sun.org.apache.bcel.internal.generic.RET;
import java.util.Date;
/**
 * @author 我就是我500
 * @date 2020-02-20 15:01
 * @describe
 **/
public class ProxyTest {
    
    
    public static void main(String[] args) {
    
    
        MyRunnable runnable =new MyRunnable();
        Thread thread =new Thread(runnable);
        thread.start();
    }
}
class MyRunnable implements Runnable
{
    
    
    @Override
    public void run() {
    
    
        System.out.println("线程开始了!");
    }
}

3. JDK动态代理
相比较于静态代理,静态代理每一个代理类都需要手写,而动态代理则可以对任何对象进行代理,只要增强代码的逻辑相同,就可以直接使用,动态代理的对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理,这是动态代理的弊端之一,代理对象的生成,是利用JDK的API动态的在内存中构建代理对象,动态代理也叫做: JDK代理、 接口代理。

方法简介:
动态代理是使用JDK内提供的方法生成代理对象,涉及到Java反射机制,所在包是java.lang.reflect,涉及的类用类Proxy类,使用方法是Proxy.newProxyInstance方法。

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

其中:

  • ClassLoader:类所使用的ClassLoader类加载器
  • Interfaces:目标对象所实现的接口Class数组,代理类可以代理其中农的方法
  • InvocationHandler:确定代理类最终会绑定在那个Handler上,换句话说,接口里定义了代理方法所执行的逻辑。
public interface InvocationHandler {
    
    
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
    //这个方法里的代码就是我们调用代理对象方法时要执行的逻辑
}

此接口中只有一个抽象方法invoke此方法有几个参数:

  • Proxy:被代理的目标对象(原对象)
  • Method:调用了什么方法
  • Args:调用方法时传递的参数
  • return:调用方法时的返回值

我们在此方法中定义的语句,就是我们调用代理对象任意方法(接口里的抽象方法)后所执行的代码!

总结来说:
我们使用Proxy.newProxyInstance方法创建代理对象,创建时需要传入加载类使用的类加载器,目标对象所实现的接口,和要执行的代码的Handler,在代理对象调用方法时,我们可以通过Hnadler获取到,在此Handler中定义我们调用代理对象时所执行的代码,并且可以获取到调用方法时所传递的参数,还可以手动确定代理对象调用后的返回值!

这里涉及到Java反射机制,JVM类加载器等知识点,可以看我其他帖子,里面有比较详细的介绍!

实例演示:将上方代码使用动态代理重写

package com.wojiushiwo;
import com.sun.org.apache.bcel.internal.generic.RET;
import com.sun.org.apache.xpath.internal.operations.Bool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
/**
 * @author 我就是我500
 * @date 2020-02-20 15:01
 * @describe
 **/
public class ProxyTest {
    
    
    public static void main(String[] args) {
    
    
        UserDAO userDAO = new UserDaoImpl();
        UserDAO proxy = getProxyInstance(userDAO);
        proxy.deleteUser(1);
        proxy.deleteUser(2);
        proxy.deleteUser(3);
        //这时,我们只是调用了基础的方法,就可以连同输出语句一起调用
    }
    //定义获取代理对象的方法
    public static <T> T getProxyInstance(T t)
    {
    
    
    	//创建InvocationHandler,里面定义了调用代理方法后所执行的代码
        InvocationHandler handler = new InvocationHandler() {
    
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
                System.out.println("开始请求删除用户"+args[0]+",时间:"+new Date());
                //执行原本的方法,这里可以进行其他判断,比如获取方法名称调用等
                Object result = method.invoke(t,args);
                if((boolean)result)
                {
    
    
                    System.out.println("用户"+args[0]+"删除成功,请求时间:"+new Date());
                }
                else
                {
    
    
                    System.out.println("用户"+args[0]+"删除失败,请求时间:"+new Date());
                }

                return result;
            }

        };
        return (T)Proxy.newProxyInstance(t.getClass().getClassLoader(),t.getClass().getInterfaces(),handler);
    }
}

//DAO接口
interface UserDAO
{
    
    
    boolean deleteUser(int id);
}
//DAO实现类
class UserDaoImpl implements UserDAO
{
    
    
    //模拟删除一个用户
    @Override
    public boolean deleteUser(int id) {
    
    
        System.out.println("数据库操作......");
        return true;
    }
}

结果与上方完全相同:
结果与上方相同

4. Cglib动态代理

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

那么在AOP编程中如何选择代理模式:
其实Cglib动态代理与JDK动态代理没有优劣之分,可以根据需求选择,比如:

  • 目标对象需要实现接口,用JDK代理
  • 目标对象不需要实现接口,用Cglib代理

方法简介:

cglib的jar下载地址:
https://download.csdn.net/download/qq_42628989/12177838

先引入jar包:cglib-nodep-2.2.2.jar
Cglib创建动态代理对象需要实现MethodInterceptor接口,并实现其中的intercept
方法。Interect方法中所定义的代码就是我们调用代理对象时所执行的方法!

interface MethodInterceptor
{
    
    
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable;
	 //这个方法里的代码就是我们调用代理对象方法时要执行的逻辑
}

其中:
Obj:被代理的目标对象(原对象)
Method:调用了什么方法
Args:调用方法时传递的参数
methodProxy:生成的代理对象
return:调用方法时的返回值

MethodInterceptor是我们的方法拦截器,等同于上文中的InvocationHandler,而创建代理对象需要使用Enhancer类,Enhancer生成代理对象主要用到以下三种方法:

  • enhancer.setSuperclass(目标对象);
  • enhancer.setCallback(方法拦截器);
  • enhancer.create();

调用create()后所生成的对象即是我们的代理对象!

实例演示:将上方代码改为Cglib实现

package com.wojiushiwo;
import com.sun.org.apache.bcel.internal.generic.RET;
import com.sun.org.apache.xpath.internal.operations.Bool;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
/**
 * @author 我就是我500
 * @date 2020-02-20 15:01
 * @describe
 **/
public class ProxyTest {
    
    
    public static void main(String[] args) {
    
    
        UserDao userDao =new UserDao();
        UserDao proxy = getProxyInstance(userDao);
        proxy.deleteUser(1);
        proxy.deleteUser(2);
        proxy.deleteUser(3);
    }
    public static <T> T getProxyInstance(T t)
    {
    
    
        MethodInterceptor methodInterceptor = new MethodInterceptor()
        {
    
    
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
                System.out.println("开始请求删除用户"+args[0]+",时间:"+new Date());
                Object result = method.invoke(t,args);
                if((boolean)result)
                {
    
    
                    System.out.println("用户"+args[0]+"删除成功,请求时间:"+new Date());
                }
                else
                {
    
    
                    System.out.println("用户"+args[0]+"删除失败,请求时间:"+new Date());
                }
                return result;

            }
        };
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(t.getClass());
        enhancer.setCallback(methodInterceptor);
        return (T)enhancer.create();
    }

}
class UserDao
{
    
    
    //模拟删除一个用户
    public boolean deleteUser(int id) {
    
    
        System.out.println("数据库操作......");
        return true;
    }
}

执行结果
执行结果完全相同!

cglib动态代理与JDK动态代理步骤基本相同,两者可以类比学习!

猜你喜欢

转载自blog.csdn.net/qq_42628989/article/details/104423287