Java 代理

代理的概念

代理是使用一个更强大的类(在原类的基础上进行功能扩展)来代替原来的类进行工作。

比如在使用UserDao时,还想做一些事务处理、日志记录等其它操作,这些操作不属于UserDao(持久层、操作数据库)的范畴,不能封装到UserDao中。

这时就可以使用代理来对原来的类进行增强。代理类在原有类的基础上进行了扩展,保留了原有类所有的功能,并添加了其他功能,更加强大。

被代理的类(UserImpl类)叫做目标类,实现代理的类(比如UserProxy)叫做代理类。

代理(proxy)分为2种:

  • 静态代理
  • 动态代理     常用的有jdk动态代理、cglib代理

原来的接口、实现类

public interface UserDao {
    public void addUser();
    public void deleteUser();
}
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("正在添加用户...");
    }

    @Override
    public void deleteUser() {
        System.out.println("正在删除用户...");
    }
}

 

静态代理

代理类:

public class UserDaoProxy implements UserDao{
    //把要代理的接口或类写成成员变量。声明为接口,可以代理这个接口所有的实现类;声明为实现类,则只能代理这个实现类。
    private UserDao userDao;

    //注入目标类的对象。
    public UserDaoProxy(UserDao userDao) {
        this.userDao = userDao;
    }


    @Override
    public void addUser() {
        //前增强
        System.out.println("正在执行前增强...");

        //调用目标类的方法
        userDao.addUser();

        //后增强
        System.out.println("正在执行后增强...");
    }

    @Override
    public void deleteUser() {
        //如果不需要增强,直接调用目标类的方法即可
        userDao.deleteUser();
    }
}

在编译时就已确定要增强的方法,以及方法的参数表(参数类型、个数),

代理对象中增强的方法、参数表已经固定了,不会变化,是静态的,所以叫做静态代理。

使用:

        //创建目标类对象
        UserDao userDao = new UserDaoImpl();

        //创建代理。
        UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);

        //不再使用目标类对象,使用的是代理
        userDaoProxy.addUser();    

静态代理的特点

  • 代理类需要 implements  目标类的接口,因为要重写目标类的方法。就是说目标类必须要有父接口。
  • 十分灵活,可以单独设置每个方法的增强
  • 很繁琐,因为需要手动设置每个方法的增强

JDK动态代理

代理类:

package com.chy.proxy;

import com.chy.dao.UserDao;

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

public class UserDaoProxyFactory {
    //将目标对象作为成员变量,需要声明为目标接口,不能声明为目标类。
    private UserDao userDao;

    //注入目标对象
    public UserDaoProxyFactory(UserDao userDao) {
        this.userDao = userDao;
    }

    //获取代理。代理是目标接口的实例。
    public UserDao getProxyInstance(){
        //获取目标类的类加载器。
        ClassLoader classLoader = userDao.getClass().getClassLoader();
        //获取目标类实现的所有接口的class对象。
        Class<?>[] interfaces = userDao.getClass().getInterfaces();

        //创建InvocationHandler接口的实例。此处使用匿名内部类来创建。
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //前增强
                System.out.println("正在执行前增强...");

                //调用目标类中对应的方法,需传入目标对象
                Object returnValue=method.invoke(userDao,args);

                //后增强
                System.out.println("正在执行后增强...");
                return returnValue;
            }
        };

        //使用Proxy类的静态方法创建实例,这个实例就是目标对象的代理。返回值是Object类型,需要强转。
        Object userDaoProxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        //强转为目标接口类型
        return (UserDao)userDaoProxy;
    }
}

看invoke()的参数,要增强的方法、参数表都不确定,需要动态传入,根据传入的参数来确定,所以叫做动态代理。

(虽然代理对象是Obejct,但成员变量已经声明为接口,实际是接口类型的)

使用:

        //创建目标对象
        UserDaoImpl userDao = new UserDaoImpl();

        //传入目标对象,创建代理。代理是目标接口类型。
        UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance();
        
        //通过代理对象来调用方法
        userDaoProxy.addUser();    

  

JDK动态代理的特点

  • 是使用jdk中的反射实现的动态代理,不像cglib一样要使用第三方的包,所以叫做jdk动态代理。
  • 代理类不需要实现接口,但目标类必须要有父接口。可代理该接口下所有的实现类。
  • 通过代理调用的目标类的方法都会被增强,且所使用的增强完全一样,有点死板。
  • 代理对象不是新类型的,而是属于该接口类型。

CGLIB动态代理

如果单独使用cglib,需要导入cglib.jar、cglib的依赖包asm.jar。

如果使用maven,则会自动导入依赖的asm.jar。

如果使用spring,在spring的核心包spring-core.jar中已经内嵌了cglib所需的包,不必再导包。

代理类:

public class UserDaoProxyFactory implements MethodInterceptor {
    //目标对象
    private UserDao userDao;

    //传入目标对象
    public UserDaoProxyFactory(UserDao target) {
        this.userDao = target;
    }

    //拦截目标方法,进行增强
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //前增强
        System.out.println("前增强...");

        //调用目标对象的方法
        Object returnValue = method.invoke(userDao, objects);

        //后增强
        System.out.println("后增强...");

        //返回目标方法的返回值,Object类型
        return returnValue;
    }

    //创建代理对象,接口类型
    public UserDao getProxyInstance(){
        //创建工具类对象
        Enhancer en = new Enhancer();
        //设置基类(父类),即继承目标类
        en.setSuperclass(userDao.getClass());
        //设置回调函数。此句代码是调用intercept()拦截目标方法,进行增强
        en.setCallback(this);
        //创建并返回代理对象。创建的对象是Object型,需要强转。
        return (UserDao) en.create();
    }
}

 看intercept()的参数,要增强的方法、参数都是动态传入。

目标对象可以使用接口类型,可代理此接口的所有实现类;也可使用某个具体的类(可以把上面的接口UserDao换为具体类UserDaoImpl),只能代理这个类。就是说目标类可以不实现接口。

 

使用:

        //创建目标对象
        UserDao userDao = new UserDaoImpl();
        //创建代理对象,需传入目标对象
        UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance();
        //通过代理调用方法
        userDaoProxy.addUser();

  

CGLIB动态代理的特点

  • 目标类实不实现接口都可以
  • 目标类不能用final修饰,因为代理类要继承目标类;目标类中的方法不能使用final(要重写,前后增强)、static(要求是实例方法)修饰。
  • 通过代理调用的目标类方法都会被增强

静态代理、jdk动态代理只能增强接口(代理接口的所有实现类),cglib动态代理既可增强接口,又可只增强某个具体的类。

静态代理的代理对象是代理类类型,jdk代理的代理对象是接口类型,cglib代理的代理对象是接口类型 | 目标类类型。

如果目标类实现了接口,使用三种代理都行;如果目标类没有实现接口,只能使用cglib代理。

如果要对方法做不同的增强,用静态代理;如果对每个方法的增强都一样,用动态代理。

如果前后增强的代码要复用,可以封装成函数,调用函数即可。

原文地址:https://www.cnblogs.com/chy18883701161/p/11391061.html