Java高级主题(六)上——Ioc+AOP原理

有了前面关于反射和动态代理的基础,理解IOC和AOP就相对简单了。

一、概述

  • IOC:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
    我们平时在写Java程序时,总要通过new的方式来产生一个对象,对象的生死存亡是与我们直接挂钩的,我们需要它时,就new一个,不需要他时就通过GC帮我们回收;控制反转的意思就是将对象的生死权交给第三方,由第三方来生成和销毁对象,而我们只在需要时从第三方手中取获取,其余不管,这样,对象的控制权就在第三方手里,我们只有使用权!这就是所谓的控制反转!
    在Spring中,提供了XML文件配置和注解的方式来向第三方表明我们需要第三方帮我们创建什么对象,Spring就是这个第三方!它负责通过XML文件的解析或者包扫描的方式,找到我们给出的映射关系,利用反射机制,在其内部帮我们“new”出对象,再存储起来,供我们使用!
  • AOP:Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
    就是动态代理的体现,在Spring中就是利用JDKProxy或者CGLibProxy技术,对方法进行拦截!
    比如说有一个叫做fun()的方法,我们在其执行前后对其拦截:


    image.png

    就像这样,fun看成是纵向的线,那么就相当于用平面把这条线截断了!
    有了上面的铺垫,我们可以大概知道,Sping的IOC和AOP可以帮我们创建并管理对象,可以对对象的方法进行拦截,那么这两个技术合在一起,就可以达到自动帮我们创建、管理、并对对象进行拦截!

二、Ioc实现

下面先给出我简化的SpringIOC容器框图:
模拟的IOC框图

image.png

这是简化后的IOC框图,实际上的SpringIOC是非常庞大的,里面包含了许多接口,以及继承关系,它把要处理的事务区分的非常细腻,将问题由大化小,层层递减,把面向接口,高内聚低耦合体现的淋漓尽致。
Spring提供了注解和Xml方式的注入,所以后面会有两个分支,分别处理注解和XML文件的配置!

BeanFactory

在别的地方说法是一个最底层容器,其实不要被其“误导”,在我这它仅仅只是一个接口,只提供了最基础的一些方法,而方法的具体实现就需要真正的高级容器了!代码如下:

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
    Object getBean(String var1) throws BeansException;
    <T> T getBean(String var1, Class<T> var2) throws BeansException;
    <T> T getBean(Class<T> var1) throws BeansException;
    Object getBean(String var1, Object... var2) throws BeansException;
    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;
    boolean containsBean(String var1);
    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
    String[] getAliases(String var1);
}

(这里我直接挪用了Spring的源码,由于是模拟实现,所以后面只实现了其getBean的方法)

ApplicationContext

在别的地方的说法是一个高级容器,其实,它还是一个接口,只不过在源码中其继承了许多接口(核心还是BeanFactory),是一个集大成者,提供了远比BeanFactory更多的功能。但目前所要实现的核心暂时用不上它,所以暂时留一个空接口吧...

public interface ApplicationContext extends BeanFactory {
     // TODO
}

说到这里,就不能往下继续了,因为在上面我们看到的,所谓的“容器”,仅仅是定义了接口,完全不能装东西啊,还有,所谓的容器里又要装什么?这里就要引入Bean!

Bean:

其实就是IOC容器里存放的东西!前面我说过,Spring会根据我们给出的映射关系,帮我们创建对象并存储起来,那么是否这个对象就是Bean?是!但也不是!如果说Spring仅仅只是帮我们管理对象,那么它的功能也太单一了,那么,现在不得不再次提到前面说过的AOP!
前面说到Spring中的AOP使用了JDKProxy和CGLibProxy这两种代理技术,这两种技术的目的都是产生代理对象,对方法进行拦截。那么,是否这个代理对象就是我们的Bean?不完全是!Bean其实是原对象+代理对象!先不急,看到后面就会明白!

下面介绍两种动态代理技术:
JDKProxy:
使用JDK代理,其所代理的类,必须要实现某一个接口:

@SuppressWarnings("unchecked")
public <E> E getJDKProxy(Class<?> klass, Object tagObject) {
    return (E) Proxy.newProxyInstance(klass.getClassLoader(), 
            klass.getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) 
                        throws Throwable {
                    // TODO 置前拦截,可对参数args进行判断
                    Object result = null;
                    try {
                        result = method.invoke(tagObject, args);
                    } catch (Exception e) {
                        // TODO 对异常进行拦截
                        throw e;
                    }
                    // TODO 置后拦截,可对方法返回值进行修改
                    return result;
                }
            });
}

使用JDK代理的话就不得不传入一个原始对象,所以如果不考虑CGLib代理,那么Bean就是原始对象+代理对象!

CGLibProxy
使用CGLib代理,是让被代理的类作为代理对象的父类,故原类不能被final修饰,也不能对final修饰的方法拦截!

以下是网上绝大多数人给出的用法:

@SuppressWarnings("unchecked")
public <E> E getCGLibProxy(Class<?> klass) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(klass); // 从这里可以明显看到,让被代理的类作为了代理对象的父类
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) 
                throws Throwable {
            // TODO 置前拦截,可对参数args进行判断
            Object result = null;
            try {
                result = methodProxy.invokeSuper(proxyObject, args);
            } catch (Exception e) {
                // TODO 对异常进行拦截
                throw e;
            }
            // TODO 置后拦截,可对方法返回值进行修改
            return result;
        }
    });
    return (E) enhancer.create();
}

