如何设计并实现一个ioc容器(转载)

来源:互联网 发布:淘宝阿迪正品店铺 编辑:程序博客网 时间:2024/05/09 12:34

转载地址

IOC的概念

什么是IOC?

IoC(Inversion of Control),意为控制反转,不是什么技术,而是一种设计思想。Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制

如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

下面举个例子说明说明是IOC:

假设我们要设计一个Girl和一个Boy类,其中Girl有kiss方法,即Girl想要Kiss一个Boy。那么,我们的问题是,Girl如何能够认识这个Boy?

在我们中国,常见的MM与GG的认识方式有以下几种:

  1. 青梅竹马
  2. 亲友介绍
  3. 父母包办

那么哪一种才是最好呢?
  
1. 青梅竹马:Girl从小就知道自己的Boy。

public class Girl {     void kiss(){     Boy boy = new Boy();   } } 

然而从开始就创建的Boy缺点就是无法在更换。并且要负责Boy的整个生命周期。如果我们的Girl想要换一个怎么办?(笔者严重不支持Girl经常更换Boy)

  1. 亲友介绍:由中间人负责提供Boy来见面
public class Girl {   void kiss(){     Boy boy = BoyFactory.createBoy();     } }

亲友介绍,固然是好。如果不满意,尽管另外换一个好了。但是,亲友BoyFactory经常是以Singleton的形式出现,不然就是,存在于Globals,无处不在,无处不能。实在是太繁琐了一点,不够灵活。我为什么一定要这个亲友掺和进来呢?为什么一定要付给她介绍费呢?万一最好的朋友爱上了我的男朋友呢?

  1. 父母包办:一切交给父母,自己不用费吹灰之力,只需要等着Kiss就好了。
public class Girl {    void kiss(Boy boy){     // kiss boy    boy.kiss();   } }

Well,这是对Girl最好的方法,只要想办法贿赂了Girl的父母,并把Boy交给他。那么我们就可以轻松的和Girl来Kiss了。看来几千年传统的父母之命还真是有用哦。至少Boy和Girl不用自己瞎忙乎了。

这就是IOC,将对象的创建和获取提取到外部。由外部容器提供需要的组件。

IoC能做什么

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

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

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

IoC和DI

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

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;

  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

  • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

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

对于Spring Ioc这个核心概念,我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。
理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在框架中堆积木而已,下一节来看看Spring是怎么用的

Spring中怎么用

我们在Spring中是这样获取对象的:

public static void main(String[] args) {       ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");       Lol lol = (Lol) context.getBean("lol");       lol.gank(); }

一起看看Spring如何让它生效呢,在 applicationContext.xml 配置文件中是酱紫的:

<bean id="lol" class="com.biezhi.test.Lol">    <property name="name" value="剑圣" />   </bean>  

Person 类是这样的:

public class Lol {    private String name;    public Lol() {    }    public void gank(){        System.out.println(this.name + "在gank!!");    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

上面的代码运行结果自然是 剑圣在gank!!

Spring更高级的用法,在3.0版本之后有了基于Annotation的注入实现,为毛每次都要配置 Xml 看到都蛋疼。。

首先还是要在 xml 中配置启用注解方式

<context:annotation-config/>  

这样就能使用注解驱动依赖注入了,下面是一个使用场景

public class Lol {    @Autowired    private DuangService duangService ;    public void buyDuang(String name, int money) {        duangService.buy(name, money);    }}
@Service("duangService")public class DuangService {    public void buy(String name, int money){        if(money > 0){            System.out.println(name + "买了" + money + "毛钱的特效,装逼成功!");        } else{            System.out.println(name + "没钱还想装逼,真是匪夷所思");        }    }}

这只是一个简单的例子,剑圣打野的时候想要买5毛钱的三杀特效,嗯。。虽然不符合逻辑

此时 DuangService 已经注入到 Lol 对象中,运行代码的结果(这里是例子,代码不能运行的)就是:

德玛买了5毛钱的特效,装逼成功!

好了,深入的不说了,我们不是学spring的,只是知道一下ioc在spring中高大上的形象,接下来步入正轨,开始设计一个IOC容器

设计一个IOC

我们要自己设计一个IOC,那么目标是什么呢?
我们的IOC容器要可以存储对象,还要有注解注入的功能即可。

Java语言允许通过程序化的方式间接对Class进行操作,Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数、属性和方法等。Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这就为使用程序化方式操作Class对象开辟了途径。

我们将从一个简单例子开始探访Java反射机制的征程,下面的Hero类拥有一个构造函数、五个方法以及两个属性,如代码清单所示:

/** * LOL英雄 */public class Hero {    // 英雄名称    private String name;    // 装备名称    private String outfit;    public Hero() {    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getOutfit() {        return outfit;    }    public void setOutfit(String outfit) {        this.outfit = outfit;    }    public void say(){        System.out.println(name + "购买了" + outfit);    }}

测试代码:

public class Test {    public static void main(String[] args) throws Exception {        //1. 通过类装载器获取Hero类对象          ClassLoader loader = Thread.currentThread().getContextClassLoader();           Class<?> clazz = loader.loadClass("com.biezhi.ioc.Hero");           //2. 获取类的默认构造器对象并通过它实例化Hero          Constructor<?> cons = clazz.getDeclaredConstructor((Class[])null);           Hero hero = (Hero)cons.newInstance();          //3. 通过反射方法设置属性          Method setBrand = clazz.getMethod("setName", String.class);        setBrand.invoke(hero, "小鱼人");        Method setColor = clazz.getMethod("setOutfit", String.class);        setColor.invoke(hero, "爆裂魔杖");        // 4. 运行方法        hero.say();    }}

输出了: 小鱼人购买了爆裂魔杖

这说明我们完全可以通过编程方式调用Class的各项功能,这和直接通过构造函数和方法调用类功能的效果是一致的,只不过前者是间接调用,后者是直接调用罢了。

在Test中,使用了几个重要的反射类,分别是ClassLoader、Class、Constructor和Method,通过这些反射类就可以间接调用目标Class的各项功能了。在①处,我们获取当前线程的ClassLoader,然后通过指定的全限定类"com.biezhi.ioc.Hero"装载Hero类对应的反射实例。在②处,我们通过Hero的反射类对象获取Hero的构造函数对象cons,通过构造函数对象的newInstrance()方法实例化Hero对象,其效果等同于new Hero()。在③处,我们又通过Hero的反射类对象的getMethod(String methodName,Class paramClass)获取属性的Setter方法对象,第一个参数是目标Class的方法名;第二个参数是方法入参的对象类型。获取方法反射对象后,即可通过invoke(Object obj,Object param)方法调用目标类的方法,该方法的第一个参数是操作的目标类对象实例;第二个参数是目标方法的入参。

第三步是通过反射方法操控目标类的元信息,如果我们将这些信息以一个配置文件的方式提供,就可以使用Java语言的反射功能编写一段通用的代码对类似于Hero的类进行实例化及功能调用操作了。

简单的例子说完了,我们开始设计一个自己的IOC容器,做出这个东东后再来看那些复杂的原理。

首先设计接口,一个IOC容器中最核心的当属容器接口,来一个Container。

那么容器里应该有什么呢,我想它至少要有存储和移除一个对象的能力,其次可以含括更多的获取和注册对象的方法。

/** * IOC容器 * @author biezhi * */public interface Container {    /**     * 根据Class获取Bean     * @param clazz     * @return     */    public <T> T getBean(Class<T> clazz);    /**     * 根据名称获取Bean     * @param name     * @return     */    public <T> T getBeanByName(String name);    /**     * 注册一个Bean到容器中     * @param object     */    public Object registerBean(Object bean);    /**     * 注册一个Class到容器中     * @param clazz     */    public Object registerBean(Class<?> clazz);    /**     * 注册一个带名称的Bean到容器中     * @param name     * @param bean     */    public Object registerBean(String name, Object bean);    /**     * 删除一个bean     * @param clazz     */    public void remove(Class<?> clazz);    /**     * 根据名称删除一个bean     * @param name     */    public void removeByName(String name);    /**     * @return  返回所有bean对象名称     */    public Set<String> getBeanNames();    /**     * 初始化装配     */    public void initWired();}

那么我写一个简单的实现代码:

/** * 容器简单实现 * @author biezhi */@SuppressWarnings("unchecked")public class SampleContainer implements Container {    /**     * 保存所有bean对象,格式为 com.xxx.Person : @52x2xa     */    private Map<String, Object> beans;    /**     * 存储bean和name的关系     */    private Map<String, String> beanKeys;    public SampleContainer() {        this.beans = new ConcurrentHashMap<String, Object>();        this.beanKeys = new ConcurrentHashMap<String, String>();    }    @Override    public <T> T getBean(Class<T> clazz) {        String name = clazz.getName();        Object object = beans.get(name);        if(null != object){            return (T) object;        }        return null;    }    @Override    public <T> T getBeanByName(String name) {        String className = beankeys.get(name);        Object obj = beans.get(className);        if(null != object){            return (T) object;        }        return null;    }    @Override    public Object registerBean(Object bean) {        String name = bean.getClass().getName();        beanKeys.put(name, name);        beans.put(name, bean);        return bean;    }    @Override    public Object registerBean(Class<?> clazz) {        String name = clazz.getName();        beanKeys.put(name, name);        Object bean = ReflectUtil.newInstance(clazz);        beans.put(name, bean);        return bean;    }    @Override    public Object registerBean(String name, Object bean) {        String className = bean.getClass().getName();        beanKeys.put(name, className);        beans.put(className, bean);        return bean;    }    @Override    public Set<String> getBeanNames() {        return beanKeys.keySet();    }    @Override    public void remove(Class<?> clazz) {        String className = clazz.getName();        if(null != className && !className.equals("")){            beanKeys.remove(className);            beans.remove(className);        }    }    @Override    public void removeByName(String name) {        String className = beanKeys.get(name);        if(null != className && !className.equals("")){            beanKeys.remove(name);            beans.remove(className);        }    }    @Override    public void initWired() {        Iterator<Entry<String, Object>> it = beans.entrySet().iterator();        while (it.hasNext()) {            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();            Object object = entry.getValue();            injection(object);        }    }    /**     * 注入对象     * @param object     */    public void injection(Object object) {        // 所有字段        try {            Field[] fields = object.getClass().getDeclaredFields();            for (Field field : fields) {                // 需要注入的字段                AutoWired autoWired = field.getAnnotation(autoWired.class);                if (null != autoWired) {                    // 要注入的字段                    Object autoWiredField = null;                    String name = autoWired.name();                    if(!name.equals("")){                        String className = beanKeys.get(name);                        if(null != className && !className.equals("")){                            autoWiredField = beans.get(className);                        }                        if (null == autoWiredField) {                            throw new RuntimeException("Unable to load " + name);                        }                    } else {                        if(autoWired.value() == Class.class){                            autoWiredField = recursiveAssembly(field.getType());                        } else {                            // 指定装配的类                            autoWiredField = this.getBean(autoWired.value());                            if (null == autoWiredField) {                                autoWiredField = recursiveAssembly(autoWired.value());                            }                        }                    }                    if (null == autoWiredField) {                        throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());                    }                    boolean accessible = field.isAccessible();                    field.setAccessible(true);                    field.set(object, autoWiredField);                    field.setAccessible(accessible);                }            }        } catch (SecurityException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }    private Object recursiveAssembly(Class<?> clazz){        if(null != clazz){            return this.registerBean(clazz);        }        return null;    }}

这里将所有Bean的名称存储在 beanKeys 这个map中,将所有的对象存储在 beans 中,用 beanKeys 维护名称和对象的关系。

在装配的时候步骤如下:

  1. 判断是否使用了自定义命名的对象(是:根据name查找bean)
  2. 判断是否使用了Class类型Bean(是:根据Class查找Bean,如果查找不到则创建一个无参构造函数的Bean)

下面是一个测试:

public class IocTest {    private static Container container = new SampleContainer();    public static void baseTest(){        container.registerBean(Lol.class);        // 初始化注入        container.initWired();        Lol lol = container.getBean(Lol.class);        lol.work();    }    public static void iocClassTest(){        container.registerBean(Lol2.class);        // 初始化注入        container.initWired();        Lol2 lol = container.getBean(Lol2.class);        lol.work();    }    public static void iocNameTest(){        container.registerBean("face", new FaceService2());        container.registerBean(Lol3.class);        // 初始化注入        container.initWired();        Lol3 lol = container.getBean(Lol3.class);        lol.work();    }    public static void main(String[] args) {        baseTest();        //iocClassTest();        //iocNameTest();    }}

输出结果:

剑圣买了5毛钱特效,装逼成功!

代码出处

原理分析

类装载器ClassLoader

类装载器工作机制

类装载器就是寻找类的节码文件并构造出类在JVM内部表示对象的组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:

[1.]装载:查找和导入Class文件;
[2.]链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的:
[2.1]校验:检查载入Class文件数据的正确性;
[2.2]准备:给类的静态变量分配存储空间;
[2.3]解析:将符号引用转成直接引用;
[3.]初始化:对类的静态变量、静态代码块执行初始化工作。

类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的Java运行时系统组件,它负责在运行时查找和装入Class字节码文件。JVM在运行时会产生三个ClassLoader:根装载器、ExtClassLoader(扩展类装载器)和AppClassLoader(系统类装载器)。其中,根装载器不是ClassLoader的子类,它使用C++编写,因此我们在Java中看不到它,根装载器负责装载JRE的核心类库,如JRE目标下的rt.jar、charsets.jar等。ExtClassLoader和AppClassLoader都是ClassLoader的子类。其中ExtClassLoader负责装载JRE扩展目录ext中的JAR类包;AppClassLoader负责装载Classpath路径下的类包。

这三个类装载器之间存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下,使用AppClassLoader装载应用程序的类,我们可以做一个实验:

public class ClassLoaderTest {    public static void main(String[] args) {        ClassLoader loader = Thread.currentThread().getContextClassLoader();        System.out.println("current loader:"+loader);        System.out.println("parent loader:"+loader.getParent());        System.out.println("grandparent loader:"+loader.getParent(). getParent());    }}

运行以上代码,在控制台上将打出以下信息:

current loader:sun.misc.Launcher$AppClassLoader@131f71a parent loader:sun.misc.Launcher$ExtClassLoader@15601ea //①根装载器在Java中访问不到,所以返回null grandparent loader:null

通过以上的输出信息,我们知道当前的ClassLoader是AppClassLoader,父ClassLoader是ExtClassLoader,祖父ClassLoader是根类装载器,因为在Java中无法获得它的句柄,所以仅返回null。

JVM装载类时使用“全盘负责委托机制”,“全盘负责”是指当一个ClassLoader装载一个类的时,除非显式地使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入;“委托机制”是指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全角度考虑的,试想如果有人编写了一个恶意的基础类(如java.lang.String)并装载到JVM中将会引起多么可怕的后果。但是由于有了“全盘负责委托机制”,java.lang.String永远是由根装载器来装载的,这样就避免了上述事件的发生。

ClassLoader重要方法

在Java中,ClassLoader是一个抽象类,位于java.lang包中。下面对该类的一些重要接口方法进行介绍:

  • Class loadClass(String name)
    name参数指定类装载器需要装载类的名字,必须使用全限定类名,如com.baobaotao. beans.Car。该方法有一个重载方法loadClass(String name ,boolean resolve),resolve参数告诉类装载器是否需要解析该类。在初始化类之前,应考虑进行类解析的工作,但并不是所有的类都需要解析,如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析。
  • Class defineClass(String name, byte[] b, int off, int len)
    将类文件的字节数组转换成JVM内部的java.lang.Class对象。字节数组可以从本地文件系统、远程网络获取。name为字节数组对应的全限定类名。
  • Class findSystemClass(String name)
    从本地文件系统载入Class文件,如果本地文件系统不存在该Class文件,将抛出ClassNotFoundException异常。该方法是JVM默认使用的装载机制。
  • Class findLoadedClass(String name)
    调用该方法来查看ClassLoader是否已装入某个类。如果已装入,那么返回java.lang.Class对象,否则返回null。如果强行装载已存在的类,将会抛出链接错误。
  • ClassLoader getParent()
    获取类装载器的父装载器,除根装载器外,所有的类装载器都有且仅有一个父装载器,ExtClassLoader的父装载器是根装载器,因为根装载器非Java编写,所以无法获得,将返回null。

除JVM默认的三个ClassLoader以外,可以编写自己的第三方类装载器,以实现一些特殊的需求。类文件被装载并解析后,在JVM内将拥有一个对应的java.lang.Class类描述对象,该类的实例都拥有指向这个类描述对象的引用,而类描述对象又拥有指向关联ClassLoader的引用,如图所示。

每一个类在JVM中都拥有一个对应的java.lang.Class对象,它提供了类结构信息的描述。数组、枚举、注解以及基本Java类型(如int、double等),甚至void都拥有对应的Class对象。Class没有public的构造方法。Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动构造的。

Java反射机制

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义,下面是最主要的三个反射类:

  • Constructor:类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。在JDK5.0中,还可以通过getConstructor(Class… parameterTypes)获取拥有特定入参的构造函数反射对象。Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。在JDK5.0中该方法演化为更为灵活的形式:newInstance (Object… initargs)。
  • Method:类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。在JDK5.0中可以通过getDeclaredMethod(String name, Class… parameterTypes)获取特定签名的方法,name为方法名;Class…为方法入参类型列表。Method最主要的方法是invoke(Object obj, Object[] args),obj表示操作的目标对象;args为方法入参,代码清单3 10③处演示了这个反射类的使用方法。在JDK 5.0中,该方法的形式调整为invoke(Object obj, Object… args)。此外,Method还有很多用于获取类方法更多信息的方法:
    1)Class getReturnType():获取方法的返回值类型;
    2)Class[] getParameterTypes():获取方法的入参类型数组;
    3)Class[] getExceptionTypes():获取方法的异常类型数组;
    4)Annotation[][] getParameterAnnotations():获取方法的注解信息,JDK 5.0中的新方法;
  • Field:类的成员变量的反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)则可获取某个特定名称的成员变量反射对象。Field类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,用户可以使用Field类中提供的带类型名的值设置方法,如setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等。

此外,Java还为包提供了Package反射类,在JDK 5.0中还为注解提供了AnnotatedElement反射类。总之,Java的反射体系保证了可以通过程序化的方式访问目标类中所有的元素,对于private或protected的成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用,请看下面的例子:

/** * LOL英雄 */public class PrivateHero {    // 英雄名称    private String name;    // 装备名称    private String outfit;    public PrivateHero() {    }    public void say(){        System.out.println(name + "购买了" + outfit);    }}
public class Test2 {    public static void main(String[] args) throws Exception {        ClassLoader loader = Thread.currentThread().getContextClassLoader();           Class<?> clazz = loader.loadClass("com.biezhi.ioc.PrivateHero");           PrivateHero hero = (PrivateHero) clazz.newInstance();        Field name = clazz.getDeclaredField("name");        // 取消Java语言访问检查以访问private变量        name.setAccessible(true);        name.set(hero, "德玛西亚之力");        Field outfit = clazz.getDeclaredField("outfit");        outfit.setAccessible(true);        outfit.set(hero, "神盾");        // 运行方法        hero.say();    }}

运行该类,打印出以下信息:

德玛西亚之力购买了神盾

在访问private、protected成员变量和方法时必须通过setAccessible(boolean access)方法取消Java语言检查,否则将抛出IllegalAccessException。
如果JVM的安全管理器设置了相应的安全机制,调用该方法将抛出SecurityException。

0 0
原创粉丝点击