Java 模拟Spring,实现IOC和AOP的核心(一)

在这里我要实现的是Spring的IOC和AOP的核心,而且有关IOC的实现,注解+XML能混合使用!

参考资料:

IOC:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

AOP:Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

(以上是度娘给出的说法,可以看到这样的说法很不容易让人理解,过于官方化,下面是我的理解)

IOC:我们平时在写Java程序时,总要通过new的方式来产生一个对象,对象的生死存亡是与我们直接挂钩的,我们需要它时,就new一个,不需要他时就通过GC帮我们回收;控制反转的意思就是将对象的生死权交给第三方,由第三方来生成和销毁对象,而我们只在需要时从第三方手中取获取,其余不管,这样,对象的控制权就在第三方手里,我们只有使用权!这就是所谓的控制反转!

在Spring中,提供了XML文件配置和注解的方式来向第三方表明我们需要第三方帮我们创建什么对象,Spring就是这个第三方!它负责通过XML文件的解析或者包扫描的方式,找到我们给出的映射关系,利用反射机制,在其内部帮我们“new”出对象,再存储起来,供我们使用!

AOP :就是动态代理的体现,在Spring中就是利用JDKProxy或者CGLibProxy技术,对方法进行拦截!

比如说有一个叫做fun()的方法,我们在其执行前后对其拦截:

在这里插入图片描述
就像这样,fun看成是纵向的线,那么就相当于用平面把这条线截断了!

有了上面的铺垫,我们可以大概知道,Sping的IOC和AOP可以帮我们创建并管理对象,可以对对象的方法进行拦截,那么这两个技术合在一起,就可以达到自动帮我们创建、管理、并对对象进行拦截!

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

模拟的IOC框图

在这里插入图片描述
这是我简化后的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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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 : classNameprotected static final Map<String, BeanElement> beanMap; // key : className value : Beanprotected 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,就必须通过反射机制来获取,反射机制就要得到类或者类名称,那么就先得到符合要求的类,这里就要用到包扫描。

首先是对类的注解:
@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 {@Autowiredprivate TestB testB;@Value(value="直接赋值")private String member;public TestA() {}}@Component(id = "testB")
public class TestB {private int a;private TestA testA;@Autowiredpublic 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();}@Beanpublic 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() {@Overridepublic 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) {// TODOe.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) {// TODOe.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中获取符合要求的beanresult[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);}@Overridepublic 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;}@Overridepublic boolean isDI() {return DI;}@Override@SuppressWarnings("unchecked")public <E> E getProxy() {return (E) proxy;}}

MethodBuffer 类如下:

/***    带有@Bean注解的方法封装类*/
public class MethodBuffer {private String id; // bean的idprivate 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都完成了注入,那么其注入过程会非常简单,但若是,彼此之间的依赖关系比较复杂,你中有我,我中有你,会形成一个环形闭环,陷入死循环,这就是循环依赖!

循环依赖

在这里插入图片描述
这里有四个类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的状态是完成注入,自然也就不会产生闭环!

在这里插入图片描述
所以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;}@Overridepublic 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();}@Overridepublic <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中的Beanarg = 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;@Autowiredprivate StudentB B;public StudentA() {}@Overridepublic String toString() {return "A:" + name + "->" +  B;}}@Component
public class StudentB {@Value(value="我是B")private String name;@Autowiredprivate StudentC C;public StudentB() {}@Overridepublic String toString() {return "B:" + name + "->" + C;}}@Component
public class StudentC {@Value(value="我是C")private String name;@Autowiredprivate StudentA A;public StudentC() {}@Overridepublic String toString() {return "C:" + name;}}public class StudentD {private String name;@Autowiredprivate StudentA A;public StudentD(String name) {this.name = name;}@Overridepublic 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);}

结果是:

在这里插入图片描述
或者这样使用:

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);}

结果:

在这里插入图片描述

有关XML方式以及AOP的实现我会在下一篇给出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/763761.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java项目:70 ssm小学生课外知识学习网站+vue

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 管理员&#xff1b;首页、个人中心、医护人员管理、科室管理、病人管理、病房管理、病人信息管理、病历管理、医嘱管理、手术安排管理、药品信…

2024-3-17上机C++刷题

题目一: 反序数_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/e0d06e79efa44785be5b2ec6e66ba898?tpId60&tqId31035&tPage2&ru/kaoyan/retest/1001&qru/ta/tsing-kaoyan/question-ranking #include<iostream> using namespace s…

进度图画法

exce表格进度图画法&#xff0c;体现在条形图以及“格子”的空间的填充两种办法。 1.excel表格画进度图 备注&#xff1a;表格照着就是可以了&#xff0c;主要是画直线的办法 在形状的下拉菜单中选择直线&#xff0c;按住shift&#xff08;可以画直线&#xff09; 画直线后&a…

【数据结构与算法】(18):树形选择排序:按照锦标赛的思想进行排序

&#x1f921;博客主页&#xff1a;Code_文晓 &#x1f970;本文专栏&#xff1a;数据结构与算法 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多数据结构与算法点击专栏链接查看&…

【系统架构师】-计算机网络

1、网络的划分 网络性能指标&#xff1a;速率、带宽(频带宽度或传送线路速率)、吞吐量、时延、往返时间、利用率。 网络非性能指标&#xff1a;费用、质量、标准化、可靠性、可扩展性、可升级性、易管理性和可维护性。 总线型(利用率低、干扰大、价格低)、 星型(交换机转发形…

【并查集专题】【蓝桥杯备考训练】:网络分析、奶酪、合并集合、连通块中点的数量、格子游戏【已更新完成】