这种方式是没错,但是不适用于后面要做的,至于原因,后面分析到了就会明白!
所以使用如下方式:

@SuppressWarnings("unchecked")
public <E> E getCGLibProxy(Class<?> klass, Object tagObject) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(klass);
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) 
                throws Throwable {
            // TODO 置前拦截,可对参数args进行判断
            Object result = null;
            try {
                result = method.invoke(tagObject, args);
            } catch (Exception e) {
                // TODO 对异常进行拦截
                throw e;
            }
            // TODO 置后拦截,可对方法返回值进行修改
            return result;
        }
    });
    return (E) enhancer.create();
}

由于是模拟实现,后面就全部采用CGLib代理!
可以看到以上面这种方式进行CGLib代理就需要原始对象,那么前面说到的Bean就必须是原对象+代理对象!
当然我知道以invokeSuper那种方式是不需要原始对象,但是原因不是因为Bean,还在后面!
综上,Bean的定义如下:

public interface BeanElement {
     <E> E getProxy();
     Object getObject();
     boolean isDI(); // 用于以后判断是否完成注入
}

在这里我把BeanElement定义为了一个接口,后面会产生两个分支,会产生两种不同处理方式的Bean,用接口更灵活,耦合也低!

现在知道了Bean到底是什么,我们就可以往下继续进行:

AbstractApplicationContext

ApplicationContext的具体实现类,但它是一个抽象类,只能实现部分功能,往后在它的基础上还要分化成两支,那么,把所有的Bean存在这里面再合适不过了!

public abstract class AbstractApplicationContext implements ApplicationContext {
    protected static final Map<String, String> beanNameMap;  // key : id      value : className
    protected static final Map<String, BeanElement> beanMap; // key : className value : Bean
    protected AopFactory aopFactory; // Aop工厂,生产代理对象
    
    static {
        beanNameMap = new HashMap<>();
        beanMap = new HashMap<>();
    }
    
    protected AbstractApplicationContext() {
        aopFactory = new AopFactory();
        // 设置切面
        aopFactory.setAdvice(
                (new AdviceHander())
                .setIntercepterFactory(new IntercepterLoader()));
    }
    
    protected void add(String id, String className, BeanElement bean) throws BeansException {
        if (beanMap.containsKey(className)) {
            // TODO 可以抛异常!
            return;
        }
        beanMap.put(className, bean);
        if (id.length() <= 0) return;
        if (beanNameMap.containsKey(id)) {
            throw new BeansException("bean:" + id + "已定义!");
        }
        beanNameMap.put(id, className);
    }
}

其中的aopFactory是代理工厂,负责生产代理,会在后面给出,先不急。
可以看到,AbstractApplicationContext这个类持有了两张静态的map,第一组是用来存取Bean的别名(id),第二组用来存放真正的Bean,这就是我们真正意义上的容器,由于其map都是static修饰的,在类加载的时候就存在了,所以往后有别的类继承它时,这两个map是共享的!只增加了一个add方法,只要是继承自它的子类,都会通过这种方式添加Bean!并且这个类是protected的,不对外提供使用!

我们先进行左边的分支,用注解的方式完成IOC
这里说的注解都是自定义注解,属于RUNTIME,就必须通过反射机制来获取,反射机制就要得到类或者类名称,那么就先得到符合要求的类,这里就要用到包扫描,我在前面的博客中有介绍过:【Java】包、jar包的扫描
首先是对类的注解:

@Component

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String id() default "";
}

其中的id相当于类名称的别名,具有唯一性,如果不设置则不处理!通过这个注解我们就可以判断哪个类是需要进行操作的,就应该自动地为这个类生成一个对象和代理对象,将其添加到beanMap中,就是bean的标志!
如果说用过Spring的XML配置,这其实就相当于一个bean标签:

<bean id="XXX" class="XXX">
     ......
</bean>

注解中的id就相当于id属性,我们是通过反射机制得到注解的,当然能得到类名称,那就相当于有了class属性!

但是仅仅有这个注解是完全不够的,我们只能通过反射机制产生一个对象,但它的成员都没赋值,仅仅是一具躯壳,所以就需要对成员添加注解,将我们需要的值注入进来;当然也可以给方法添加注解,通过setter方法给成员赋值,Spring就是使用的这种方式!
这是对成员的注解:
@Autowired

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface Autowired {
    boolean requisite() default true;
}

这里直接使用的spring源码,在源码中,这个注解可以成员、方法以及构造方法添加。其中的requisite是是否必须注入,这是Spring提供的更为细腻的操作,我的实现暂时不考虑它。

如果说这个注解是给成员添加的,那么标志着它需要赋值!使用这个注解的前提是它本身是一个复杂类型,不是基本类型,它的赋值,是将我们beanMap中的符合要求的Bean注入进去!至于基本类型后面有别的解决办法。
用Component 和Autowired注解其实就相当于如下XML的配置:

<bean id="XXX" class="XXX">
   <property name="XXX" ref="XXX">
</bean>

