Java手写IOC实现过程

Java手写IOC实现过程

参考:手写IOC实现过程

手写ioc前基础知识

什么是IOC(Inversion of Control 控制反转)

IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

什么是DI(Dependency Injection 依赖注入)

DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

IOC和DI什么关系

oC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

什么是依赖

传统应用程序设计中所说的依赖一般指“类之间的关系”,那先让我们复习一下类之间的关系:

泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;

实现:表示类对接口的实现;

依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识关系”,只在某个特定地方(比如某个方法体内)才有关系。

关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;

聚合:属于是关联的特殊情况,体现部分-整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是一种弱关联;

组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周期,是一种强关联;

Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:

Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和容器之间的依赖关系。

容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是传统类与类之间的“关联”、“聚合”、“组合”关系。

依赖注入的好处

动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;

更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容器注入依赖实现;

更好实践优先使用对象组合,而不是类继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合。

  • 采用对象组合,Bean的功能可能由几个依赖Bean的功能组合而成,其Bean本身可能只提供少许功能或根本无任何功能,全部委托给依赖Bean,对象组合具有动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;

  • 而如果采用类继承,Bean没有依赖Bean,而是采用继承方式添加新功能,,而且功能是在编译时就确定了,不具有动态性,而且采用类继承导致Bean与子Bean之间高度耦合,难以复用。

    增加Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;

    降低Bean之间耦合:由于我们完全采用面向接口编程,在代码中没有直接引用Bean依赖实现,全部引用接口,而且不会出现显示的创建依赖对象代码,而且这些依赖是由容器来注入,很容易替换依赖实现类,从而降低Bean与依赖之间耦合;

    代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码结构更清晰。

从以上我们可以看出,其实依赖注入只是一种装配对象的手段,设计的类结构才是基础,如果设计的类结构不支持依赖注入,Spring IoC容器也注入不了任何东西,从而从根本上说**“如何设计好类结构才是关键,依赖注入只是一种装配对象手段”。**

手写IOC

标记配置分为集中式管理和分散式管理,即xml文件方式和注解方式配置元数据信息,手写ioc我采用注解配置元数据方式来实现ioc的大致运行过程。

定义元数据配置信息

@Component/@Controller/@Repository@Service

扫描什么样的class装载到ioc容器中进行管理。

@Autowired

什么样的对象进行依赖注入。

1
2
3
4
5
6
7
8
9
10
/**
* @Classname Component
* @Description
* @Date 2020/8/6 14:54
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
1
2
3
4
5
6
7
8
9
10
/**
* @Classname Controller
* @Description TODO
* @Date 2020/8/6 14:56
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
1
2
3
4
5
6
7
8
9
10
/**
* @Classname Repository
* @Description TODO
* @Date 2020/8/6 14:58
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
1
2
3
4
5
6
7
8
9
10
/**
* @Classname Service
* @Description TODO
* @Date 2020/8/6 14:57
* @Created by zhangtianci
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
1
2
3
4
5
6
7
8
9
10
11
/**
* @Classname Autowired
* @Description 自动注入注解
* @Date 2020/8/6 14:28
* @Created by zhangtianci
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}

实现ioc容器

核心功能:加载被配置标记的class文件,交给ioc容器管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package org.simplespring.core;

import lombok.extern.slf4j.Slf4j;
import org.simplespring.core.annotation.Component;
import org.simplespring.core.annotation.Controller;
import org.simplespring.core.annotation.Repository;
import org.simplespring.core.annotation.Service;
import org.simplespring.util.ClassUtil;
import org.simplespring.util.ValidationUtil;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* @Classname BeanContainer
* @Description bean容器
* <p>
* BeanContainer应该是单例的 采用内部枚举的方式实现。
*
* 应该拥有的实例方法:
* 1.boolean isLoad() 是否加载
* 2.int getSize() 获取bean个数
* 3.loadBeans(String packageName) 根据包名加载所有被配置标记的class文件/
* 4.获取/删除/增加bean及提供一些便利的方法
*
* @Date 2020/8/6 14:41
* @Created by zhangtianci
*/
@Slf4j
public class BeanContainer {
/**
* 存放所有被配置标记(xml/注解)的class,并new出一个实例对象bean
* 存放在一个map中
*/
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();

/**
* 加载bean的注解列表
*/
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);

