Spring IOC AOP的原理 如果让你自己设计IOC,AOP如何处理(百度)

百度的面试官问,如果让你自己设计一个IOC,和AOP,如何设计,

我把IOC的过程答出来了,但是明显不对, 
(1) IOC 利用了反射,自己有个id,classtype,hashmap,所有的功能都在hashmap中,然后利用反射的Class.forName把把classtype转化成类,然后利用反射的setFieldValue()从hashMap中把属性和方法取出来,注入进去。最终把类创建出来,

(2)AOP是动态代理,其实底层也是反射;

一、如何自己实现Spring的IOC的功能

我们都知道,IOC是利用了反射机制,接下来就让我们自己写个Spring 来看看Spring 到底是怎么运行的吧! (百度二面:如何自己实现Spring的 IOC依赖反转的功能)
首先,我们定义一个Bean类,这个类用来存放一个Bean拥有的属性 

/* Bean Id */  
    private String id;  
    /* Bean Class */  
    private String type;  //这是类名称
    /* Bean Property */  
    private Map<String, Object> properties = new HashMap<String, Object>();  

 

 一个Bean包括id,type,和Properties。 type 就是Class类名称

<bean id="personService" class="cn.itcast.service.OrderFactory" factory-method="createOrder"/>

public class OrderFactory {
    // 注意这里的这个方法是 static 的!
    public static OrderServiceBean createOrder(){   
        return new OrderServiceBean();
    }
}

 

接下来Spring 就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean 的 Id ,HasMap 的value是这个Bean,只有这样我们才能通过context.getBean("animal")这个方法获得Animal这个类。我们都知道Spirng可以注入基本类型,而且可以注入像List,Map这样的类型,接下来就让我们以Map为例看看Spring是怎么保存的吧 

Map配置可以像下面的   

<bean id="test" class="Test">  
        <property name="testMap">  
            <map>  
                <entry key="a">  
                    <value>1</value>  
                </entry>  
                <entry key="b">  
                    <value>2</value>  
                </entry>  
            </map>  
        </property>  
    </bean> 

 

 Spring是怎样保存上面的配置呢?,代码如下:  

if (beanProperty.element("map") != null) {  
                    Map<String, Object> propertiesMap = new HashMap<String, Object>();  
                    Element propertiesListMap = (Element) beanProperty  
                            .elements().get(0);  
                    Iterator<?> propertiesIterator = propertiesListMap  
                            .elements().iterator();  
                    while (propertiesIterator.hasNext()) {  
                        Element vet = (Element) propertiesIterator.next();  
                        if (vet.getName().equals("entry")) {  
                            String key = vet.attributeValue("key");  
                            Iterator<?> valuesIterator = vet.elements()  
                                    .iterator();  
                            while (valuesIterator.hasNext()) {  
                                Element value = (Element) valuesIterator.next();  
                                if (value.getName().equals("value")) {  
                                    propertiesMap.put(key, value.getText());  
                                }  
                                if (value.getName().equals("ref")) {  
                                    propertiesMap.put(key, new String[] { value  
                                            .attributeValue("bean") });  
                                }  
                            }  
                        }  
                    }  
                    bean.getProperties().put(name, propertiesMap);  
                }

 

 接下来就进入最核心部分了,让我们看看Spring 到底是怎么依赖注入的吧,其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。让我们看看具体它是怎么做的吧。 

首先实例化一个类,像这样  

public static Object newInstance(String className) {  
        Class<?> cls = null;  
        Object obj = null;  
        try {  
            cls = Class.forName(className);  
            obj = cls.newInstance();  
        } catch (ClassNotFoundException e) {  
            throw new RuntimeException(e);  
        } catch (InstantiationException e) {  
            throw new RuntimeException(e);  
        } catch (IllegalAccessException e) {  
            throw new RuntimeException(e);  
        }  
        return obj;  
    }  

 

 接着它将这个类的依赖注入进去,像这样   

public static void setProperty(Object obj, String name, String value) {  
        Class<? extends Object> clazz = obj.getClass();  
        try {  
            String methodName = returnSetMthodName(name);  
            Method[] ms = clazz.getMethods();  
            for (Method m : ms) {  
                if (m.getName().equals(methodName)) {  
                    if (m.getParameterTypes().length == 1) {  
                        Class<?> clazzParameterType = m.getParameterTypes()[0];  
                        setFieldValue(clazzParameterType.getName(), value, m,  
                                obj);  
                        break;  
                    }  
                }  
            }  
        } catch (SecurityException e) {  
            throw new RuntimeException(e);  
        } catch (IllegalArgumentException e) {  
            throw new RuntimeException(e);  
        } catch (IllegalAccessException e) {  
            throw new RuntimeException(e);  
        } catch (InvocationTargetException e) {  
            throw new RuntimeException(e);  
        }  
}  

 

最后它将这个类的实例返回给我们,我们就可以用了。我们还是以Map为例看看它是怎么做的,我写的代码里面是创建一个HashMap并把该HashMap注入到需要注入的类中,像这样, 