我们同样是通过反射机制得到的Autowired注解,那么一定可以得到成员名称,和成员类型,成员名称就相当于name属性,通过成员类型就可以得到类型名称,就相当于ref属性!

如果说是给构造方法添加,那么就规定了我们在反射机制执行时需要调用哪个构造方法,相当于如下:

<bean id="XXX" class="XXX">
   <constructor-arg index="0" ref="XXX">
    <constructor-arg index="1" ref="XXX">
</bean>

对于构造方法的处理我觉得使用注解的方式比XML配置要更好,注解可以直接定位到某一个构造方法,但是XML文件的方式就需要遍历比较,找出符合要求的,而且关于这个符合要求这一说还有更为复杂的问题,我会在后面用XML的方式详细介绍!

还有一种就是对方法添加,实际上就是提供给setter方法使用的,通过执行setter方法给成员赋值,可能看到这里会觉得这样做是不是多此一举,其实不是,因为这样做会避免我后面会谈到的循环依赖的问题!所以给方法添加,和对成员添加等效:

<bean id="XXX" class="XXX">
   <property name="XXX" ref="XXX">
</bean>

上面我说过使用Autowired 是不处理基本类型的,它是将bean注入的,基本类型完全没有必要作为bean,那么,我们就可以给出一个注解,直接赋值:

@Value

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface Value {
    String value();
}

其中value就是我们要注入的值,但是它是String类型的,可能需要强制类型转换
演示如下:

@Component
public class TestA {
    @Autowired
    private TestB testB;
    @Value(value="直接赋值")
    private String member;
    
    public TestA() {
    }

}

@Component(id = "testB")
public class TestB {
    private int a;
    private TestA testA;
    
    @Autowired
    public TestB(@Value(value="10")int a, TestA testA) {
        this.a = a;
        this.testA = testA;
    }
    
}

就相当于:

<bean class="xxx.xxx.TestA">
    <property name="testB" ref="xxx.xxx.TestB">
    <property name="member" value="直接赋值">
</bean>
<bean id="testB" class="xxx.xxx.TestB">
    <constructor-arg index="0" value="10"></constructor-arg>
    <constructor-arg index="1" ref="xxx.xxx.TestA"></constructor-arg>
</bean>

为了简单处理,Autowired注解我在后面就只处理成员的。

有了上面的两个注解是否够呢?当然不够。仔细想一想,如果说我们需要Spring帮我们创建的对象,其对应的类又被打成了jar包,那么我们完全没有办法对已经形成jar包的代码添加注解;又或者说是我们需要创建的对象不是通过反射机制就能产生的,它是一个工厂对象,需要走工厂模式那一套来创建,上面的两个注解就远远不能满足我们的要求了!因此,我们还需要一个作为补充的注解:

@Bean

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Bean {
    String id() default "";
}

可以看出这是对方法的注解,因为我们可以通过反射机制执行方法,将方法的返回值作为Bean的原始对象,再产生一个代理对象,这样就能解决上面的所说的问题!

@Component
public class Action {
    
    @Bean(id="getDocumentBuilderFactory")
    public DocumentBuilderFactory getDocumnet() throws Exception {
        return DocumentBuilderFactory.newInstance();
    }
    
    @Bean
    public DocumentBuilder getDocumentBuilder(DocumentBuilderFactory factory) throws Exception {
        return factory.newDocumentBuilder();
    }
    
}

就相当于:

<bean class="xxx.xxx.Action "></bean>
<bean class="javax.xml.parsers.DocumentBuilderFactory" id="getDocumentBuilderFactory" 
    factory-method="newInstance"></bean>
<bean class="javax.xml.parsers.DocumentBuilderFactory" 
    factory-bean="getDocumentBuilderFactory" factory-method="newDocumentBuilder">
</bean>

有了这些注解,我们就能对包进行扫描,可以继续向下进行!

AnnotationContext

这个类继承自AbstractApplicationContext,它是protected的,不对外提供,我用它来进行有关注解的扫描和解析,但它的功能有限,不能完成全部的注入,这会涉及到注入的顺序,以及注入之间的依赖关系:

/**
 * 执行包扫描
 * 将符合要求的结果添加到父类AbstractApplicationContext的beanMap中
 * 只处理@Component和@Bean注解
 * @Autowired注解留给下一级处理
 */
public class AnnotationContext extends AbstractApplicationContext {
    // method缓冲区,保存暂时不能执行的方法,其中的MethodBuffer用来封装method,以及invoke时所需要的内容
    private List<MethodBuffer> methodList; 

    protected AnnotationContext() {
    }
    
    protected AnnotationContext(String packageName) {
        scanPackage(packageName);
    }
    
    /**
     * 通过aopFactory产生代理对象,将代理对象和原始对象封装成bean添加到父类的map中
     */
    private void addBean(Class<?> klass, Object object, String id, String className) {
        // aopFactory是其父类AbstractApplicationContext的成员,原来产生代理对象
        Object proxy = aopFactory.creatCGLibProxy(klass, object);
        // AnnoationBean是BeanElement接口的一个实现类
        AnnotationBean bean = new AnnotationBean(object, proxy);
        // 父类AbstractApplicationContext的add方法
        add(id, className, bean);
    }
    
