【Java】模拟Sping,实现其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() {
@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都完成了注入,那么其注入过程会非常简单,但若是,彼此之间的依赖关系比较复杂,你中有我,我中有你,会形成一个环形闭环,陷入死循环,这就是循环依赖!
循环依赖
这里有四个类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;
} @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); }
结果是:
或者这样使用:
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的实现我会在下一篇给出。
【Java】模拟Sping,实现其IOC和AOP核心(一)的更多相关文章
- 【Java】模拟Sping,实现其IOC和AOP核心(二)
接着上一篇,在上一篇完成了有关IOC的注解实现,这一篇用XML的方式实现IOC,并且完成AOP. 简易的IOC框图 注解的方式实现了左边的分支,那么就剩下右边的XML分支: XmlContext:这个 ...
- 请简要介绍Sping MVC、IoC和AOP
Sping MVC是在Spring框架上发展起来的框架,它提供了构建Web应用程序的全功能MVC模块,使用了Spring可插入的MVC架构,可以自由的选择各个模块所使用的架构,非常灵活.Spring ...
- 模拟Java-Sping,实现其IOC和AOP核心
简易的IOC框图 注解的方式实现了左边的分支,那么就剩下右边的XML分支: XmlContext:这个类是也是AbstractApplicationContext的子类,和AnnotationCont ...
- J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP
J2EE进阶(十四)超详细的Java后台开发面试题之Spring IOC与AOP 前言 搜狐畅游笔试题中有一道问答题涉及到回答谈谈对Spring IOC与AOP的理解.特将相关内容进行整理. ...
- Sping框架的IOC特性 悲观锁、乐观锁 Spring的AOP特性
Sping框架的IOC特性 IOC(Inversion of Control):控制反转 以下以课程与老师的安排来介绍控制反转. 一个合理的课程编排系统应该围绕培训的内容为核心,而不应该以具体的培训老 ...
- java简单例子介绍IOC和AOP
IOC和AOP的一些基本概念 介绍 IOC 一.什么是IOC IoC就是Inversion of Control,控制反转.在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内 ...
- 反射应用--IOC和AOP
反射最大的价值就是用来写框架,下面贴出自己的3篇代码,模拟实现SPING框架的bean工厂,IOC,AOP.当然这里重点是在利用反射实现功能,为了图方便,我用的是Properties文件,关于XML后 ...
- Spring中IOC和AOP的详细解释(转)
原文链接:Spring中IOC和AOP的详细解释 我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂 ...
- spring IOC 和AOP 方面
spring 的2大核心 是Ioc 和 aop spring的依赖注入:在程序运行期间,由外部容器动态的将依赖对象注入到组件中 IOC: 实例化spring容器的二种方法 第一种:在类路径下寻找配 ...
随机推荐
- esp32的GPIO操作
对于任何一款芯片,GPIO接口是其最基本的组成部分,也是一款芯片入门的最基本操作,下面论述下 关于esp32开发版的GPIO操作,本文中重点讲解下 关于如何创建eclipse工程,并通过eclipse ...
- ubuntu16.04下idea、webstorm等开发工具不能输入中文问题
问题: ubuntu16.04下idea.webstorm开发工具不能输入中文,就算切换到中文输入法输入的也是英文字母. 解决方案: 1.vim打开开发工具的启动文件(idea下就是idea.sh) ...
- 《深入理解JAVA虚拟机》——学习笔记
JVM内存模型以及分区 JVM内存分为: 1.方法区:线程共享的区域,存储已经被虚拟机加载的类信息.常量.静态变量.即时编译器编译后的代码等数据 2.堆:线程共享的区域,存储对象实例,以及给数组分配的 ...
- 4.html基础标签:表单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Git入门基础详情教程
前言 写了一篇文章<一篇文章了解Github和Git教程>还觉得不错,继续写了<为了Github默默付出,我想了解你>,那么继续写Git 基础知识. Git 官网:https: ...
- HoloLens开发手记 - HoloLens真机上手简评
千呼万唤始出来,终于今天拿到了HoloLens真机. 使用体验 使用自带的应用录制了一段使用视频,如下 设备概览 包装盒 本体 试戴 实际效果 GalaxyExplorer试玩 全息图像贴到现实场景表 ...
- javascript 最全面的数组操作合集
一.数组添加.删除.替换.截取操作 1.arr.unshift(1) 在数组头部添加一个元素 1 (直接改变原数组,返回值为添加元素后数组的length) 2.arr.shift() 在数组的头部删除 ...
- jenkins 集成钉钉机器人通知
公司使用钉钉做为公司内部的通讯工具,所以想通过Jenkins发布完成以后通过钉钉来通知大家,研究发现钉钉提供机器人,所以我把机器人集成进来通知相关人员. 1.创建通知人群组,添加机器人(钉钉默认自带了 ...
- TypeEncodings
官网链接: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Ar ...
- 手把手教您定制化Centos6.x安装界面
1.获取安装界面代码 挂载image/install.img:mount image/install.img /mnt/5 -o loop 复制挂载后的代码至self_intall ...