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.rhkb.cn/news/281881.html

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

相关文章

OpenLayers基础教程——使用WebGL加载海量数据(1)

1、前言 最近遇到一个问题&#xff1a;如何在OpenLayers中高效加载海量的场强点&#xff1f;由于项目中的一些要求&#xff0c;不能使用聚合的方法加载。一番搜索之后发现&#xff1a;OpenLayers中有一个WebGLPoints类&#xff0c;使用该类可以轻松应对几十万的数据量&#xf…

3D高斯泼溅的崛起

沉浸式媒体领域正在以前所未有的速度发展&#xff0c;其中 3D 高斯溅射成为一项关键突破。 这项技术在广泛的应用中看起来非常有前景&#xff0c;并且可能会彻底改变我们未来创建数字环境以及与数字环境交互的方式。 在本文中&#xff0c;我们将通过与摄影测量和 NeRF 等前辈进…

【软考高项】十五、信息系统工程之系统集成

1、集成基础 定义&#xff1a;通过硬件平台、网络通信平台、数据库平台、工具平台、应用软件平台将各类资源有机、高效地集成到一起&#xff0c;形成一个完整的工作台面 基本原则包括:开放性、结构化、先进性和主流化 2、网络集成 包括&#xff1a;传输子系统、交换子系统、…

Google的MELON: 通过未定位图像重建精确3D模型的突破性算法

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

QML TextField 默认无法鼠标选中内容

1.import QtQuick.Controls 2.0 后的TextField默认无法选中内容如下图&#xff1a; 2.增加属性设置 selectByMouse: true 可以选中内容了 TextField{ selectByMouse: true text:"1234567890987654321" } 效果如下:

安装调试kotti_ai:AI+互联网企业级部署应用软件包@riscv+OpenKylin

先上结论&#xff1a;riscvOpenKylin可以安装pyramidkottikotti_ai 但是paddle_serving_client无法安装&#xff0c;项目的AI实现部分需要改用其它方法&#xff0c;比如onnx。最终onnx也没有装成&#xff0c;只好用飞桨自己的推理。 安装kotti pip install kotti 安装kotti和…

【Git】第一课:Git的介绍

简介 什么是Git? Git是一个开源的分布式版本控制系统&#xff0c;用于跟踪代码的改变和协同开发。它最初由Linus Torvalds为了管理Linux内核开发而创建&#xff0c;现已成为开源软件开发中最流行的版本控制系统&#xff0c;没有之一。Git允许多人同时在不同的分支上工作&…

将main打包成jar;idea打包main为jar包运行

将main打包成jar&#xff1b;idea打包main为jar包运行 适用场景&#xff1a;可以封装一些小工具。 配置jar Maven中添加 <packaging>jar</packaging>将其打包为jar。 设置运行入口main 编译jar 看到jar输出 运行效果&#xff1a; 其中&#xff0c;三方依赖也被…

DEYOv2: Rank Feature with Greedy Matchingfor End-to-End Object Detection

摘要 与前代类似&#xff0c; DEYOv2 采用渐进式推理方法 来加速模型训练并提高性能。该研究深入探讨了一对一匹配在优化器中的局限性&#xff0c;并提出了有效解决该问题的解决方案&#xff0c;如Rank 特征和贪婪匹配 。这种方法使DEYOv2的第三阶段能够最大限度地从第一和第二…

【IEEE】Multimodal Machine Learning: A Survey and Taxonomy

不废话&#xff0c;先上思维导图&#xff0c;哈哈哈&#xff01; 论文题目Machine Learning: A Survey and Taxonomy作者Tadas Baltrusaitis , Chaitanya Ahuja , and Louis-Philippe Morency状态已读完会议或者期刊名称IEEE TRANSACTIONS ON PATTERN ANALYSIS AND MACHINE IN…

电机与直线模组选型