    protected void scanPackage(String packageName) {
        new PackageScanner() {
            @Override
            public void dealClass(Class<?> klass) {
                if (!klass.isAnnotationPresent(Component.class)) return;
                Component component = klass.getAnnotation(Component.class);
                String className = klass.getName();
                String name = component.id();
                try {
                    // 这里简单起见,不考虑构造方法的重载,默认执行无参构造 
                    Object object = klass.newInstance();
                    // 产生BeanElement,加入到beanMap中
                    addBean(klass, object, name, className);
                    // 处理带有@Bean注解的方法
                    dealMethod(klass, object);
                } catch (Exception e) {
                    // TODO 
                    e.printStackTrace();
                }
            }
        }.scanPackage(packageName);
        if (methodList == null) return;
        // 执行缓存的所有方法
        for (MethodBuffer methodBuffer : methodList) {
            // 获得方法执行所需要的东西
            String id = methodBuffer.getId();
            Class<?> returnClass = methodBuffer.getReturnClass();
            Method method = methodBuffer.getMethod();
            Object object = methodBuffer.getObject();
            Parameter[] parameters = methodBuffer.getParameters();
            
            try {
                dealMultiParaMethod(returnClass, method, object, parameters, id);
            } catch (Exception e) {
                // TODO 
                e.printStackTrace();
            } 
        }
        methodList.clear();
    }
    
    private void dealMultiParaMethod(Class<?> returnClass, Method method, 
            Object object, Parameter[] parameters, String id) 
                    throws BeansException, IllegalAccessException, IllegalArgumentException, 
                    InvocationTargetException, ValueOnlyPrimitiveType {
        int count = parameters.length;
        Object[] result = new Object[count];
        
        for (int index = 0; index < count; index++) {
            Parameter para = parameters[index];
            // 判断参数是否带有@Value注解
            if (para.isAnnotationPresent(Value.class)) { 
                Class<?> type = para.getType();
                // 判断@Value注解标识的参数是否是基本类型(八大类型和String)
                if (!type.isPrimitive() && !type.equals(String.class)) {
                    throw new ValueOnlyPrimitiveType("Value只能用基本类型!");
                }
                // TypeConversion是我自己的一个工具类,用于将字符串转换成基本类型!
                result[index] = TypeConversion.getValue(para.getAnnotation(Value.class).value(), 
                                            para.getType().getSimpleName());
            } else {
                // 如果不是基本类型,那么就需要从beanMap中获取符合要求的bean
                result[index] = getBean(para.getType());
            }
        }
        // 执行方法,获得方法返回值
        Object returnObject = method.invoke(object, result);
        // 为方法执行结果创建bean,添加到beanMap中
        addBean(returnClass, returnObject , id, returnClass.getName());
    }
    
    /**
     * 遍历所有方法,处理带有@Bean注解的方法
     */
    private void dealMethod(Class<?> klass, Object object) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Method[] methods = klass.getDeclaredMethods();
        for (Method method : methods) {
            if (!method.isAnnotationPresent(Bean.class)) continue; 
            
            Class<?> returnType = method.getReturnType();
            if (returnType.equals(void.class)) {
                // TODO 如果没有返回值,那么根本没有必要做
                return;
            }
            String id= method.getAnnotation(Bean.class).id();
            Parameter[] parameters = method.getParameters();
            // 判断方法是否有参数,没有参数,直接执行,添加Bean
            // 有参数就先缓存起来,等所有都扫描完成后再执行
            if (parameters.length <= 0) {
                Object returnObject = method.invoke(object);
                addBean(returnType, returnObject, id, returnType.getName());
            } else {
                (methodList = methodList == null ? new ArrayList<>() : methodList)
                    .add(new MethodBuffer(returnType, object, method, parameters, id));
            }
        }
    }
    
}

这个类只负责扫描包,为带有@Component注解的类通过反射机制生成对象,再通过代理工厂将其加工成代理对象,然后封装再AnnotationBean中作为bean,将其添加到BeanMap中!
其次还处理了带有@Bean注解的的方法,如果说是方法带有参数,那么就像将这个方法的执行延后,等所有东西都扫描完成后再执行;而对于无参的方法,则可以直接执行,并为执行结果产生的对象创建代理,生成AnnotationBean,添加进beanMap中。至于@Autowired注解这个类暂不处理,留给下一级处理!

AnnotationBean类的定义如下:

/**
 * 注解分支中BeanElement的具体实现类
 */
public class AnnotationBean implements BeanElement {
    private boolean DI; // 判断是否完成注入
    private Object object; // 原始对象
    private Object proxy; // 代理对象
    
    AnnotationBean() {
        this(false, null, null);
    }
    
    AnnotationBean(Object object, Object proxy) {
        this(false, object, proxy);
    }
    
    AnnotationBean(boolean dI, Object object, Object proxy) {
        DI = dI;
        setObject(object);
        setProxy(proxy);
    }
    
    @Override
    public Object getObject() {
        return object;
    }
    
    AnnotationBean setObject(Object object) {
        this.object = object;
        return this;
    }

    AnnotationBean setProxy(Object proxy) {
        this.proxy = proxy;
         return this;
    }

    void setDI(boolean DI) {
        this.DI = DI;
    }
    
