设计模式的类型
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 |
|
2 | 结构型模式 这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。 |
|
3 | 行为型模式 这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。 |
|
4 | J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 |
|
单例模式
单例模式分为懒汉式、饿汉式
懒汉式:
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() { //通过加锁来实现线程安全
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式:
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class Singleton {
private static Singleton instance = new Singleton(); //
private Singleton (){}
public static Singleton getInstance() { //静态方法
return instance;
}
}
在Android源码中,有一些重要的类和组件使用了单例模式。以下是一些常见的例子:
-
Application类:Android应用程序的入口点是一个继承自Application类的对象。这个类在整个应用程序的生命周期中只会创建一个实例,因此可以看作是一个全局单例。
-
ActivityManager:ActivityManager是Android系统提供的一个管理Activity的类,它负责管理应用程序中的Activity栈、启动新的Activity等。ActivityManager被设计为单例,通过ActivityManager.getInstance()方法获取其唯一实例。
-
LayoutInflater:LayoutInflater是Android系统用于解析布局资源文件并生成对应View对象的类。LayoutInflater通过调用LayoutInflater.from()方法获取其唯一实例。
-
WindowManager:WindowManager是Android系统用于管理窗口的类,它负责显示和管理应用程序的UI界面。WindowManager被设计为单例,通过WindowManagerImpl.getDefault()方法获取其唯一实例。
-
TelephonyManager:TelephonyManager是Android系统提供的用于访问与电话相关的信息和功能的类。TelephonyManager通过调用Context.getSystemService()方法获取其唯一实例。
-
NotificationManager:NotificationManager是Android系统用于管理通知的类,它负责发送、取消和管理通知。NotificationManager被设计为单例,通过调用Context.getSystemService()方法获取其唯一实例。
注意,这些只是Android源码中使用单例模式的一些例子,并不是全部。实际上,在Android源码中,还有很多其他的类和组件也使用了单例模式,目的是为了保证在整个系统中只有一个实例存在,方便统一管理和访问。
工厂模式
适配器模式:
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
android中的Adapter就有很多了,这个大家都经常用。 Adapter是AdapterView视图与数据之间的桥梁,Adapter提供对数据的访问,也负责为每一项数据产生一个对应的View。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
例子下面这个图:
我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。
我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。
我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter, MediaAdapter使用 AdvancedMediaPlayer 对象来播放所需的格式。
AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式。
责任链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。 发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
编程中的小体现:
if(a<10){
...
}
else if (a<20){
...
}
else if(a<30){
...
}
else{
...
}
程序必须依次扫描每个分支进行判断,找到对应的分支进行处理。
优点:
可以降低系统的耦合度(请求者与处理者代码分离),简化对象的相互连接,同时增强给对象指派职责的灵活性,增加新的请求处理类也很方便;
缺点:不能保证请求一定被接收,且对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。 每次都是从链头开始,这也正是链表的缺点。
Android中:触摸、按键等各种事件的传递就是这种模式,可以看一下这篇文章View事件分发机制源码分析,我这就不多说了。
责任链模式在Android源码中广泛应用,尤其在事件处理、消息传递等方面。以下是一些Android源码中使用责任链模式的典型场景:
-
事件分发机制:Android中的View事件分发机制就是一个经典的责任链模式的应用。事件从Activity的Window开始传递,依次经过Window、DecorView、ViewGroup和最终的View。在传递的过程中,每个节点都有机会处理事件或将事件传递给下一个节点,直到事件被处理或到达终点。
-
Handler消息处理:在Android中,Handler和Looper配合使用来实现线程间的消息传递和处理。当一个消息发送到Handler时,它会被添加到消息队列中,然后由Looper负责从队列中取出消息,并将消息传递给Handler进行处理。整个过程中,消息的处理可以被Handler链中的不同节点处理,直到消息被处理或链结束。
-
BroadcastReceiver处理广播:当一个广播发送时,Android系统会根据注册的BroadcastReceiver来处理广播。多个BroadcastReceiver可以注册相同的广播,它们形成一个责任链,依次处理广播。如果某个BroadcastReceiver处理了广播,则广播不会再传递给后面的Receiver。
-
拦截器(Interceptor):在Android开发中,拦截器模式经常用于处理网络请求、权限验证等场景。例如,OkHttp库中的Interceptor就是一个责任链模式的实现,每个Interceptor可以对请求进行预处理或者对响应进行处理,然后将请求或响应传递给下一个Interceptor。
观察者模式:
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
在Android中,广播使用了观察者模式(也称为发布-订阅模式)。观察者模式是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。
在Android中,广播由两个主要组件组成:广播发送者(Broadcast Sender)和广播接收者(Broadcast Receiver)。广播发送者负责发送广播消息,而广播接收者则负责接收并处理广播消息。
具体而言,Android广播使用了以下几个角色和机制:
-
广播发送者(Broadcast Sender):广播发送者是发送广播消息的组件,可以是应用程序的任意组件,例如Activity、Service或BroadcastReceiver。广播发送者通过发送特定的广播意图(Intent)来触发广播事件。
-
广播接收者(Broadcast Receiver):广播接收者是注册接收广播消息的组件。它通过定义一个继承自BroadcastReceiver的子类,并实现onReceive()方法来处理接收到的广播消息。
-
广播意图(Broadcast Intent):广播意图是一个用于描述广播类型的对象,它是以字符串形式表示的广播事件。广播发送者通过发送带有特定广播意图的Intent来触发广播事件。
-
广播注册(Broadcast Registration):广播接收者需要在代码中注册才能接收广播消息。这可以通过在AndroidManifest.xml文件中声明广播接收者,或者在运行时使用Context.registerReceiver()方法进行动态注册。
-
广播过滤器(Broadcast Filter):广播过滤器用于过滤广播消息,只有符合过滤条件的广播消息才会被接收者接收到。过滤条件可以基于广播意图的action、category和data等属性进行匹配。
通过观察者模式,Android广播实现了松耦合的消息传递机制,使得不同组件之间可以方便地进行通信和交互。这种设计模式在实现事件驱动型的应用场景中非常常见。
代理模式
https://juejin.cn/post/6844903744954433544
Java动态代理与java反射机制关系紧密:Java反射机制详解
Java 代理模式详解 | JavaGuide (这个理解比较好)
如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
- 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
- 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件
静态代理:
举例:
https://juejin.cn/post/6844903744954433544
静态代理的缺点
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
- 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
- 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
如何改进?---动态代理
当然是让代理类动态的生成啦,也就是动态代理。
为什么类可以动态的生成?
这就涉及到Java虚拟机的类加载机制了,推荐翻看《深入理解Java虚拟机》7.3节 类加载的过程。
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据访问入口
由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
- 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
- 从网络中获取,典型的应用是 Applet
- 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为
*$Proxy
的代理类的二进制字节流 - 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
- 从数据库中获取等等
所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。但是如何计算?如何生成?情况也许比想象的复杂得多,我们需要借助现有的方案。
实现动态代理的思考方向
为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:
- 通过实现接口的方式 -> JDK动态代理
- 通过继承类的方式 -> CGLIB动态代理
注:使用ASM对使用者要求比较高,使用Javassist会比较麻烦
1 JDK动态代理
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
,我们仍然通过案例来学习
JDK 动态代理类使用步骤
- 定义一个接口及其实现类;
- 自定义
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
举例:
编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke
方法中编写方法调用的逻辑处理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client2 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 1. 创建被代理的对象,UserService接口的实现类
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
Class[] interfaces = userServiceImpl.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler logHandler = new LogHandler(userServiceImpl);
/*
5.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
// 调用代理的方法
proxy.select();
proxy.update();
// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
}
}
运行结果:
log start time [Thu Dec 20 16:55:19 CST 2018]
查询 selectById
log end time [Thu Dec 20 16:55:19 CST 2018]
log start time [Thu Dec 20 16:55:19 CST 2018]
更新 update
log end time [Thu Dec 20 16:55:19 CST 2018]
InvocationHandler 和 Proxy 的主要方法介绍如下:
java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)
定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
java.lang.reflect.Proxy
static InvocationHandler getInvocationHandler(Object proxy)
用于获取指定代理对象所关联的调用处理器
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回指定接口的代理类
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
static boolean isProxyClass(Class<?> cl)
返回 cl 是否为一个代理类
代理类的调用过程
生成的代理类到底长什么样子呢?借助下面的工具类,把代理类保存下来再探个究竟
(通过设置环境变量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理类)
保存后的代理类如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;
public final class UserServiceProxy extends Proxy implements UserService {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public UserServiceProxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
// 省略...
}
public final String toString() throws {
// 省略...
}
public final void select() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
// 省略...
}
public final void update() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("proxy.UserService").getMethod("select");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("proxy.UserService").getMethod("update");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从 UserServiceProxy 的代码中我们可以发现:
- UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
- 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
- 类和所有方法都被
public final
修饰,所以代理类只可被使用,不可以再被继承 - 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以
m + 数字
的格式命名 - 调用方法的时候通过
super.h.invoke(this, m1, (Object[])null);
调用,其中的super.h.invoke
实际上是在创建代理的时候传递给Proxy.newProxyInstance
的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑
而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法
JDK动态代理执行方法调用的过程简图如下:
2 CGLIB动态代理(还需要看下)
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
- obj : 被代理的对象(需要增强的对象)
- method : 被拦截的方法(需要增强的方法)
- args : 方法入参
- proxy : 用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
CGLIB 动态代理类使用步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类
代码示例
maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
相关问题:
JDK动态代理与CGLIB动态代理对比
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 无需实现接口,达到代理类无侵入
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能
描述动态代理的几种实现方式?分别说出相应的优缺点
代理可以分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK动态代理" 和 "CGLIB动态代理" 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object
- 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
- 缺点:不同的接口要有不同的代理类实现,会很冗余
JDK 动态代理:
-
为了解决静态代理中,生成大量的代理类造成的冗余;
-
JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
-
jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
-
jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
-
优点:解决了静态代理中冗余的代理实现类问题。
-
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB 代理:
-
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
-
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
-
实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
-
但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
-
同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
-
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
-
缺点:技术实现相对难理解些。