一。普通电机选型 普通电机选型&#xff08;一&#xff09; 三相异步电机 定子&#xff1a;产生旋转磁场 转子&#xff1a;切割磁场&#xff0c;产生洛伦兹力 结构简单&#xff0c;成本低&#xff0c;稳定 效率较低&#xff0c;转速不稳定 N60f/P 定子旋转速度&#xff1a;150…

ubuntu系统下如何使用vscode编译和调试#小白入门#

编程环境&#xff1a;ubuntu系统为18.04.1&#xff0c;vscode版本为1.66.2 一、VSCode切换中文显示&#xff1a; 1、vscode安装完成后启动,在左侧externsions中搜索“简体中文”插件&#xff0c;并完成安装&#xff1a; 2、选择右下角齿轮形状的"Manage"&#xff…

运动想象 (MI) 迁移学习系列 (14) : EEGNet-Fine tuning

运动想象迁移学习系列:EEGNet-Fine tuning 0. 引言1. 主要贡献2. 提出的方法2.1 EEGNet框架2.2 微调 3. 实验结果3.1 各模型整体分类结果3.2 算法复杂度比较3.3 不同微调方法比较 4. 总结欢迎来稿 论文地址&#xff1a;https://www.nature.com/articles/s41598-021-99114-1#cit…

【HTTP完全注解】范围请求

范围请求 范围请求是HTTP的一种内容协商机制&#xff0c;该机制允许客户端只请求资源的部分内容。范围请求在传送大的媒体文件&#xff0c;或者与文件下载的断点续传功能搭配使用时非常有用。 范围请求的工作流程 范围请求通过在HTTP请求标头Range中表明需要请求的部分资源的…

可视化日记——极坐标绘制雷达图

目录 一、创建极坐标 二、数据集准备 三、划分角度 四、指定半径 五、绘制 一、创建极坐标 Python中没有直接画雷达图的函数&#xff0c;若要绘制需要先创建画布和极坐标轴域&#xff0c;再设定角度与半径的参数&#xff08;极坐标中角度与半径确定一个点的位置&#xff…

专业135+总分400+重庆邮电大学801信号与系统考研经验重邮电子信息与通信工程,真题,大纲,参考书。

今年分数出来还是比较满意&#xff0c;专业801信号与系统135&#xff0c;总分400&#xff0c;没想到自己也可以考出400以上的分数&#xff0c;一年的努力付出都是值得的&#xff0c;总结一下自己的复习心得&#xff0c;希望对大家复习有所帮助。专业课&#xff1a;&#xff08;…

(ROOT)KAFKA详解

生产篇 使用 /** Licensed to the Apache Software Foundation (ASF) under one or more* contributor license agreements. See the NOTICE file distributed with* this work for additional information regarding copyright ownership.* The ASF licenses this file to Y…

设计模式学习笔记 - 设计原则与思想总结:2.运用学过的设计原则和思想完善之前性能计数器项目

概述 在 《设计原则 - 10.实战&#xff1a;针对非业务的通用框架开发&#xff0c;如何做需求分析和设计及如何实现一个支持各种统计规则的性能计数器》中&#xff0c;我们讲解了如何对一个性能计数器框架进行分析、设计与实现&#xff0c;并且实践了一些设计原则和设计思想。当…

macOS 通过 MacPorts 正确安装 MySQL 同时解决无法连接问题

如果你通过 sudo port install 命令正常安装了 MySQL&#xff0c;再通过 sudo port load 命令启动了 MySQL Server&#xff0c;此刻却发现使用 Navicat 之类的 GUI 软件无法连接&#xff0c;始终返回无法连接到 127.0.0.1 服务器。这是一个小坑&#xff0c;因为他默认使用了 So…

Git——GitHub远端协作详解

目录 Git&GitHub1、将内容Push到GitHub上1.1、在GitHub上创建新项目1.2、upstream1.3、如果不想要相同的分支名称 2、Pull下载更新2.1、Fetch指令2.2、Fetch原理2.3、Pull指令2.4、PullRebase 3、为什么有时候推不上去3.1、问题复现3.2、解决方案一&#xff1a;先拉再推3.3…