    @Override
    public boolean isDI() {
        return DI;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <E> E getProxy() {
        return (E) proxy;
    }
    
}

MethodBuffer 类如下:

/**
 *    带有@Bean注解的方法封装类
 */
public class MethodBuffer {
    private String id; // bean的id
    private Class<?> returnType; // 方法返回值类型
    private Object object; // 方法执行所需对象
    private Method method; // 方法本身
    private Parameter[] parameters; // 方法所需参数
    
    MethodBuffer() {
    }

    MethodBuffer(Class<?> returnType, Object object, Method method, Parameter[] parameters, String id) {
        this.returnType = returnType;
        this.object = object;
        this.method = method;
        this.parameters = parameters;
        this.id = id;
    }

    String getId() {
        return id;
    }
    
    Object getObject() {
        return object;
    }

    Class<?> getReturnType() {
        return returnType;
    }

    Method getMethod() {
        return method;
    }

    Parameter[] getParameters() {
        return parameters;
    }
    
}

*在处理@Bean注解时,就能发现我前面提出的问题,用CGLibProxy产生的代理为什么还需要原始对象?
我们处理@Bean注解,是为了得到方法的返回值,如果直接对返回值所对应的类进行代理,产生代理对象,那么,在方法执行时,如果原始对象的成员被赋值了,那么代理对象是不会知道的,那么产生的代理是不完整的!使用methodProxy.invokeSuper(proxyObjet, args)方法是不可行的了!所以一定要保存原始对象,使用method.invoke(object, args)才是合理的!

AnnotationConfigApplicationContext

这是注解这一支最高级的容器,是最终留给用户使用的,用来完成最后@Autowired注解标识的成员的注入!
如果说是需要注入的bean都能找的到,且这些bean都完成了注入,那么其注入过程会非常简单,但若是,彼此之间的依赖关系比较复杂,你中有我,我中有你,会形成一个环形闭环,陷入死循环,这就是循环依赖!
循环依赖


image.png

这里有四个类ClassA、ClassB、ClassC、ClassD,并且都没有完成注入,如果现在想getBean得到ClassA的Bean,那么就需要先对ClassA的Bean完成注入,但是其注入又需要ClassB,那么,就需要先将B注入,但B又要C,那就先注入C,可是C需要D,只能先注入D,但是D确需要A!绕了一圈又回来了,陷入了死循环,这就是我们要解决的循环依赖!

解决方案:
一、前面我说过@Autowired注解可以给setter方法添加,用来解决循环依赖!
如果说我们使用这种方式给ClassA的setClassB方法添加@Autowired,而不是给其ClassB成员添加,那么这个循环自然而然就不会出现!
二、假设自身以完成注入:
在ClassA注入之前,让它的状态变为完成注入,然后继续找B,发现B没注入,找C,C没注入,找D,D没注入,找A,此时A的状态是完成注入,自然也就不会产生闭环!


image.png

所以AnnotationConfigApplicationContext就是为了最后的注入:

public class AnnotationConfigApplicationContext extends AnnotationContext {
    public AnnotationConfigApplicationContext() {
    }
    // 调用父类的构造
    public AnnotationConfigApplicationContext(String packageName) {
        super(packageName);
    }
    // Advice是代理的拦截处理,内部使用默认的一种方式,用户也可以注入一种方式
    public AnnotationConfigApplicationContext setAdvice(Advice advice) {
        aopFactory.setAdvice(advice);
        return this;
    }
    
    public AnnotationConfigApplicationContext parsePackage(String packageName) {
        scanPackage(packageName);
        return this;
    }
    
    @Override
    public Object getBean(String name) throws BeansException {
        String className = beanNameMap.get(name);
        return className == null ? get(name) : get(className);
    }

    private <T> T get(String className, Class<?> klass) throws BeansException {
        BeanElement bean =  beanMap.get(className);
        if (bean == null) {
            throw new BeansException("Bean :" + klass + "不存在!");
        }
        if (!bean.isDI() && bean instanceof AnnotationBean) {
            autowired(klass, (AnnotationBean)bean);
        }
        return bean.getProxy();
    }
    
    @Override
    public <T> T getBean(Class<T> klass) throws BeansException {
        return get(klass.getName());
    }
    
    private void autowired(AnnotationBean bean) throws BeansException {
        // 一开始令自身完成注入
        bean.setDI(true);
        Object object = bean.getObject();
        Class<?> klass = object.getClass();    
        Field[] fields = klass.getDeclaredFields();
        Object arg = null;
        for (Field field : fields) {
            if (field.isAnnotationPresent(Value.class)) {
                try {
                    // 注入基本类型的值
                    arg = injectValue(field);
                } catch (ValueOnlyPrimitiveType e) {
                    e.printStackTrace();
                }
            } else if (field.isAnnotationPresent(Autowired.class)) {
                // 注入bean中的Bean
                arg = injectBean(field);
            } else {
                continue;
            }
            try {
                // 成员注入
                field.setAccessible(true);
                field.set(object, arg);
            } catch (Exception e) {
                throw new BeansException(klass + "依赖关系不正确!");
            }
        }
    }
    
    private Object injectValue(Field field) throws ValueOnlyPrimitiveType {
        Class<?> type = field.getType();
        if (!type.isPrimitive() && !type.equals(String.class)) {
            throw new ValueOnlyPrimitiveType("Value只能用于八大基本类型!");
        }
        Value value = field.getAnnotation(Value.class);
        return TypeConversion.getValue(value.value(), type.getSimpleName());
    }
    
