• JAVA中的动态代理和反射有什么区别和联系?
  • 发布于 2个月前
  • 424 热度
    0 评论
前言
代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了客户端和实际对象之间的中介,客户端通过代理对象访问实际对象,从而隐藏了实际对象的具体实现细节。

代理模式通常涉及三个角色:
抽象主题(Subject):定义了代理对象和实际对象之间的共同接口,客户端通过该接口访问实际对象。
实际主题(Real Subject):实现了抽象主题接口,是被代理的真实对象,即客户端最终要访问的对象。
代理(Proxy):实现了抽象主题接口,并持有一个实际主题的引用。代理对象在执行具体操作之前或之后,可以添加额外的逻辑。
代理模式的主要目的是在不改变实际对象的情况下,通过代理对象提供额外的功能或控制访问。它可以用于实现延迟加载、访问控制、远程访问、事务管理等。代理模式还可以实现AOP(面向切面编程)的核心机制,通过代理对象在方法执行前后添加横切逻辑。

优点:
1.代理对象和实际对象之间的解耦,客户端只需要通过代理对象进行访问,无需直接与实际对象交互。
2.可以在代理对象中添加额外的逻辑,如访问控制、缓存、日志记录等。

3.可以实现延迟加载,只有在需要时才创建实际对象。


缺点:
1.增加了系统的复杂性,引入了额外的代理类。

2.可能会降低系统的性能,因为代理对象需要执行额外的操作。


演示
JDK动态代理
实现方式:JDK动态代理是基于接口的代理,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。通过Proxy.newProxyInstance方法创建代理对象,代理对象会实现指定接口,并将方法调用转发给InvocationHandler处理。
限制:JDK动态代理要求目标对象必须实现接口。如果目标对象没有实现接口,就无法使用JDK动态代理。
性能:JDK动态代理的性能相对较低,因为它需要使用反射来调用目标对象的方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 堆代码 duidaima.com
interface UserService {
    void addUser(String username);
}

class UserServiceImpl implements UserService {
    public void addUser(String username) {
        System.out.println("Adding user: " + username);
    }
}

class UserServiceProxy implements InvocationHandler {
    private Object target;

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method call");
        Object result = method.invoke(target, args);
        System.out.println("After method call");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserServiceProxy userServiceProxy = new UserServiceProxy();
        UserService proxy = (UserService) userServiceProxy.bind(userService);
        proxy.addUser("堆代码");
    }
}
在上面的示例中,我们定义了一个UserService接口和一个实现类UserServiceImpl。然后,我们创建了一个UserServiceProxy类,实现了InvocationHandler接口,并在invoke方法中添加了额外的逻辑。最后用Proxy.newProxyInstance方法创建了代理对象,并将其绑定到实际对象上。

CGLIB动态代理
实现方式:CGLIB动态代理是基于类的代理,使用net.sf.cglib.proxy.Enhancer类和net.sf.cglib.proxy.MethodInterceptor接口实现。通过Enhancer创建代理对象,代理对象会继承目标对象,并重写方法,在方法调用时通过MethodInterceptor处理。
限制:CGLIB动态代理可以代理没有实现接口的类,因为它是基于类的代理。
性能:CGLIB动态代理的性能相对较高,因为它直接操作字节码,无需使用反射。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class UserService {
    public void addUser(String username) {
        System.out.println("Adding user: " + username);
    }
}

class UserServiceInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method call");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method call");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new UserServiceInterceptor());
        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("堆代码 ");
    }
}
在上面的示例中,我们定义了一个UserService类,没有实现任何接口。然后,我们创建了一个UserServiceInterceptor类,实现了MethodInterceptor接口,并在intercept方法中添加了额外的逻辑。最后使用Enhancer类创建了代理对象,并设置了回调对象为UserServiceInterceptor。