/**
* 是否已经加载过bean
*/
private boolean loaded = false;

/**
* 获取bean容器
*
* @return
*/
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}

private enum ContainerHolder {
HOLDER;

private BeanContainer instance;

ContainerHolder() {
instance = new BeanContainer();
}
}

/**
* 是否已经加载过bean
*/
public boolean isLoad(){
return loaded;
}

/**
* 获取容器中bean的个数
*/

public int getSize(){
return beanMap.size();
}

/**
* 加载指定路径下的所有class文件
* 并将所有被配置标记(xml/注解)的class对象和new出一个实例对象放入容器
*/
public synchronized void loadBeans(String packageName){
//判断容器是否已经加载过
if (loaded){
log.warn("Container has loaded!");
return;
}
// 导出所有的class对象
Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
//为空直接return
if (ValidationUtil.isEmpty(classSet)) {
log.warn("extract nothing from packageName" + packageName);
return;
}

classSet.stream().forEach(clazz -> {
for (Class<? extends Annotation> annotationClazz : BEAN_ANNOTATION) {
if (clazz.isAnnotationPresent(annotationClazz)){
//将目标类本身作为键,目标类的实例作为值,放入到beanMap中
beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
}
}

});

loaded = true;
}


/**
* 添加一个class对象及其Bean实例
*
* @param clazz Class对象
* @param bean Bean实例
* @return 原有的Bean实例, 没有则返回null
*/
public Object addBean(Class<?> clazz, Object bean) {
return beanMap.put(clazz, bean);
}

/**
* 移除一个IOC容器管理的对象
*
* @param clazz Class对象
* @return 删除的Bean实例, 没有则返回null
*/
public Object removeBean(Class<?> clazz) {
return beanMap.remove(clazz);
}

/**
* 根据Class对象获取Bean实例
*
* @param clazz Class对象
* @return Bean实例
*/
public Object getBean(Class<?> clazz) {
return beanMap.get(clazz);
}
/**
* 获取容器管理的所有Class对象集合
*
* @return Class集合
*/
public Set<Class<?>> getClasses(){
return beanMap.keySet();
}
/**
* 获取所有Bean集合
*
* @return Bean集合
*/
public Set<Object> getBeans(){
return new HashSet<>( beanMap.values());
}
/**
* 根据注解筛选出Bean的Class集合
*
* @param annotation 注解
* @return Class集合
*/
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
//1.获取beanMap的所有class对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.通过注解筛选被注解标记的class对象,并添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for(Class<?> clazz : keySet){
//类是否有相关的注解标记
if(clazz.isAnnotationPresent(annotation)){
classSet.add(clazz);
}
}
return classSet.size() > 0? classSet: null;
}
/**
* 通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
*
* @param interfaceOrClass 接口Class或者父类Class
* @return Class集合
*/
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
//1.获取beanMap的所有class对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.判断keySet里的元素是否是传入的接口或者类的子类,如果是,就将其添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for(Class<?> clazz : keySet){
//判断keySet里的元素是否是传入的接口或者类的子类
if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
classSet.add(clazz);
}
}
return classSet.size() > 0? classSet: null;
}


}

定义依赖注入器

核心功能:扫描被管理的bean对象,进行依赖注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package org.simplespring.inject;

import lombok.extern.slf4j.Slf4j;
import org.simplespring.core.BeanContainer;
import org.simplespring.inject.annotation.Autowired;
import org.simplespring.util.ClassUtil;
import org.simplespring.util.ValidationUtil;
import java.lang.reflect.Field;
import java.util.Set;

