我们先来给出一个复杂的类;这个类的成员是类类型的成员。
public class A {
private B b;
private C c;
public A() {
}
public void doSomething() {
System.out.println("A类的b成员:" + b);
System.out.println("A类的c成员:" + c);
}
}
public class B {
private C c;
public B() {
}
public void doSomething() {
System.out.println("输出B类的c成员:" + c);
}
}
public class C {
public C() {
}
public void doSomething() {
System.out.println("C类没有成员");
}
}
对于上述类的注入,不能仅仅是简简单单的newInstance()就可以实现的,因为A类的成员也是类类型的成员。
现在考虑这么一种思路:将某应用所涉及的类及其对象,都集中存储到一个集合(池子)中;凡是在这个集合中的类,尤其是这些类的成员类型也在这个池子中,则,这些成员的初始化都从池子中的对象还给予!那么,A类的B类类型成员,C类类型成员,还有B类中的C类类型成员,都由一段代码自动完成“初始化”。以后使用这些类的对象时,一律从这个集合(池子)中取。
构建一个容器(上下文),在这个容器中存储类及其对象;在使用这些类的对象时,基本上都是从这个容器中获取的;这些类的成员,若其类型也在容器中,则,它们将被自动初始化,且用容器中的对象完成初始化。需要说明的是,对于类中的成员的初始化选择,应该由用户决定。
选择权的实现有两种具体的方法:
1、通过XML配置映射关系;
2、通过注解配置映射关系。
这里,我们采用的是注解方式实现。我们增加一个Component注解,
@Retention(RUNTIME)
@Target(TYPE)
public @interface Component {
String name() default "";
}
Component注解:这是对类的注解。只要一个类有Component注解,那么表示这个类将被放入Bean容器中。
我们通过包扫描技术包扫描实现上述的想法:
public void scanBeanByPackage(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (klass.isPrimitive()
|| klass.isInterface()
|| klass.isAnnotation()
|| klass.isEnum()
|| klass.isArray()
|| !klass.isAnnotationPresent(Component.class)) {
return;
}
Object object = null;
try {
object = klass.newInstance();
BeanDefinition bd = new BeanDefinition();
bd.setKlass(klass);
bd.setObject(object);
beanPool.put(klass.getName(), bd);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.packageScanner(packageName);
}
上述的beanPool是一个map集合,键是类的名字,值是自己定义的BeanDefinition类,
public class BeanDefinition {
private Class<?> klass;
private Object object;
//是已经完成注入
private boolean inject;
//是否是单例的
private boolean singleton;
public BeanDefinition() {
this.singleton = true;
this.inject = false;
}
Class<?> getKlass() {
return klass;
}
void setKlass(Class<?> klass) {
this.klass = klass;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
boolean isInject() {
return inject;
}
void setInject(boolean inject) {
this.inject = inject;
}
boolean isSingleton() {
return singleton;
}
void setSingleton(boolean singleton) {
this.singleton = singleton;
}
}
上述操作完成了对有@Componnet注解的类的实例化,并将其放到池子中的操作,但是,没有实现对这个实例中成员的注入工作。对于类类型的成员的初始化,我们应该再增加一个Autowired注解。有这个注解的成员,应该被自动找到依赖关系并进行实例化。
@Retention(RUNTIME)
@Target(FIELD)
public @interface Autowired {
String name() default "";
}
除了用注解方式实现依赖关系的描述外,还可以用XML方式配置依赖
关系;这种情况下,对于Bean的发现,可能需要多次“包扫描”或者“XML配置解析”;那么,在没有完全扫描完之前,是没有办法确定依赖关系的完整性的!因此,在包扫描或解析XML配置时,不能急于处理依赖关系,即,不能急于完成“注入”工作!完成注入的最佳实际,应该延迟到GetBean()时!
这可以称之为:懒汉模式【懒汉模式、饿汉模式】。
所以说,我们应该完善getBean()方法;
public static <T> T getBean(Class<?> klass) {
return getBean(klass.getName());
}
@SuppressWarnings("unchecked")
public static <T> T getBean(String klassName) {
BeanDefinition beanDefinition = beanPool.get(klassName);
if (beanDefinition == null) {
System.out.println(klassName + "没有找到");
return null;
}
Object object = beanDefinition.getObject();
if (!beanDefinition.isSingleton()) {
Class<?> klass = beanDefinition.getKlass();
try {
Object obj =klass.newInstance();
beanDefinition.setObject(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (!beanDefinition.isInject() || !beanDefinition.isSingleton()) {
beanDefinition.setInject(true);
// TODO 这里应该开始实现对类的注入;
}
return (T) object;
}
上述,TODO可以看到,我们这时完成的因该是对成员的注入,这时,已经把所有提供的类全部进行了包扫描,并且已经放到了beanPool中,我们只需要将他们取出来,并进行赋值。
Autowired注解:对要注入的成员的注解(也可以通过set方法得到该类)。一个成员有该注解,那么说明这个成员需要被注入。有可能该成员是一个类类型的成员,其的内部还有一个成员也有Autowired注解,也需要被注入但是目前还没有被注入,那么我们就需要先将那个内部成员注入,之后才能对该成员进行注入。这时,这个Autowired注解就对应了一个Component注解。这就会涉及到一个很复杂的问题:循环注入。各个类之间彼此循环依赖,这个问题我们在后面解决。
现在我们开始完成 TODO的操作,
private static void inject(BeanDefinition beanDefinition) {
// 这里是对成员的注入;
injectField(beanDefinition);
// 这里是对方法的注入,
injectMethod(beanDefinition);
}
private static void injectField(BeanDefinition beanDefinition) {
Class<?> klass = beanDefinition.getKlass();
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Autowired.class)) {
continue ;
}
Object value = getBean(field.getType());
field.setAccessible(true);
try {
field.set(beanDefinition.getObject(), value);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
private static void injectMethod(BeanDefinition beanDefinition) {
Class<?> klass = beanDefinition.getKlass();
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Autowired.class) || method.getParameterCount() != 1
|| !method.getName().startsWith("set")) {
continue ;
}
Parameter[] parameters = method.getParameters();
Class<?> paraKlass = parameters[0].getType();
Object obj = getBean(paraKlass);
try {
// 通过反射机制执行该方法
method.invoke(beanDefinition.getObject(),obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
上述的注入方式包含了成员的注入和方法的注入。
对于成员的注入,有一步关键性的操作是:field.setAccessible(true);,但是这个操作破坏了private的权限,使得private成员可以被取得到。对于方法的注入,我们只能注入set方法,且参数是单参的,并且这个方法的权限是public的。
由此,我们已经实现了将一个类包括类里面的类类型的成员,注入到beanPool里面。其中,类和作为成员的类之间最大的区别在于前者实在scanPackage的时候注入的,后者是在getBean()的时候注入的。但是,我更倾向于Autowired注解用到set方法身上,这样可以不破坏private本身的权限。但是不论怎样,我们都可以在getBean()的时候获取到该类的对象。
其实,在getBean()方法中,有一个细节,我们已经实现了单例和多例。
Object object = beanDefinition.getObject();
// 检测是否是单例的
if (!beanDefinition.isSingleton()) {
// 非单例的,重新进行new操作,并且覆盖之前的object,
Class<?> klass = beanDefinition.getKlass();
try {
Object obj =klass.newInstance();
beanDefinition.setObject(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
上述的Autowired注解存在缺陷:Autowired只能获取池子中的对象,而池中对象都是需要给对应的类以@Component注解;对于不可更改的Jar包中的类,就没有办法增加@Component注解,也就不能实现“注入”操作。
Bean注解:
这时,Bean注解的存在就是为了解决这样的问题。
给一个方法增加Bean注解,而将这个方法的返回值对象和返回值对象类型作为键值对,存储到池子中!在反射机制执行该方法之后,我们就可以实现将一个类注入到池子中。
Bean注解的第二个应用场合:
若相关类没有提供可用的构造方法;所谓的没有提供可用的构造方法包括相关构造方法是private的,或者,构造方法不能直接调用,或者,构造方法不能直接生成对象!在这种情况下,由于对于Component注解的处理是通过调用相关类的无参构造产生的,那么,对于上述情况,就不能产生
这个类的对象!此时,可以通过Bean注解,调用合适的获取该类对象的方法,取得这个类的对象,并加入BeanPool中!
Bean注解的第三个应用场合:
相关类的对象,不是简单无参构造就能直接使用的;意思是:这个类虽然存在无参构造,但是,无参构造出来的对象不能直接使用。那么,在这种情况下,通过Bean注解的方法,完成其对象所必须的基础数据,从而使得该对象可用!
有Bean注解且含有参数的方法的处理:这里的关键是:方法所依赖的参数是否满足?如果满足,则,该方法是可以执行并得到一个Bean的。如果多个方法的参数形成了循环依赖关系,则,应将这种循环依赖环,告知用户。其核心是:检测依赖是否满足。这里可以先考虑构造MethodDefinition类。
MethodDefinition中应该存储方法反射执行所需要的内容:
1、对象;
2、方法本身;
3、参数对象集合。
这里最难解决的问题是:参数所需要的对象可能是随机满足的。
将所有不满足依赖关系的方法中的参数形成一个Map,此Map的键是参数类型,而值是MethodDefinition所形成的List。再准备一个满足要求的MethodDefinition的列表。
综上所述:
准备3种东西:
1、不能满足依赖关系的MethodDefinition列表:列表1;
2、参数类型为键,MethodDefinition为值的Map;
3、满足了依赖关系的MethodDefinition列表:列表2。
如下图所示:
大致思路:对于有Bean注解的方法,先获取其参数类型,检测是否在beanPool里面存在,如果存在,则继续获取其参数,如果不存在,将该方法放到unOkMethods里面,并将该参数的类型作为键,该方法放到dependenceMethodPool中以该参数为键所对应的值里面,知道全部遍历完成。如果方法全部可以获取到,那么,直接将该方法放到okMethods中,等待后续的执行。我们需要在BeanFactory类里面,只要put一次,我们就需要检测,是否有参数满足可以获取到。一旦可以获取到,就把该参数从dependenceMethodPool中删去。如果某个方法的参数全部可以获取到,直接将unOkMethods里面的该方法删除,将该方法加入到okMethods列表里面。在包扫描所有的类后,我们需要执行这些方法。
代码如下:
public class MethodDependence {
private final static Map<Class<?>, List<MethodDefinition>> dependenceMethodPool = new HashMap<Class<?>, List<MethodDefinition>>();
private static final List<MethodDefinition> okMethods = new LinkedList<MethodDefinition>();
private static final List<MethodDefinition> unOkMethods = new LinkedList<MethodDefinition>();
MethodDependence() {
}
static void addUnInvokeMethod(Map<Class<?>, Integer> mapPara,MethodDefinition methodDefinition) {
unOkMethods.add(methodDefinition);
for (Class<?> para : mapPara.keySet() ) {
if (!dependenceMethodPool.containsKey(para)) {
List<MethodDefinition> methodDefinitionList = new LinkedList<MethodDefinition>();
dependenceMethodPool.put(para, methodDefinitionList);
}
List<MethodDefinition> methodDefinitionList = dependenceMethodPool.get(para);
methodDefinitionList.add(methodDefinition);
}
}
static void checkMethod(Class<?> beanClass) {
List<MethodDefinition> methodList = dependenceMethodPool.get(beanClass);
if (methodList.isEmpty() || methodList == null) {
dependenceMethodPool.remove(beanClass);
return ;
}
for (MethodDefinition method : methodList ) {
if (method.sub() <= 0) {
unOkMethods.remove(method);
okMethods.add(method);
}
}
dependenceMethodPool.remove(beanClass);
}
static void invokeMethod() {
while(!okMethods.isEmpty()) {
MethodDefinition methodDefinition = okMethods.remove(0);
Method method = methodDefinition.getMethod();
Object object = methodDefinition.getObject();
Class<?> klass = object.getClass();
BeanFactory.invokeMethodWithPara(klass, method, object);
}
}
}
// BeanFactory类
private static void dealMethodWithPara(Class<?> klass, Method method,Object object) {
Map<Class<?>, Integer> paraMap = getMethodPara(method);
if (paraMap.isEmpty()) {
invokeMethodWithPara(klass,method,object);
return ;
}
MethodDefinition methodDefinition = new MethodDefinition();
methodDefinition.setMethod(method);
methodDefinition.setObject(object);
methodDefinition.setParaCount(paraMap.size());
MethodDependence.addUnInvokeMethod(paraMap, methodDefinition);
}
private static void dealBean(BeanDefinition beanDefinition) {
Class<?> klass = beanDefinition.getKlass();
Method[] methods = klass.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Bean.class) ){
continue ;
}
if ( method.getParameterCount() > 0) {
// 处理带参的方法;
dealMethodWithPara(klass,method,beanDefinition.getObject());
continue ;
}
Object object = beanDefinition.getObject();
Class<?> returnKlass = method.getReturnType();
try {
Object obj = method.invoke(object,new Object[] {});
BeanDefinition bean = new BeanDefinition();
bean.setKlass(returnKlass);
bean.setObject(obj);
beanPool.put(returnKlass.getName(), bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
// 这里的Integer类型可以是随便的,因为这里只关心键Class>类型。
private static Map<Class<?>, Integer> getMethodPara(Method method) {
Class<?>[] parametertypes = method.getParameterTypes();
Map<Class<?>, Integer> paraMap = new HashMap<Class<?>, Integer>();
List<Class<?>> okParas = new LinkedList<Class<?>>();
for (Class<?> parameterType : parametertypes) {
paraMap.put(parameterType, 0);
if (beanPool.get(parameterType.getName()) != null) {
okParas.add(parameterType);
}
}
for (Class<?> one : okParas) {
paraMap.remove(one);
}
return paraMap;
}
public static void invokeMethodWithPara(Class<?> klass,Method method,Object object) {
Class<?>[] paraTypes = method.getParameterTypes();
Object[] objects = new Object[paraTypes.length];
for (int index = 0;index < paraTypes.length;index++) {
objects[index] = getBean(paraTypes[index]);
}
Class<?> returnPara = method.getReturnType();
BeanDefinition bean = new BeanDefinition();
try {
Object obj = method.invoke(object, objects);
bean.setObject(obj);
bean.setKlass(returnPara);
beanPool.put(returnPara.getName(), bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public class MethodDefinition {
private Object object;
private Method method;
private int paraCount;
MethodDefinition() {
this.paraCount = 0;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
Method getMethod() {
return method;
}
void setMethod(Method method) {
this.method = method;
}
void setParaCount(int paraCount) {
this.paraCount = paraCount;
}
int sub() {
return --this.paraCount;
}
int getParaCount() {
return paraCount;
}
}
1、遇到一个带参方法,先检测其所有参数;
1.1、若参数满足要求,则,暂不处理(可以将count–),这里的count是“参数个数”
1.2、若参数不能得到满足,则,将这个参数类型作为键, 这个MethodDefinition作为值,存储到Map中;
1.3、当对所有参数都进行了检测,若存在未满足的参数, 则,将MethodDefinition存储到列表1;
1.4、当所有参数都满足要求,则,将其存储到列表2;
2、每处理完一个Bean,都扫描Map,将依赖这个Bean的MethodDefinition 的count–; 若count为0了,则,将其存储到列表2中。
事实上,类的名字有时会非常的长,为了方便我们记忆,Spring给出了Qualifier注解。
Qualifier注解:用在方法或参数身上,表示它的别名。
别名的引出,我们从beanPool中获取或存储时就不应该简简单单的直接get或者put了。因为有的类型会写别名,有的类型用户可能不会去写别名。但是,**代码编到了这个程度,不应该去修改大量的代码。**为此,我们应该改动最少的代码来完成这一目的。
我们可以增加一个类,名字叫做BeanPool;
public class BeanPool {
private static final Map<String, BeanDefinition> classBeanPool
= new HashMap<>();
private static final Map<String, BeanDefinition> aliasBeanPool
= new HashMap<>();
static void put(String key, BeanDefinition bean) {
Class<?> beanClass = bean.getKlass();
String beanClassName = beanClass.getName();
if (beanClassName.equals(key)) {
classBeanPool.put(key, bean);
} else {
aliasBeanPool.put(key, bean);
}
}
static BeanDefinition get(String key) {
BeanDefinition bean = classBeanPool.get(key);
if (bean == null) {
bean = aliasBeanPool.get(key);
}
return bean;
}
}
这个类里面有两个map,一个是类的别名对应的BeanDefinition,一个是类名字对应的BeanDefinition。重做get和put方法,这样,我们可以修改最少的代码。
对于getMethodPara(Method method)方法,我们不应该只是返回一个键是Class>,值是随便的Map了,而应该改为键是String,值是Class>,String表示别名或者类的名称,Class>表示对应的类的类型。这样一来,可以方便我们获取和存储。
现在我们只需要将 Map
private static Map<String ,Class<?>> getMethodPara(Method method) {
Map<String ,Class<?>> paraPool = new HashMap<>();
Class<?>[] parameterTypes = method.getParameterTypes();
// 去重;去掉类型重复的参数类型
for (int index = 0; index < parameterTypes.length; index++) {
if (parameterTypes[index].isAnnotationPresent(Qualifier.class)) {
String name = parameterTypes[index].getAnnotation(Qualifier.class).name();
paraPool.put(name, parameterTypes[index]);
} else {
paraPool.put(parameterTypes[index].getName(), parameterTypes[index]);
}
}
List<Class<?>> klassList = new ArrayList<Class<?>>();
for (Class<?> type : paraPool.values()) {
BeanDefinition beanDefinition = dealPara(type);
if (beanDefinition != null) {
klassList.add(type);
}
}
for (int index = 0; index < klassList.size(); index++) {
if (klassList.get(index).isAnnotationPresent(Qualifier.class)) {
paraPool.remove(klassList.get(index).getAnnotation(Qualifier.class).name());
} else {
paraPool.remove(klassList.get(index).getName());
}
}
return paraPool;
}
到此为止,我们类的注入已经大致完成。至于其中有些类存在的循环依赖,循环注入等,在后续博文会写到。
我们来检测一下我们的代码吧:
看例子:
@Component
public class TwoClass {
@Autowired
// 这里的Complex类是存放在jar包里的,无法更改这个Complex类。 所以用bean注解来完成注入。
private Complex c;
public TwoClass() {
}
@Override
public String toString() {
return "这是一个TwoClass的对象";
}
}
@Component
public class ThreeClass {
@Autowired
private TwoClass two;
public ThreeClass() {
}
public TwoClass getTwo() {
return two;
}
public void setTwo(TwoClass two) {
this.two = two;
}
}
@Component
public class OneClass {
@Autowired
private TwoClass two;
@Autowired
private Complex complex;
@Autowired
private Calendar date;
public OneClass() {
}
public Complex getComplex() {
return complex;
}
public void setComplex(Complex complex) {
this.complex = complex;
}
@Autowired
public void setTwo(TwoClass two) {
this.two = two;
}
public TwoClass getTwo() {
return two;
}
public void doOneThing() {
System.out.println(two);
}
}
@Component
public class Config {
public Config() {
}
@Bean
public Calendar getComplex(@Qualifier(name="one") Complex one) {
System.out.println("将Complex这个不能更改的类,装入池子!");
Complex c = new Complex(one.getReal(),one.getVir());
c.setReal(c.getReal() + 2);
System.out.println("c:" + c);
Calendar date = Calendar.getInstance();
return date;
}
@Bean(name="one")
public Complex getComplex() {
Complex complex = new Complex(3,4);
return complex;
}
}
public class Test {
public static void main(String[] args) {
BeanFactory.scanPackage("com.mec.mpring.demo");
BeanFactory beanFactory = new BeanFactory();
Calendar one = beanFactory.getBean(Calendar.class);
System.out.println(one);
System.out.println("-----------------------");
OneClass oneClass = beanFactory.getBean(OneClass.class);
System.out.println(oneClass);
}
}