在港片中,经常能看到一些酷炫的大哥被警察抓了,警察会试图从他们口中套出一些关键信息。但这些大哥们通常会非常冷静地回应:“我有权保持沉默,我要找我的律师。”
这个律师就像是大哥的“法律盾牌”,全权处理所有法律事务。律师的角色不仅仅是代理大哥发言,更是在法律的战场上为大哥披荆斩棘。具体来说,律师会做以下几件事情:
准备法律文件:律师会精心准备各种法律文件,确保每一份文件都无懈可击,为大哥的辩护打下坚实的基础。
与检察官沟通:律师会与检察官进行多轮沟通,争取对大哥最有利的条件,甚至可能会在法庭上展开一场激烈的辩论。
法庭辩护:在法庭上,律师会代表大哥发言,用专业的法律知识和丰富的经验,为大哥进行有力的辩护,确保大哥的权益不受侵犯。
提供法律建议:律师还会向大哥提供专业的法律建议,帮助他了解自己的权利和义务,让他在法律的框架内做出最明智的决定。
这个过程就像是一场精心策划的战役,律师作为代理,不仅代理了大哥的发言,还在操作执行前后添加了诸多增强处理,确保大哥在法律的保护下安然无恙。这种场景在港片中屡见不鲜,不仅展示了法律的严肃性,还增添了一丝戏剧性和紧张感,让人看得津津有味。
港片中的大哥,就是运用代理模式的行家能手。下面来说代理模式,代理模式是一种结构型的设计模式,代理对象(律师)具备真实对象(犯事大哥)的功能,并代替真实对象完成相应操作(律师代表大哥讲话),同时可以在操作执行前后进行增强处理(准备法律文件、与检察官沟通等)。
通过一个形象的例子我们知道了代理模式,通过这个例子我们可以聊一下为什么要用代理模式(请律师):
权限控制(保护目标对象):保护客户的隐私和权益,避免不当操作。律师作为代理,可以控制犯罪嫌疑人与外界的交流,确保所有信息的传递都在法律框架内进行。例如,律师可以决定哪些信息可以透露给检察官,哪些信息需要保密。
优化性能(提高效率):代理模式可以实现资源的延迟加载和缓存,提高资源利用效率,减少不必要的资源消耗。律师在需要时才会准备具体的法律文件,而不是一开始就进行大量的准备工作。例如,只有在确定案件进入法庭审理阶段时,律师才会准备详细的辩护词。
增加功能(减轻目标对象的负担):代理模式可以在代理对象中添加额外的功能,而不需要修改原始对象的代码。
简化客户端代码:客户端可以更专注于核心业务,而不必处理复杂的细节。犯罪嫌疑人只需要与律师沟通,而不需要直接处理复杂的法律事务。
代理对象分为2类分别是静态代理和动态代理
静态代理需要我们自己去实现代理模式。下面以请律师为demo实现一个静态代理的示例。从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就引进动态代理。
1、定义法律援助接口
public interface LegalService {
void representClient();
}
2、目标对象(大哥)
public class Suspect implements LegalService {
@Override
public void representClient() {
System.out.println("我是大哥,我需要律师为我进行辩护.");
}
}
3、实现代理(律师)
public class LawyerProxy implements LegalService {
private Suspect suspect;
public LawyerProxy(Suspect suspect) {
this.suspect = suspect;
}
@Override
public void representClient() {
prepareLegalDocuments();
communicateWithProsecutor();
suspect.representClient();
provideLegalAdvice();
}
private void prepareLegalDocuments() {
System.out.println("准备法律文件.");
}
private void communicateWithProsecutor() {
System.out.println("与检察官进行战略沟通,协商有利条件.");
}
private void provideLegalAdvice() {
System.out.println("为嫌疑人提供专业法律建议,确保其权利得到保护.");
}
}
4、 场景
public class Main {
public static void main(String[] args) {
Suspect suspect = new Suspect();
LegalService lawyer = new LawyerProxy(suspect);
lawyer.representClient();
}
}
//输出场景内容
准备法律文件.
与检察官进行战略沟通,协商有利条件.
我是大哥,我需要律师为我进行辩护.
为嫌疑人提供专业法律建议,确保其权利得到保护.
动态代理是开源工具提供给我们实现好的工具可以直接使用(动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务。其实运用的是反射机制)。目前来说接触到的动态代理主要有2种分别对应的是jdk实现的动态代理 和 cglib动态代理。
jdk动态代理主要使用的是 java.lang.reflect包。
核心类:InvocationHandler接口 和 Proxy类
核心方法:invoke 和 newProxyInstance
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable
public static Object newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)throws IllegalArgumentException
法律援助接口和目标对象(大哥)不用变使用静态代理的代码,其他进行修改。
1、实现InvocationHandler ,作为代理处理器
InvocationHandler
,这个处理器将拦截对真实对象的调用,并在调用前后添加额外的逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LawyerProxyHandler implements InvocationHandler {
private final LegalService realLawyer;
public LawyerProxyHandler(LegalService realLawyer) {
this.realLawyer = realLawyer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用真实方法之前执行的逻辑
preHandle(args);
// 调用真实方法
Object result = method.invoke(realLawyer, args);
// 在调用真实方法之后执行的逻辑
postHandle(args);
return result;
}
private void preHandle(Object[] args) {
System.out.println("律师正在准备法律文件和证据.");
}
private void postHandle(Object[] args) {
System.out.println("律师已完成对委托人的代理.");
}
}
2、创建具体代理对象(律师)
import java.lang.reflect.Proxy;
public class LegalServiceProxy {
public static LegalService getProxyInstance(LegalService realLawyer) {
return (LegalService) Proxy.newProxyInstance(
realLawyer.getClass().getClassLoader(), // 目标类的类加载器
new Class>[]{LegalService.class}, // 目标类实现的接口
new LawyerProxyHandler(realLawyer) // 代理处理器
);
}
}
3、使用场景
public class Main {
public static void main(String[] args) {
// 创建真实对象
LegalService dg = new RealLawyer();
// 创建代理对象
LegalService lawyerProxy = LegalServiceProxy.getProxyInstance(dg);
// 通过代理对象调用方法
lawyerProxy.representClient();
}
}
//输出
律师正在准备法律文件和证据.
我是大哥,我需要律师为我进行辩护.
律师已完成对委托人的代理.
从上面可以看出,jdk动态代理的前提条件是,要有接口存在,那还有许多场景是没有接口的,这个时候就需要cglib动态代理了,CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。
1、大哥类要改一下,因为没有接口
public class Suspect{
@Override
public void representClient() {
System.out.println("我是大哥,我需要律师为我进行辩护.");
}
}
2、 实现MethodInterceptor,作为代理处理器
实现MethodInterceptor
接口,这个拦截器将拦截对真实对象的调用,并在调用前后添加额外的逻辑。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LawyerProxyInterceptor implements MethodInterceptor {
private final Suspect suspect;
public LawyerProxyInterceptor(Suspect suspect) {
this.suspect = suspect;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在调用真实方法之前执行的逻辑
preHandle(args);
// 调用真实方法
Object result = proxy.invokeSuper(obj, args);
// 在调用真实方法之后执行的逻辑
postHandle(args);
return result;
}
private void preHandle(Object[] args) {
System.out.println("律师正在准备法律文件和证据.");
}
private void postHandle(Object[] args) {
System.out.println("律师已完成对委托人的代理.");
}
}
3、 创建具体代理对象(律师)
使用CGLIB的Enhancer
类创建代理对象。这个代理对象将代理真实对象,并在调用方法时通过MethodInterceptor
进行拦截。
import net.sf.cglib.proxy.Enhancer;
public class LegalServiceProxy {
public static RealLawyer getProxyInstance(Suspect suspect) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(suspect.getClass()); // 设置目标类
enhancer.setCallback(new LawyerProxyInterceptor(suspect)); // 设置回调
return (Suspect) enhancer.create(); // 创建代理对象
}
}
4、使用场景
public class Main {
public static void main(String[] args) {
// 创建真实对象
Suspect suspect = new Suspect();
// 创建代理对象
Suspect lawyerProxy = LegalServiceProxy.getProxyInstance(suspect);
// 通过代理对象调用方法
lawyerProxy.representClient();
}
}
//输出
律师正在准备法律文件和证据.
我是大哥,我需要律师为我进行辩护.
律师已完成对委托人的代理.
JDK动态代理和CGLIB动态代理是Java中实现动态代理的两种常用机制,虽然它们都可以为目标对象创建代理对象并拦截方法调用,但它们的工作原理和使用场景有所不同。以下是它们的区别:
JDK动态代理:
基于接口:JDK动态代理要求被代理的类必须实现一个或多个接口。如果类没有实现任何接口,JDK动态代理将无法工作。
优点:实现简单,使用Java内置API、无需依赖第三方库。
缺点:只能代理接口,不能代理普通类。方法调用时使用反射,性能相对较低。
CGLIB动态代理:
基于类集成:CGLIB动态代理通过生成目标类的子类来实现代理。它不要求目标类必须实现接口,因此它适用于没有实现接口的类。
优点:可以代理没有接口的类。方法调用性能较高,避免了反射调用。
缺点:创建代理类时需要进行字节码操作,性能开销较大。需要依赖cglib和ASM库。
JDK动态代理:
使用反射机制,通过 java.lang.reflect.Proxy
类和 InvocationHandler
接口来实现代理。代理对象仅代理接口中的方法。
当调用代理对象的方法时,代理类会拦截方法调用,并通过 InvocationHandler.invoke()
方法执行额外的逻辑。
CGLIB动态代理:
基于字节码操作,使用 CGLIB(Code Generation Library)生成目标类的子类并重写目标类的方法来实现代理。通过继承方式拦截所有非 final
方法的调用。
CGLIB 使用的是 ASM 字节码生成框架,生成的是字节码级别的代理类,因此性能相对较好,但生成代理类的开销比JDK动态代理略大。
JDK动态代理:
对于实现了接口的类来说,JDK动态代理在创建代理对象时开销较小,因为它仅依赖反射机制来处理接口方法的调用。
对于频繁调用代理方法的场景,JDK动态代理可能比CGLIB略慢,因为每次调用都涉及反射。
CGLIB动态代理:
由于CGLIB是通过字节码生成来创建代理类,生成代理类的开销比JDK动态代理高一些,尤其是在代理类较多的情况下。
但CGLIB代理的实际方法调用性能更高,因为它通过字节码操作,减少了反射调用的开销。
JDK动态代理:
适用于接口驱动的编程,如果目标类实现了接口,那么使用JDK动态代理是首选方式。
适合在不需要对类进行直接代理的场景,通常在应用中,业务逻辑往往是通过接口定义的,因此JDK代理在实际项目中更常用。
CGLIB动态代理:
适用于没有实现接口的类,例如一些现有类或者第三方库的类没有提供接口的情况下,可以使用CGLIB动态代理。
适用于对类进行代理时,但需要注意类不能是 final
,否则CGLIB无法生成代理子类。
JDK动态代理:
在Spring AOP中,如果目标对象实现了接口,Spring默认使用JDK动态代理。这是因为Spring AOP的核心思想是基于接口的面向切面编程(Aspect-Oriented Programming)。
CGLIB动态代理:
如果目标对象没有实现任何接口,Spring AOP会自动使用CGLIB动态代理。在Spring配置中,你也可以强制使用CGLIB代理(通过设置 proxyTargetClass=true
)。