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

转载地址

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。

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值