    private Object injectBean(Field field) throws BeansException {
        Class<?> fieldType = field.getType();
        BeanElement fieldBean =  beanMap.get(fieldType.getName());
        if (fieldBean == null) { return null;}
        if (!fieldBean.isDI() && fieldBean instanceof AnnotationBean) {
            autowired((AnnotationBean)fieldBean);
        }
        return fieldBean.getProxy();
    }
}

这里解决循环依赖就使用了我上面给出的第二种方案,利用递归来实现!

注解部分的简单实现已基本完成,虽然有些地方没有处理或是处理的比较简陋,但是SpringIOC的核心思想就是如此,只不过Spring实现的更为精致、细腻!
来看看它的使用:
先给出几个需要注入的类:

@Component(id="studentA")
public class StudentA {
    @Value(value="我是A")
    String name;
    @Autowired
    private StudentB B;
    
    public StudentA() {
    }
    
    @Override
    public String toString() {
        return "A:" + name + "->" +  B;
    }
    
}

@Component
public class StudentB {
    @Value(value="我是B")
    private String name;
    @Autowired
    private StudentC C;
    
    public StudentB() {
    }

    @Override
    public String toString() {
        return "B:" + name + "->" + C;
    }
    
}

@Component
public class StudentC {
    @Value(value="我是C")
    private String name;
    
    @Autowired
    private StudentA A;
    
    public StudentC() {
    }

    @Override
    public String toString() {
        return "C:" + name;
    }
    
}

public class StudentD {
    private String name;
    @Autowired
    private StudentA A;
    
    public StudentD(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "D:" + name + "->" + A;
    }
    
}

@Component
public class MethodAction {
    public MethodAction() {
    }