if (value instanceof Map) {  
                Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()  
                        .iterator();  
                Map<String, Object> map = new HashMap<String, Object>();  
                while (entryIterator.hasNext()) {  
                    Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();  
                    if (entryMap.getValue() instanceof String[]) {  
                        map.put((String) entryMap.getKey(),  
                                getBean(((String[]) entryMap.getValue())[0]));  
                    }  
                }  
                BeanProcesser.setProperty(obj, property, map);  
            } 

 

好了,这样我们就可以用Spring 给我们创建的类了,是不是也不是很难啊?当然Spring能做到的远不止这些,这个示例程序仅仅提供了Spring最核心的依赖注入功能中的一部分。  

 参考:Spring IOC 原理

二、IOC原理

让我们来看下IoC容器到底是如何工作。在此我们以xml配置方式来分析一下:

一、准备配置文件:就像前边Hello World配置文件一样,在配置文件中声明Bean定义也就是为Bean配置元数据。

二、由IoC容器进行解析元数据: IoC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IoC容器根据BeanDefinition进行实例化、配置及组装Bean。

三、实例化IoC容器:由客户端实例化容器,获取需要的Bean。 

实例化bean 有三种方式:类构造器实例化、静态工厂方法实例化及实例工厂方法实例化

参考:Spring实例化Bean的三种方式及Bean的类型

IoC(Inversion of Control)  

  (1). IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。 对于Spring而言,就是由Spring来控制对象的生命周期和对象之间的关系;IoC还有另外一个名字——“依赖注入(Dependency Injection)”。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,即由容器动态地将某种依赖关系注入到组件之中。  

(2). 在Spring的工作方式中,所有的类都会在spring容器中登记,告诉spring这是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

(3). 在系统运行中,动态的向某个对象提供它所需要的其他对象。  

(4). 依赖注入的思想是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性(至于是什么样的HashMap后面会提到)注入到类中。 总而言之,在传统的对象创建方式中,通常由调用者来创建被调用者的实例,而在Spring中创建被调用者的工作由Spring来完成,然后注入调用者,即所谓的依赖注入or控制反转。 注入方式有两种:依赖注入和设置注入; IoC的优点:降低了组件之间的耦合,降低了业务对象之间替换的复杂性,使之能够灵活的管理对象。

 

控制反转/依赖注入

IOC(DI):Java如果对象调用对象,对象间的耦合度高了。而IOC的思想是:Spring容器来实现这些相互依赖对象的创建、协调工作。对象只需要关系业务逻辑本身就可以了。从这方面来说,对象如何得到他的协作对象的责任被反转了(IOC、DI)。DI其实就是IOC的另外一种说法。DI 就是:获得依赖对象的方式反转了。 

IoC与DI

  首先想说说IoC(Inversion of Control,控制倒转)所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

 那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。如果你还不明白的话,我决定放弃。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。关于反射的相关资料请查阅java doc。

 理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

下面来让大家了解一下Spring到底是怎么运行的。  

public static void main(String[] args) {  
        ApplicationContext context = new FileSystemXmlApplicationContext(  
                "applicationContext.xml");  
        Animal animal = (Animal) context.getBean("animal");  
        animal.say();  
    }  

 这段代码你一定很熟悉吧,不过还是让我们分析一下它吧,首先是applicationContext.xml 

<bean id="animal" class="phz.springframework.test.Cat">  
        <property name="name" value="kitty" />  
    </bean> 

  他有一个类phz.springframework.test.Cat   

public class Cat implements Animal {  
    private String name;  
    public void say() {  
        System.out.println("I am " + name + "!");  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
}  

 实现了phz.springframework.test.Animal接口  

public interface Animal {  
    public void say();  
}  

 

很明显上面的代码输出I am kitty! 那么到底Spring是如何做到的呢? 

 

 

AOP(Aspect Oriented Programming)

(1). AOP面向方面编程基于IoC,是对OOP的有益补充;

(2). AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了 多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的 逻辑或责任封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

(3). AOP代表的是一个横向的关 系,将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,选择性的提供业务逻辑。而 剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。

(4). 实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

(5). Spring实现AOP:JDK动态代理和CGLIB代理 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。 CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强;需要引入包asm.jar和cglib.jar。     使用AspectJ注入式切面和@AspectJ注解驱动的切面实际上底层也是通过动态代理实现的。

如果需要了解具体的动态代理参考:深入理解Java反射+动态代理

(6). AOP使用场景:                     

Authentication 权限检查        

Caching 缓存        

Context passing 内容传递        

Error handling 错误处理        

Lazy loading 延迟加载        

Debugging  调试      

logging, tracing, profiling and monitoring 日志记录,跟踪,优化,校准        

Performance optimization 性能优化,效率检查        

Persistence  持久化        

Resource pooling 资源池        

Synchronization 同步        

Transactions 事务管理    

另外Filter的实现和struts2的拦截器的实现都是AOP思想的体现。  

参考:Spring框架IOC和AOP的实现原理

posted @ 2018-06-15 16:37  aspirant  阅读(4226)  评论(0编辑  收藏  举报