/**
* @Classname DependencyInjector
* @Description 依赖注入器
* @Date 2020/8/6 14:38
* @Created by zhangtianci
*/
@Slf4j
public class DependencyInjector {
/**
* 拥有一个Bean容器
*/
private BeanContainer beanContainer;
public DependencyInjector(){
beanContainer = BeanContainer.getInstance();
}


/**
* 执行依赖注入
*
* 1.遍历Bean容器中所有的Class对象
* 2.遍历Class对象的所有成员变量
* 3.找出被Autowired标记的成员变量
* 4.获取这些成员变量的类型
* 5.获取这些成员变量的类型在容器里对应的实例
* 6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
*/
public void doIoc(){
if(ValidationUtil.isEmpty(beanContainer.getClasses())){
log.warn("empty classset in BeanContainer");
return;
}
//1.遍历Bean容器中所有的Class对象
for(Class<?> clazz : beanContainer.getClasses()){
//2.遍历Class对象的所有成员变量
Field[] fields = clazz.getDeclaredFields();
if (ValidationUtil.isEmpty(fields)){
continue;
}
for(Field field : fields){
//3.找出被Autowired标记的成员变量
if(field.isAnnotationPresent(Autowired.class)){
Autowired autowired = field.getAnnotation(Autowired.class);
String autowiredValue = autowired.value();
//4.获取这些成员变量的类型
Class<?> fieldClass = field.getType();
//5.获取这些成员变量的类型在容器里对应的实例
Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
if(fieldValue == null){
throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue);
} else {
//6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
Object targetBean = beanContainer.getBean(clazz);
ClassUtil.setField(field, targetBean, fieldValue, true);
}
}
}
}


}
/**
* 根据Class在beanContainer里获取其实例或者实现类
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
if (fieldValue != null){
return fieldValue;
} else {
Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue);
if(implementedClass != null){
return beanContainer.getBean(implementedClass);
} else {
return null;
}
}
}
/**
* 获取接口的实现类
*/
private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) {
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
if(!ValidationUtil.isEmpty(classSet)){
if(ValidationUtil.isEmpty(autowiredValue)){
if(classSet.size() == 1){
return classSet.iterator().next();
} else {
//如果多于两个实现类且用户未指定其中一个实现类,则抛出异常
throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one");
}
} else {
for(Class<?> clazz : classSet){//别名采用clazz.getSimpleName()简单实现
if(autowiredValue.equals(clazz.getSimpleName())){
return clazz;
}
}
}
}
return null;
}


}

工具包

作为框架 类加载/反射/校验等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package org.simplespring.util;

import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* @Classname ClassUtil
* @Description
* @Date 2020/8/8 15:20
* @Created by zhangtianci
*/
@Slf4j
public class ClassUtil {

public static final String FILE_PROTOCOL = "file";

/**
* 获取类加载器
* 因为项目部署在tomcat等一些web容器中
* 这些web容器加载部署在里面的apps 所以需要自定义classLoader去加载class文件
* 当我们写框架需要去通过类加载器去加载class文件时 就需要通过Thread.currentThread().getContextClassLoader()
* 拿到tomcat的自定义的classLoader去加载项目里面的class文件
* 详情 参考我写的classLoader加载相关文章
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}

/**
* 通过包名加载class
*
* 点进去Class.forName()方法进去看看
*
* @CallerSensitive
* public static Class<?> forName(String className)
* throws ClassNotFoundException {
* Class<?> caller = Reflection.getCallerClass();
* return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
* }
*
* 获取到调用这个方法的class对象 然后用这个class对象的classLoader去加载这个class文件
* 所以归根结底是 拿到tomcat的自定义的classLoader去加载的class文件
* 所以没问题
* @param className class全名=package + 类名
* @return
*/
public static Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
log.error("loadClass failed!",e);
throw new RuntimeException(e);
}

}