    @Bean(id="studentD")
    public StudentD getStudentD(@Value(value="我是D")String name) {
        return new StudentD(name);
    }
}

主函数:

public static void main(String[] args) throws BeansException {
    ApplicationContext applicationContext = 
                new AnnotationConfigApplicationContext("com.zc.ioc.demo");
        StudentD studentD = applicationContext.getBean(StudentD.class);
        System.out.println(studentD);

}

结果是:


image.png

或者这样使用:

public static void main(String[] args) throws BeansException {
    BeanFactory beanFactory = new AnnotationConfigApplicationContext("com.zc.ioc.demo");
        StudentD studentD = (StudentD) beanFactory.getBean("studentD");
        System.out.println(studentD);
}

结果是:


image.png

注解的方式实现了左边的分支,那么就剩下右边的XML分支:
XmlContext:
这个类是也是AbstractApplicationContext的子类,和AnnotationContext相似,只不过这里是要解析XML文件而不是注解:
(关于XML文件的解析之前给过一篇博客:【Java】XML文件的解析
对于XML文件的处理,是不太容易的,会产生很多问题,后面只是实现核心步骤,很多属性就不考虑了!
首先给出XmlBean,和AnnotationBean一样,都是继承自BeanElement

public class XmlBean implements BeanElement {
    private boolean DI;
    private Object object;
    private Object proxy;
    private Map<Field, String> wiredMap; 
    // key:object的为注入成员  value:依赖的className
    // 将不能注入的成员先保存起来
    
    protected XmlBean() {
        this(true, null, null);
    }

    protected XmlBean(Object object, Object proxy) {
        this(true, object, proxy);
    }

    protected XmlBean(boolean dI, Object object, Object proxy) {
        DI = dI;
        this.object = object;
        this.proxy = proxy;
    }

    protected void addWiredElement(Field field, String ref) throws RepeatProperty {
        if (wiredMap == null) {
            wiredMap = new HashMap<>();
        }
        if (wiredMap.containsKey(field)) {
            throw new RepeatProperty(object.getClass() + "成员:" + field.getName() + "已定义!");
        }
        wiredMap.put(field, ref);
    }
    
    protected void setDI(boolean DI) {
        this.DI = DI;
    }

    protected Map<Field, String> getWiredMap() {
        return wiredMap;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public <E> E getProxy() {
        return (E) proxy;
    }

    @Override
    public Object getObject() {
        return object;
    }

    @Override
    public boolean isDI() {
        return DI;
    }

}

XmlContext:

public class XmlContext extends AbstractApplicationContext {
    protected XmlContext() {
    }
    
    protected XmlContext(String xmlPath) {
        innerParseXml(xmlPath);
    }
    
    // 和注解方式中的做法一样,只不过产生的是XML方式的BeanElement
    private XmlBean addXmlBean(Class<?> klass, Object object, String classId, String className) throws BeansException {
        Object proxy = aopFactory.creatCGLibProxy(klass, object);
        XmlBean bean = new XmlBean(object, proxy);
        add(classId, className, bean);
        return bean;
    }

    protected void innerParseXml(String xmlPath) {
        // 找到根标签 
        new XMLReader() {
            @Override
            public void dealElment(Element element, int index) {
                // 处理bean标签
                new XMLReader() {
                    @Override
                    public void dealElment(Element element, int index) {
                        // 得到id属性和class属性的值
                        String classId = element.getAttribute("id");
                        String className = element.getAttribute("class");
                        try {
                            // 由class得到类
                            Class<?> klass = Class.forName(className);
                            // 处理constructor标签
                            new XMLReader() {
                                @Override
                                public void dealElment(Element element, int index) {
                                // TODO 处理有参数的构造方法,这里就会遇到许多问题,在这里我就不处理了,后面会给出解决思路
                                }
                            }.parse(element, "constructor-arg");
                            // 由于上面没有处理带参数的构造方法,这里直接通过反射机制调用无参构造产生对象
                            // 并且利用产生的对象生成代理对象,最后得到Bean放入beanMap中
                            Object object = klass.newInstance();
                            XmlBean bean = addXmlBean(klass, object, classId, className);
                            
                            // 处理property标签
                            new XMLReader() {
                                @Override
                                public void dealElment(Element element, int index) {
                                    try {
                                        dealProperty(element, klass, bean);
                                    } catch (XmlPropertyMustNeedNameException e) {
                                        e.printStackTrace();
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                }
                            }.parse(element, "property");
                        } catch (Exception e1) {
                            e1.printStackTrace();
                        }
                    }
                }.parse(element, "bean");
            }
        }.parse(XMLReader.openDocument(xmlPath), "SimpleSpring");
    }
    
    private void dealProperty(Element element, Class<?> klass, XmlBean bean) 
            throws XmlPropertyMustNeedNameException, Exception {
        // 得到property标签name属性的值
        String fieldName = element.getAttribute("name");
        if (fieldName.length() <= 0) {
            throw new XmlPropertyMustNeedNameException("Bean" + klass.getName() + "的Property标签必须声明name属性!");
        }
        // 通过反射机制得到成员
        Field field = klass.getDeclaredField(fieldName);
        // 得到该成员的类型
        Class<?> fieldType = field.getType();
        // 得到value属性
        String value = element.getAttribute("value");
        // 得到ref属性
        String ref = element.getAttribute("ref");
        
        // 判断ref和value是否同时存在 
        if (value.length() > 0 && ref.length() > 0) {
            throw new CanNotJudgeParameterException("value:" + value + " ref:" + ref + "只能存在一个!");
        }
        Object arg = null;
        // value存在,则直接通过类型转换给成员赋值
        if (value.length() > 0) {
            if (!fieldType.isPrimitive() && !fieldType.equals(String.class)) {
                throw new ValueOnlyPrimitiveType("Value只能用于八大基本类型!");
            }
            // TypeConversion是我自己写的,将字符串转换为基本类型的工具
            arg = TypeConversion.getValue(value, fieldType.getSimpleName());
            field.setAccessible(true);
            field.set(bean.getObject(), arg);
        }
        if (ref.length() > 0) {
            // ref属性存在,由于存在相互依赖关系,所以现在不做处理,只是将其保存起来
            // 设置该bean的状态为尚未注入
            bean.setDI(false);
            bean.addWiredElement(field, ref);
        }
    }

}

XmlContext能做的工作也十分有限,只能完成简单的注入,剩下的注入工作留给下一级处理!

在这里之所以没有处理constructor标签,是因为对与构造方法的处理存在许多因素:
比如:

public class Test {
    public Test(String one, int two) {
        ......
    }
    public Test(int two, String one) {
        ......
    }
}

通过XML文件读取出来的都是字符串,如何区分它是字符串“123”,而不是int类型123?这两个构造方法到底执行哪个?
再比如说:

public Test(int one, int two, Student student) {
        ......
    }
    
    public Test(String one, int two, Student student) {
        ......
    }

    public Test(int two, String one, Student student) {
        ......
    }

通过反射机制,我们就需要得到构造方法的集合getConstructors();然后筛选出参数个数符合要求的子集,再遍历这个子集的每一个构造方法,然后遍历当前构造方法的所有参数,一个一个比对参数类型是否符合要求,直到找到符合要求的那一个为止,但是,如果说我们是想执行第三个构造方法,它却找到的是第一个,完全就出问题了!
所以Spring的解决办法是给出一个type属性

<bean id="xxx" class="xxx.xxx.Test">
    <constructor-arg idnex="0" value="1" type="int.class">
    <constructor-arg idnex="1" value="2" type="java.lang.String">
    <constructor-arg idnex="2" ref="student">
</bean>

只有这样做才能真真区分,所以以后在使用Spring的constructor标签时,当构造方法有歧义时,一定要给出type属性,避免出错,也减少了查找时的遍历!

接下来就是最后一个类,xml分支的最高容器:

ClassPathXmlApplicationContext

上面的XmlContext只是完成了基本的注入问题,还有后续有关于注入之间的依赖关系,甚至是依赖循环(关于依赖循环在我的上一篇中有专门介绍,这里就不再介绍了)

public class ClassPathXmlApplicationContext extends XmlContext {
    public ClassPathXmlApplicationContext() {
    }
    
    public ClassPathXmlApplicationContext(String xmlPath) {
        super(xmlPath);
    }
    
    public ClassPathXmlApplicationContext parseXml(String xmlPath) {
        innerParseXml(xmlPath);
        return this;
    }
    
    @Override
    public <T> T getBean(Class<T> klass) throws BeansException {
        String className = klass.getName();
        BeanElement bean =  beanMap.get(className);
        
        if (bean == null) {
            throw new BeansException("Bean :" + klass + "不存在!");
        }
        // 在这里还是只考虑XmlBean的注入,不考虑AnnotationBlean注解的完成情况
        if (!bean.isDI() && bean instanceof XmlBean) {
            autowired(className, (XmlBean)bean);
        }
        
        return bean.getProxy();
    }
    
    private void autowired(String klassName, XmlBean bean) throws BeansException {
        // 和AnnotationBean的解决思路一样,先设置状态为已注入,防止循环依赖的无限递归
        bean.setDI(true);
        // 得到尚未注入的成员map
        Map<Field, String> wiredMap = bean.getWiredMap();
        if (wiredMap == null || wiredMap.isEmpty()) return;
        // 遍历map
        for (Field field : wiredMap.keySet()) {
            String ref = wiredMap.get(field);
            String tagClassName = beanNameMap.get(ref);
            // ref如果是id则在beanNameMap中找,如果是className就在beanMap中找
            BeanElement wiredBean = tagClassName == null ? beanMap.get(ref) : beanMap.get(tagClassName);
            if (bean == null) {
                return;
            }
            if (!wiredBean.isDI() && wiredBean instanceof XmlBean) {
                autowired(ref, (XmlBean)wiredBean);
            }
            field.setAccessible(true);
            try {
                field.set(bean.getObject(), wiredBean.getObject());
            } catch (Exception e) {
                throw new BeansException(klassName + "依赖关系不正确!");
            }
        }
        wiredMap.clear();
    }
    
}

看过注解方式的话再看XML就会发现两者其实是一回事,都是通过两者提供的映射关系,利用反射机制完成注入!
只不过两者提供的映射关系在解析起来时各有各的特点!

Xml方式的实现这里就简单实现了,来看看使用情况:

public class StudentA {
    String name;
    private StudentB B;
    
    public StudentA() {
    }
    
    @Override
    public String toString() {
        return "A:" + name + "->" +  B;
    }
    
}

@Component
public class StudentB {
    private String name;
    private StudentC C;
    
    public StudentB() {
    }

    @Override
    public String toString() {
        return "B:" + name + "->" + C;
    }
    
}

@Component
public class StudentC {
    private String name;
    private StudentA A;
    
    public StudentC() {
    }

    @Override
    public String toString() {
        return "C:" + name;
    }
    
}

xml的配置:

<SimpleSpring>
    <bean id="haha" class="com.zc.ioc.demo.StudentA">
        <property name="name" value="我是A"></property>
        <property name="B" ref="com.zc.ioc.demo.StudentB"></property>
    </bean>
    <bean class="com.zc.ioc.demo.StudentB">
        <property name="name" value="我是B"></property>
        <property name="C" ref="com.zc.ioc.demo.StudentC"></property>
    </bean>
    <bean class="com.zc.ioc.demo.StudentC">
        <property name="name" value="我是C"></property>
        <property name="A" ref="haha"></property>
    </bean>
</SimpleSpring>

主函数:

public static void main(String[] args) throws BeansException {
        // 或者是使用BeanFactory beanFactory = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
        StudentA bean = applicationContext.getBean(StudentA.class);
        System.out.println(bean);
}

输出:


image.png

那么试一试注解和Xml方式的混合使用:

@Component
public class StudentA {
    @Value(value="我是A")
    String name;
    @Autowired
    private StudentB B;
    
    public StudentA() {
    }
    
    @Override
    public String toString() {
        return "A:" + name + "->" +  B;
    }
    
}

@Component
public class StudentB {
    @Value(value="我是B")
    private String name;
    @Autowired
    private StudentC C;
    
    public StudentB() {
    }

    @Override
    public String toString() {
        return "B:" + name + "->" + C;
    }
    
}
@Component
public class StudentC {
    @Value(value="我是C")
    private String name;
    @Autowired
    private StudentD D;
    
    @Autowired
    private StudentA A;
    
    public StudentC() {
    }

    @Override
    public String toString() {
        return "C:" + name + "->" + D;
    }
    
}

public class StudentD {
    private String name;
    
    public StudentD() {
    }
    
    @Override
    public String toString() {
        return "D:" + name;
    }
    
}

Xml配置:

<SimpleSpring>
    <bean class="com.zc.ioc.demo.StudentD">
        <property name="name" value="我是D"></property>
    </bean>
</SimpleSpring>

主函数:

public static void main(String[] args) throws BeansException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml");
        StudentD studentD = applicationContext.getBean(StudentD.class);
        System.out.println(studentD);
        
        applicationContext= new AnnotationConfigApplicationContext("com.zc.moedl");
        StudentA studentA = applicationContext.getBean(StudentA.class);
        System.out.println(studentA);
}

输出结果:


image.png

看起来是没有问题了,但是如果Xml和注解之间的出现顺序不同,结果也会不一样,还得仔细考虑,而且我做的这个是延迟注入,只有在getBean的时候才会完成最后的注入,并且若是注解中需要一个Xml的bean注入,而xml的这个bean又依赖于注解中的一个bean,那么这套方法是不可行的!

参考:
模拟Sping,实现其IOC和AOP核心(一)
用 JDK 动态代理 API 写的简单的 AOP 程序

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容