目录 1、网络分析&#xff08;第十一届蓝桥杯省赛第一场C A组/B组&#xff09; 2、奶酪&#xff08;NOIP2017提高组&#xff09; 3、合并集合&#xff08;模板&#xff09; 4、连通块中点的数量&#xff08;模板&#xff09; 5、格子游戏&#xff08;《信息学奥赛一本通》…

flink1.18.0报错 an implicit exists from scala.Int => java.lang.Integer, but

完整报错 type mismatch;found : Int(100)required: Object Note: an implicit exists from scala.Int > java.lang.Integer, but methods inherited from Object are rendered ambiguous. This is to avoid a blanket implicit which would convert any scala.Int to a…

【Java反序列化】CommonsCollections-CC1链分析

前言 好几天没发博文了&#xff0c;偷偷憋了个大的——CC1链分析&#xff0c;手撸了一遍代码。虽然说&#xff0c;这个链很老了&#xff0c;但还是花费了我一段时间去消化吸收&#xff0c;那么接下来&#xff0c;我会简洁的介绍下整个链的利用过程&#xff0c;还有哪些不理解的…

初识C++(一)

目录 一、什么是C 二、关键字&#xff1a; 三、命名空间 &#xff1a; 1. C语言存在的问题&#xff1a; 2. namespace关键字&#xff1a; 3. 注意点&#xff1a; 4.使用命名空间分为三种&#xff1a; 四、输入输出&#xff1a; 五、缺省函数&#xff1a; 1. 什么是缺省…

【Linux】进程地址空间——有这篇就够了

前言 在我们学习C语言或者C时肯定都听过老师讲过地址的概念而且老师肯定还会讲栈区、堆区等区域的概念&#xff0c;那么这个地址是指的物理内存地址吗&#xff1f;这里这些区域又是如何划分的呢&#xff1f; 我们在使用C语言的malloc或者C的new函数开辟空间时&#xff0c;开辟…

栈——数据结构——day4

栈的定义 栈是限定仅在一段进行插入和删除操作的线性表。 我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表&#xff0c;简称LIFO结构。 栈的插入操作&#xff0c;叫作进栈&#…

【收藏】什么是API测试?这是我见过的最全的测试指南!

在最近的部署中&#xff0c;当我被问到“什么是API测试&#xff1f;”时&#xff0c;我正与客户一起制定API测试策略。那时我突然意识到&#xff0c;要描述API测试居然是一件如此具有挑战性的事情&#xff0c;即使你如实地描述了它&#xff0c;也往往听起来很无聊和复杂。 好吧…

第十二届蓝桥杯省赛CC++ 研究生组

十二届省赛题 第十二届蓝桥杯省赛C&C 研究生组-卡片 第十二届蓝桥杯省赛C&C 研究生组-直线 第十二届蓝桥杯省赛C&C 研究生组-货物摆放 第十二届蓝桥杯省赛C&C 研究生组-路径 第十二届蓝桥杯省赛C&C 研究生组-时间显示 第十二届蓝桥杯省赛C&C 研究生组…

AI PPT生成工具 V1.0.0

AI PPT是一款高效快速的PPT生成工具&#xff0c;能够一键生成符合相关主题的PPT文件&#xff0c;大大提高工作效率。生成的PPT内容专业、细致、实用。 软件特点 免费无广告&#xff0c;简单易用&#xff0c;快速高效&#xff0c;提高工作效率 一键生成相关主题的标题、大纲、…

TCP | TCP协议格式 | 三次握手

1.TCP协议 为什么需要 TCP 协议 &#xff1f;TCP 工作在哪一层&#xff1f; IP网络层是不可靠的&#xff0c;TCP工作在传输层&#xff0c;保证数据传输的可靠性。 TCP全称为 “传输控制协议&#xff08;Transmission Control Protocol”&#xff09;。 TCP 是面向连接的、可靠…

YOLOV9训练自己的数据集

1.代码下载地址GitHub - WongKinYiu/yolov9: Implementation of paper - YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information 2.准备自己的数据集 这里数据集我以SAR数据集为例 具体的下载链接如下所示&#xff1a; 链接&#xff1a;https:/…

备战蓝桥杯Day34 - 每日一题

题目描述 解题思路 1.输入数据n&#xff0c;并将字符串类型转换成整数类型 2.求出输入n是2的几次幂&#xff08;调用math库中的求对数的方法&#xff09;&#xff0c;在下面的循环中要用到 3.定义sum和&#xff0c;将抽取到的牌的总数加起来存储 4.count 0 # 记录 2 的第几…

算法打卡day20|二叉树篇09|Leetcode 669. 修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.把二叉搜索树转换为累加树

算法题 Leetcode 669. 修剪二叉搜索树 题目链接:669. 修剪二叉搜索树 大佬视频讲解&#xff1a;修剪二叉搜索树视频讲解 个人思路 把这道题想复杂了&#xff0c;还想着如何去重构树 解法 递归法 依旧递归三步走 1.确定递归函数的参数以及返回值 这题递归需要返回值&#…

探索人工智能基础:从概念到应用【文末送书-42】

文章目录 人工智能概念人工智能基础【文末送书-42】 人工智能概念 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;作为当今科技领域的热门话题&#xff0c;已经深刻地影响着我们的生活和工作。但是&#xff0c;要理解人工智能&#xff0c;我们首先需…

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器 文章目录 【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器一、环境说明二、安装配置步骤1.下载完成后&#xff0c;传至服务器或者本机并解压至C:/Program Files/目录下2.打开PowerShell终端3.进入到包含ssh可执行exe文件的文…