总结
实现方式:JDK动态代理是基于接口的代理,而CGLIB动态代理是基于类的代理。
接口要求:JDK动态代理要求目标对象必须实现接口,而CGLIB动态代理可以代理没有实现接口的类。
性能:CGLIB动态代理的性能通常比JDK动态代理更高,因为它直接操作字节码,无需反射。但在某些情况下,JDK动态代理可能更适合,特别是当目标对象实现了多个接口时。
选择使用JDK动态代理还是CGLIB动态代理取决于具体的使用场景。如果目标对象实现了接口,且对性能要求不高,可以选择JDK动态代理。如果目标对象没有实现接口,或对性能要求较高,可以选择CGLIB动态代理。

这里顺带提一下动态代理与反射:
动态代理
目的:动态代理是一种机制,用于在运行时创建代理对象,以在代理对象上执行额外的逻辑或操作。
使用方式:使用动态代理时,我们需要定义一个接口或类,并创建一个代理对象,将其绑定到实际对象上。代理对象会拦截对实际对象的方法调用,并可以在调用前后执行额外的逻辑。

实现方式:Java提供了两种动态代理的实现方式,即基于接口的动态代理(JDK动态代理)和基于类的动态代理(CGLIB动态代理)。


反射
目的:反射是一种机制,用于在运行时检查、获取和操作类的成员(字段、方法、构造函数等)。
使用方式:使用反射时,我们可以通过Class对象获取类的信息,如字段、方法、构造函数等。然后,可以使用反射机制调用这些成员,甚至可以在运行时创建类的实例。

实现方式:Java的反射机制主要通过Class、Field、Method等类来实现。


1.通过类的全限定名字符串获取Class对象
Class<?> clazz = Class.forName("com.example.MyClass");
2.通过类名的.class语法获取Class对象
Class<?> clazz = MyClass.class;
3.通过类加载器的loadClass方法获取Class对象
ClassLoader classLoader = MyClass.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
4.通过对象的getClass方法获取Class对象
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();


class MyClass {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("MyClass");
            Object obj = clazz.newInstance();

            Method method = clazz.getMethod("sayHello", String.class);
            // Method method = clazz.getDeclaredMethod("sayHello", String.class);
            method.invoke(obj, "堆代码");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
在上面的示例中,我们使用Class.forName方法通过类名获取Class对象。然后,使用newInstance方法创建类的实例。接下来,获取sayHello方法的Method对象,并使用invoke方法调用该方法,传递参数"堆代码"。
getDeclaredMethod和getMethod是Java反射中用于获取方法的两个方法。它们在获取方法时有以下几个区别:

访问级别:
getDeclaredMethod:可以获取类中声明的所有方法,包括私有方法、受保护方法和公共方法。不考虑方法的访问修饰符。
getMethod:只能获取公共方法(public修饰符),无法获取私有方法和受保护方法。
继承关系:
getDeclaredMethod:可以获取类中声明的方法,包括私有方法、受保护方法和公共方法,不考虑继承关系。
getMethod:除了能获取类中声明的公共方法,还可以获取从父类继承的公共方法。
参数类型:
getDeclaredMethod和getMethod都可以通过参数类型来定位方法。但是,对于方法重载的情况,getDeclaredMethod和getMethod的参数类型要求略有不同。
getDeclaredMethod:参数类型必须与目标方法的参数类型完全匹配。
getMethod:参数类型可以是目标方法参数类型的子类。
动态代理和反射都是在运行时处理类和对象的机制。
它们都允许我们在程序运行时动态地获取和操作类的信息。
动态代理和反射都提供了一种动态性的机制,允许我们在运行时进行灵活的操作。
它们在某些场景下可以结合使用,例如使用反射获取类的信息,然后使用动态代理来创建代理对象并执行额外的逻辑。
总结来说,动态代理和反射都是Java中非常有用的机制,可以在运行时动态地操作类和对象。动态代理主要用于创建代理对象并执行额外的逻辑,而反射主要用于在运行时检查和操作类的成员。
用户评论