/**
* 通过一个class对象实例化一个实例对象(通过默认构造器)
* @param clazz
* @param accessible
* @param <T>
* @return
*/
public static <T> T newInstance(Class<?> clazz, boolean accessible){
try {
//clazz.getDeclaredConstructor() 获取指定参数类表的构造器
//这里获取默认构造器
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(accessible);
return (T)constructor.newInstance();
} catch (Exception e) {
log.error("new instance failed!");
throw new RuntimeException(e);
}
}

/**
* 设置类的属性值
*
* @param field 成员变量
* @param target 类实例
* @param value 成员变量的值
* @param accessible 是否允许设置私有属性
*/
public static void setField(Field field, Object target, Object value, boolean accessible){
field.setAccessible(accessible);
try {
field.set(target, value);
} catch (IllegalAccessException e) {
log.error("setField error", e);
throw new RuntimeException(e);
}
}


/**
* 加载包路径下的所有的class文件
* 将class对象放进set集合中
*
* 1.获取classLoader加载器
* 2.递归加载路径下的所有class文件
* @param packageName
* @return
*/
public static Set<Class<?>> extractPackageClass(String packageName){

//1.获取classLoader加载器
ClassLoader classLoader = getClassLoader();
//2.获取资源文件的的url
URL url = classLoader.getResource(packageName.replace(".","/"));
if (url == null){
log.warn("unable to retrieve anything from package: " + packageName);
return null;
}

//3.依据不同的资源类型,采用不同的方式获取资源的集合
Set<Class<?>> classSet = null;
//过滤出文件类型的资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
classSet = new HashSet<Class<?>>();
File packageDirectory = new File(url.getPath());
extractClassFile(classSet, packageDirectory, packageName);
}
//TODO 此处可以加入针对其他类型资源的处理

return null;
}

/**
* 递归加载包路径下的class文件
* @param classSet
* @param packageDirectory
* @param packageName
*/
private static void extractClassFile(Set<Class<?>> classSet, File packageDirectory, String packageName) {

if(!packageDirectory.isDirectory()){
return;
}
//如果是一个文件夹,则调用其listFiles方法获取文件夹下的文件或文件夹
File[] files = packageDirectory.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.isDirectory()){
return true;
} else{
//获取文件的绝对值路径
String absoluteFilePath = pathname.getAbsolutePath();
if(absoluteFilePath.endsWith(".class")){
//若是class文件,则直接加载
addToClassSet(absoluteFilePath);
}
}
return false;
}


//根据class文件的绝对值路径,获取并生成class对象,并放入classSet中
private void addToClassSet(String absoluteFilePath) {
//1.从class文件的绝对值路径里提取出包含了package的类名
//如/Users/zhangtc/springframework/simple-spring/target/classes/com/zhangtianci/entity/dto/MainPageInfoDTO.class
//需要弄成com.zhangtianci.entity.dto.MainPageInfoDTO
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
className = className.substring(0, className.lastIndexOf("."));
//2.通过反射机制获取对应的Class对象并加入到classSet里
Class targetClass = loadClass(className);
classSet.add(targetClass);
}
});

if(files == null){
return;
}
Arrays.stream(files).forEach( file -> {
extractClassFile(classSet,file,packageName);
});


}


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package org.simplespring.util;

import java.util.Collection;
import java.util.Map;

public class ValidationUtil {
/**
* String是否为null或""
*
* @param obj String
* @return 是否为空
*/
public static boolean isEmpty(String obj) {
return (obj == null || "".equals(obj));
}

/**
* Array是否为null或者size为0
*
* @param obj Array
* @return 是否为空
*/
public static boolean isEmpty(Object[] obj) {
return obj == null || obj.length == 0;
}
/**
* Collection是否为null或size为0
*
* @param obj Collection
* @return 是否为空
*/
public static boolean isEmpty(Collection<?> obj){
return obj == null || obj.isEmpty();
}
/**
* Map是否为null或size为0
*
* @param obj Map
* @return 是否为空
*/
public static boolean isEmpty(Map<?, ?> obj) {
return obj == null || obj.isEmpty();
}
}