【Eclipse插件开发】Java基本类加载原理 VS 插件类加载原理
标题说明了一切,我想说的就是如果你在做Eclipse插件开发,想真正搞清楚Eclipse插件类加载,那么Java基本类加载原理是基础,无法逾越!!!我会举一个小例子来简单说明一下
【情景】
例如我们在底层模块插件(就叫做Host)中做了一个解析器实现(什么解析器就别管了),对应类型为IParser(com.my.host.parser.IParser),简单定义如下:
基本的原则告诉我们:如果一个实例的创建过程较为负责,则因该把这种实例的创建和实例的使用解耦合。于是,我们在Host插件中提供了一个如下的工厂类:
同时我们在Host插件中,也提供了一个默认的IParser实现,就叫做DefaultParser吧。那到这里我们的Host插件共提供了如下类型:
com.my.host.parser.IParser 解析器接口定义
com.my.host.parser.ParserFactory 解析器工厂
com.my.host.parser.impl.DefaultParser 解析器接口默认实现
下面我们在Host插件的基础之上建立了上层功能插件(就叫做Extension插件吧,Extension插件依赖于Host插件),提供了解析器实现,对应类型为com.my.extension.parser.MyParserImpl。我们在Extension插件中想使用这个实现,假设有如下几种中创建方式:
第一种:直接创建实例,偏不用你Host中的工厂。 俗了点,但是没问题。
第二种,直接使用Class.forName(),还是不用你Host中的工厂。也是俗了点,但也没问题,为什么没问题,可能就没有想清楚了~_~
第三种:用一把工厂 ---》》 ClassNotFoundException
大致错误原因:
这个时候如果你只基本熟悉Eclipse的插件类加载机制(会去找所依赖的插件中的类型...),可能一时半会想不清楚。 Java的类加载基本原理中有一条:Class.forName()调用方式加载类型所默认使用的类加载器实例是加载当前类型的类加载器。对于在Extension插件Customer中调用Class.forName()使用的是加载Customer类型的类加载器,也就是Extension插件的唯一类加载器;对于在Host插件中的ParserFactory中调用Class.forName()则使用的加载ParserFactory类型的类加载器实例,也就是Host插件的唯一类加载器。你想让Host插件类加载器去加载一个在Extension插件中定义的类型,注意Extension插件是依赖于Host插件的啊,根据Eclipse插件类记载规则,这种加载请求无法从Host插件类加载器委托到Extension类加载器(说明:使用扩展点机制可以,extension registry会帮你委托这一个请求,找到对应的BundleHost...)。如果你想让Extension插件的类加载器去加载Host中定义的切export出来的类型,Extension插件的类加载器会把这个请求合理的委托给Host插件类加载器。
第四种:用一把工厂,指定类加载器。由于 com.my.extension.parser.MyParserImpl本身就和Customer定义在同一插件中,所以公用同一插件类加载器实例,两者对于Extension插件来说都是local class,加载肯定没问题。
以上是举了一个小例子,仅仅是为了说明一个道理: 掌握Java类加载基本原理是掌握Eclipse插件类加载原理的基础,不可逾越!!!如果让你在一个插件应用中写一个较为负责的自定义类加载器,但靠背一点Eclipse类加载的规则,那更不够了...
Java类加载基本原理还有很多,我博客中有随笔(老早之前写的,土了点,头一段时间贴到了博客上了)。
【情景】
例如我们在底层模块插件(就叫做Host)中做了一个解析器实现(什么解析器就别管了),对应类型为IParser(com.my.host.parser.IParser),简单定义如下:
1
public
interface
IParser {
2 public Object parse(Object input) throws CoreException;
3 }
2 public Object parse(Object input) throws CoreException;
3 }
基本的原则告诉我们:如果一个实例的创建过程较为负责,则因该把这种实例的创建和实例的使用解耦合。于是,我们在Host插件中提供了一个如下的工厂类:
1
public
class
ParserFactory {
2 /**
3 * create IParser instance
4 *
5 * @param clazz qualified parser type name
6 * @return
7 * @throws CoreException
8 */
9 public static IParser createParser(String clazz) throws CoreException{
10 try {
11 Object object = Class.forName(clazz).newInstance();
12 if (object instanceof IParser)
13 return (IParser)object;
14
15 return null ;
16 } catch (Exception e) {
17 throw new CoreException( new Status(IStatus.ERROR, " host " , 101 , " 创建工厂失败: " + clazz, e));
18 }
19 }
20
21 /**
22 * create IParser instance with corresponding class loader
23 *
24 * @param clazz qualified parser type name
25 * @param classLoader corresponding class loader
26 * @return
27 * @throws CoreException
28 */
29 public static IParser createParser(String clazz, ClassLoader classLoader) throws CoreException{
30 try {
31 Object object = classLoader.loadClass(clazz);
32 if (object instanceof IParser)
33 return (IParser)object;
34
35 return null ;
36 } catch (Exception e) {
37 throw new CoreException( new Status(IStatus.ERROR, " host " , 101 , " 创建工厂失败: " + clazz, e));
38 }
39 }
40 }
41
这里的简单工厂中创建方法返回的是IParser接口类型,目的就是为了保证可以创建子类型的灵活性,同时确保对外提供的接口是一致的,有助于客户端基于接口编程(说的有点上纲上线了~_~)
2 /**
3 * create IParser instance
4 *
5 * @param clazz qualified parser type name
6 * @return
7 * @throws CoreException
8 */
9 public static IParser createParser(String clazz) throws CoreException{
10 try {
11 Object object = Class.forName(clazz).newInstance();
12 if (object instanceof IParser)
13 return (IParser)object;
14
15 return null ;
16 } catch (Exception e) {
17 throw new CoreException( new Status(IStatus.ERROR, " host " , 101 , " 创建工厂失败: " + clazz, e));
18 }
19 }
20
21 /**
22 * create IParser instance with corresponding class loader
23 *
24 * @param clazz qualified parser type name
25 * @param classLoader corresponding class loader
26 * @return
27 * @throws CoreException
28 */
29 public static IParser createParser(String clazz, ClassLoader classLoader) throws CoreException{
30 try {
31 Object object = classLoader.loadClass(clazz);
32 if (object instanceof IParser)
33 return (IParser)object;
34
35 return null ;
36 } catch (Exception e) {
37 throw new CoreException( new Status(IStatus.ERROR, " host " , 101 , " 创建工厂失败: " + clazz, e));
38 }
39 }
40 }
41
同时我们在Host插件中,也提供了一个默认的IParser实现,就叫做DefaultParser吧。那到这里我们的Host插件共提供了如下类型:
com.my.host.parser.IParser 解析器接口定义
com.my.host.parser.ParserFactory 解析器工厂
com.my.host.parser.impl.DefaultParser 解析器接口默认实现
下面我们在Host插件的基础之上建立了上层功能插件(就叫做Extension插件吧,Extension插件依赖于Host插件),提供了解析器实现,对应类型为com.my.extension.parser.MyParserImpl。我们在Extension插件中想使用这个实现,假设有如下几种中创建方式:
第一种:直接创建实例,偏不用你Host中的工厂。 俗了点,但是没问题。
1
package
com.my.extension.model;
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 // 直接创建实例的方式
6 IParser parser = new MyParserImpl();
7
8 Object model = parser.parse(input);
9 // TODO 拿到模型之后干其他活
10 }
11 }
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 // 直接创建实例的方式
6 IParser parser = new MyParserImpl();
7
8 Object model = parser.parse(input);
9 // TODO 拿到模型之后干其他活
10 }
11 }
第二种,直接使用Class.forName(),还是不用你Host中的工厂。也是俗了点,但也没问题,为什么没问题,可能就没有想清楚了~_~
1
package
com.my.extension.model;
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 try {
6 // 使用默认的Class.forName()的方式
7 IParser parser = (IParser)Class.forName( " com.my.extension.parser.MyParserImpl " ).newInstance();
8
9 Object model = parser.parse(input);
10 // TODO 拿到模型之后干其他活
11 } catch (Exception e) {
12 // TODO: handle exception
13 }
14
15 }
16 }
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 try {
6 // 使用默认的Class.forName()的方式
7 IParser parser = (IParser)Class.forName( " com.my.extension.parser.MyParserImpl " ).newInstance();
8
9 Object model = parser.parse(input);
10 // TODO 拿到模型之后干其他活
11 } catch (Exception e) {
12 // TODO: handle exception
13 }
14
15 }
16 }
第三种:用一把工厂 ---》》 ClassNotFoundException
1
package
com.my.extension.model;
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 // 使用工厂,不指定类加载器
6 IParser parser = ParserFactory.createParser( " com.my.extension.parser.MyParserImpl " );
7
8 Object model = parser.parse(input);
9 // TODO 拿到模型之后干其他活
10 }
11 }
疑惑了:第二种使用方式为什么没问题,第三种怎么就有问题了?代码实现中都是Class.forName(
"
"
).newInstance();调用啊???
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 // 使用工厂,不指定类加载器
6 IParser parser = ParserFactory.createParser( " com.my.extension.parser.MyParserImpl " );
7
8 Object model = parser.parse(input);
9 // TODO 拿到模型之后干其他活
10 }
11 }
大致错误原因:
这个时候如果你只基本熟悉Eclipse的插件类加载机制(会去找所依赖的插件中的类型...),可能一时半会想不清楚。 Java的类加载基本原理中有一条:Class.forName()调用方式加载类型所默认使用的类加载器实例是加载当前类型的类加载器。对于在Extension插件Customer中调用Class.forName()使用的是加载Customer类型的类加载器,也就是Extension插件的唯一类加载器;对于在Host插件中的ParserFactory中调用Class.forName()则使用的加载ParserFactory类型的类加载器实例,也就是Host插件的唯一类加载器。你想让Host插件类加载器去加载一个在Extension插件中定义的类型,注意Extension插件是依赖于Host插件的啊,根据Eclipse插件类记载规则,这种加载请求无法从Host插件类加载器委托到Extension类加载器(说明:使用扩展点机制可以,extension registry会帮你委托这一个请求,找到对应的BundleHost...)。如果你想让Extension插件的类加载器去加载Host中定义的切export出来的类型,Extension插件的类加载器会把这个请求合理的委托给Host插件类加载器。
第四种:用一把工厂,指定类加载器。由于 com.my.extension.parser.MyParserImpl本身就和Customer定义在同一插件中,所以公用同一插件类加载器实例,两者对于Extension插件来说都是local class,加载肯定没问题。
1
package
com.my.extension.model;
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 // 使用工厂,指定类加载器
6 IParser parser = ParserFactory.createParser( " com.my.extension.parser.MyParserImpl " , this .getClass().getClassLoader());
7
8 Object model = parser.parse(input);
9 // TODO 拿到模型之后干其他活
10 }
11 }
2
3 public class Customer {
4 public void doOperation(Object input) throws CoreException{
5 // 使用工厂,指定类加载器
6 IParser parser = ParserFactory.createParser( " com.my.extension.parser.MyParserImpl " , this .getClass().getClassLoader());
7
8 Object model = parser.parse(input);
9 // TODO 拿到模型之后干其他活
10 }
11 }
以上是举了一个小例子,仅仅是为了说明一个道理: 掌握Java类加载基本原理是掌握Eclipse插件类加载原理的基础,不可逾越!!!如果让你在一个插件应用中写一个较为负责的自定义类加载器,但靠背一点Eclipse类加载的规则,那更不够了...
Java类加载基本原理还有很多,我博客中有随笔(老早之前写的,土了点,头一段时间贴到了博客上了)。
本博客中的所有文章、随笔除了标题中含有引用或者转载字样的,其他均为原创。转载请注明出处,谢谢!