前言
之前的两篇博客【从山寨Spring中学习Spring IOC原理-XML-Setter】和【从山寨Spring中学习Spring IOC原理-XML-Constructor】我们模拟了Spring框架下的手动装配。但是总所周知自动装配才是Spring的精髓,所以既然要山寨就不可能少了这一部份。一般来说Spring的自动装配只需要在字段声明的地方加一个@Autowired
就可以了,但是在xml
下,需要配置byName
或者byType
的方式去开启。既不需要构造方法,也不需要Setter方法。我们这一篇就是山寨一个Spring的bytype
的自动装配过程,为了博客看起来比较方便,讲解的内容部分会穿插代码片,而完整的代码会贴到最后。更多Spring内容进入【Spring解读系列目录】。
准备内容
还是准备一个事务接口UserDao,一个事务实现类UserDaoImpl,一个事务干扰类UserDaoImpl2,一个业务接口UserService,一个业务实现UserServiceImpl。
public interface UserDao {
public void query();
}
public class UserDaoImpl implements UserDao{
@Override
public void query() {
System.out.println("UserDaoImpl query 1");
}
}
public class UserDaoImpl2 implements UserDao{
@Override
public void query() {
System.out.println("UserDaoImpl query 2");
}
}
public interface UserService {
public void find();
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Override
public void find() {
System.out.println("UserServiceImpl find()");
userDao.query();
}
}
思路
那么我们现在想一下,如果要完成自动装配有哪些问题。首先就是依赖,因为xml
已经无法提供依赖了,所以我们要自动取判断依赖的情况。其次是报错,如果xml
如果描述的和真实的情况不符需要针对这种错误抛出异常。最后添加自动装配标签,Spring的default-autowire
是配置在<beans>
标签里的因此,我们也需要在root
标签的位置去判断是否有这个标签。直接再原来的基础上添加代码,后面就要考虑如何取到类中的依赖了。
//检查是不是配了自动装配
Attribute attributeAuto=elementRoot.attribute("default-autowire");
boolean flag=false; //自动装配标志,如果为true说明配置了
if(!Objects.isNull(attributeAuto)){
flag=true;
}
如何判断依赖
在我们之前的手动装配的模拟程序中,我们看一个对象有没有依赖要根据两个情况:
- 第一看
xml
中标签没有子标签,因为有子标签就意味着有依赖。 - 第二看类中有没有依赖。
但是如果开始了自动装配,我们就可以假设<bean>
标签里永远没有子标签。所以当前能够判断一个类有没有依赖的主要依据,就是这个类中有没有属性。如果有属性,就说明有依赖,需要程序进行注入。所以不管是哪个类都需要我们去判断有没有属性。如果发现有依赖就需要去map
中找到对应的类型,然后给类中的字段赋值完成注入。
//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){
if(attributeAuto.getValue().equals("byType")){
//判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性
Field[] fields=clazz.getDeclaredFields();
//如果有依赖就要从map中找到对应类型
for (Field field : fields) {
//拿到要注入的属性类型
Class injectObjClass=field.getType();
//拿到属性以后遍历map,找到injectObjClass对应的属性类型
for (String key: map.keySet()) {
//从map中拿出对应类型的名字,并且和injectObjClass的名字对比
if (map.get(key).getClass().getInterfaces()[0].getName().equals(injectObjClass.getName())){
injectObject=map.get(key);//拿出对象
}
}
object=clazz.newInstance(); //new出实例对象
field.setAccessible(true);
field.set(object,injectObject); //注入
}
}
}
模拟报错
依赖做好了,但是Spring中有一个很常见的报错:如果发现两个类有了同一个依赖就会报错expected single matching bean but found 2。我们这里也可以把这个错误模拟出来,如果发现有两个实例同时对一个类进行注入,我们也报错。所以构建一个我们自己的报错类ConflictException,如果报错就抛出这个异常。然后修改代码。
public class ConflictException extends RuntimeException{
public ConflictException(String message) {
super(message);
}
}
//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){
if(attributeAuto.getValue().equals("byType")){
//判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性
Field[] fields=clazz.getDeclaredFields();
int count=0;
Object injectObject=null;
//如果有依赖就要从map中找到对应类型
for (Field field : fields) {
...}
if (count>1){
//说明我们找到两个类同时想要注入,Spring中会抛出异常,我们这里也抛一个异常出去
throw new ConflictException("我们自己的异常:expected single matching bean but found 2");
}else {
object=clazz.newInstance(); //new出实例对象
field.setAccessible(true);
field.set(object,injectObject); //注入
}
}
}
}
测试可用
首先我们看下我们写的异常有没有其作用,配置UserDaoImpl
和UserDaoImpl2
冲突,dao
和dao2
同时匹配UserServiceImpl
里面的UserDao
属性,所以一定要报错。运行测试类Test:
<beans default-autowire="byType">
<bean id="dao" class="com.demo.dao.UserDaoImpl"></bean>
<bean id="dao2" class="com.demo.dao.UserDaoImpl2"></bean>
<bean id="service" class="com.demo.service.UserServiceImpl">
</bean>
</beans>
public class Test {
public static void main(String[] args) {
BeanFactoryAuto beanFactoryAuto=new BeanFactoryAuto("spring-auto.xml");
UserService service= (UserService) beanFactoryAuto.getBean("service");
service.find();
}
}
运行结果:
com.demo.exception.ConflictException: 我们自己的异常:expected single matching bean but found 2
at com.demo.beanfactory.BeanFactoryAuto.parseXml(BeanFactoryAuto.java:123)
at com.demo.beanfactory.BeanFactoryAuto.<init>(BeanFactoryAuto.java:27)
at com.demo.test.Test.main(Test.java:25)
Exception in thread "main" java.lang.NullPointerException
at com.demo.test.Test.main(Test.java:27)
然后看下正常情况下,有没有给我们自动注入。打印成功,说明我们的程序已经完成了这个注入,正常执行了逻辑。
<beans default-autowire="byType">
<bean id="dao" class="com.demo.dao.UserDaoImpl"></bean>
<!--<bean id="dao2" class="com.demo.dao.UserDaoImpl2"></bean>-->
<bean id="service" class="com.demo.service.UserServiceImpl">
</bean>
</beans>
运行结果:
UserServiceImpl find()
UserDaoImpl query 1
为了更彻底的验证,我们重新创建一个逻辑SecondDao和SecondDaoImpl,让UserServiceImpl去依赖这个接口,再运行看看。
public interface SecondDao {
public void query();
}
public class SecondDaoImpl implements SecondDao{
@Override
public void query() {
System.out.println("SecondDaoImpl query()");
}
}
public class UserServiceImpl implements UserService {
private SecondDao userDao;
@Override
public void find() {
System.out.println("UserServiceImpl find()");
userDao.query();
}
}
<beans default-autowire="byType">
<bean id="dao3" class="com.demo.dao.SecondDaoImpl"></bean>
<bean id="service" class="com.demo.service.UserServiceImpl">
</bean>
</beans>
运行结果:
UserServiceImpl find()
SecondDaoImpl query()
一样完美的适配了,那么到这里一个简易版的byType
自动注入就基本完成了。
注入优先权
自动装配的优先权是低于手动装配的。所以如果在xml
中配置了<property>
或者<constructor-arg>
的参数,自动装配的效果应该是可以被屏蔽掉的,这里要怎么做呢。其实很简单,我们当前已经把前面两篇的程序组合在一起了,所以如果我们通过任意一种手动装配必然会给当前对象赋值,也就是object
不会为null
,所以只要在if(flag)
前面加上判断就可以屏蔽自动装配的效果。修改一下xml
运行打印正常。如果这里自动装配起作用了,一定会报我们的自定义异常的。
for (Iterator<Element> itSecond = elementFirstChild.elementIterator(); itSecond.hasNext();) {
if (elementSecondChild.getName().equals("property")){
object=clazz.newInstance(); //手动装配property赋值
}else if (elementSecondChild.getName().equals("constructor-arg")){
object=constructor.newInstance(injectObj); //手动装配构造参数赋值
}
}
if (Objects.isNull(object)){
//屏蔽自动装配
if(flag){
...}
}
<beans default-autowire="byType">
<bean id="dao" class="com.demo.dao.UserDaoImpl"></bean>
<bean id="service" class="com.demo.service.UserServiceImpl">
<property name="userDao" ref="dao"></property>
</bean>
</beans>
运行结果,自动装配被屏蔽了,如果起作用报错:single matching bean but found 2
UserServiceImpl find()
UserDaoImpl query 1
总结
本篇博客完成了对Spring IOC
自动注入byType
模式的模拟。至于byName
的模式是根据属性名来的,其实这个已经在【从山寨Spring中学习Spring IOC原理-XML-Setter】这篇中有过涉及(因为笔者偷懒,本来应该解析setter
方法名的,直接解析属性名了),大家可以自己模拟或者改改笔者的程序,模拟出来这个例子。其实不止这里,因为笔者的代码只能算一个不完整的框架,如果有兴趣大家可以根据这个小框架修改,比如xml
里面配置了<property>
但是类里面没有要抛出异常,等等。下一篇【从山寨Spring中学习Spring IOC原理-自动装配注解】我们就去写一个有关Spring的自动注解的类,以了解在注解模式下,Spring是怎么找到那些类并实例化的。
附完整代码
这个BeanFactoryAuto
自动装配的类已经整合了手动装配。
public class BeanFactoryAuto {
//map存放类的实例
Map<String, Object> map=new HashMap<>();
/**
* 工厂方法
* @param xml
*/
public BeanFactoryAuto(String xml) {
parseXml(xml);
}
/**
* 解析
* @param xml
*/
public void parseXml(String xml){
//拿到根路径
String path=this.getClass().getResource("/").getPath()+xml;
File file=new File(path);
SAXReader reader = new SAXReader();
try {
Document document = reader.read(file);
Element elementRoot=document.getRootElement();
//检查是不是配了自动装配
Attribute attributeAuto=elementRoot.attribute("default-autowire");
boolean flag=false; //自动装配标志,如果为true说明配置了
if(!Objects.isNull(attributeAuto)){
flag=true;
}
for (Iterator<Element> itFirst = elementRoot.elementIterator(); itFirst.hasNext();) {
Element elementFirstChild = itFirst.next();
Attribute attributeId=elementFirstChild.attribute("id");
String beanName=attributeId.getValue();
Attribute attributeClass=elementFirstChild.attribute("class");
String clazzName=attributeClass.getValue();
Class clazz=Class.forName(clazzName);
Object object=null;
for (Iterator<Element> itSecond = elementFirstChild.elementIterator(); itSecond.hasNext();) {
Element elementSecondChild =itSecond.next();
if (elementSecondChild.getName().equals("property")){
//手动装配property逻辑
object=clazz.newInstance();
Object injectObj=map.get(elementSecondChild.attribute("ref").getValue());
String nameValue=elementSecondChild.attribute("name").getValue();
Field field=clazz.getDeclaredField(nameValue);
field.setAccessible(true);
field.set(object,injectObj);
}else if (elementSecondChild.getName().equals("constructor-arg")){
//手动装配构造方法逻辑
String refValue=elementSecondChild.attribute("ref").getValue();
Object injectObj=map.get(refValue);
Class injectObjClazz=injectObj.getClass();
String nameValue=elementSecondChild.attribute("name").getValue();
Field field=clazz.getDeclaredField(nameValue);
Constructor constructor=clazz.getConstructor(field.getType());
object=constructor.newInstance(injectObj);
}
}
if (Objects.isNull(object)){
//屏蔽自动装配
//如果直接走到这里说明没有子标签,但是又加了default-autowire
if(flag){
if(attributeAuto.getValue().equals("byType")){
//判断是否有依赖,如果有依赖,就byType的注入,因此先要拿到类里的属性
Field[] fields=clazz.getDeclaredFields();
int count=0;
Object injectObject=null;
//如果有依赖就要从map中找到对应类型
for (Field field : fields) {
//拿到要注入的属性类型
Class injectObjClass=field.getType();
//拿到属性以后遍历map,找到injectObjClass对应的属性类型
for (String key: map.keySet()) {
//从map中拿出对应类型的名字,并且和injectObjClass的名字对比
if (map.get(key).getClass().getInterfaces()[0].getName().equals(injectObjClass.getName())){
//每找到一个就记录一次,这里模仿的就是Spring注入冲突
count++;
injectObject=map.get(key);
}
}
if (count>1){
//说明我们找到两个类同时想要注入,Spring中会抛出异常,我们这里也抛一个异常出去
throw new ConflictException("我们自己的异常:expected single matching bean but found 2");
}else {
object=clazz.newInstance(); //new出实例对象
field.setAccessible(true);
field.set(object,injectObject);
}
}
}
}
}
if(object==null){
//没有子标签,意味着没有依赖所以new出来
object=clazz.newInstance();
}
map.put(beanName,object);
}
System.out.println(map.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取对应的bean
* @param bean
* @return
*/
public Object getBean(String bean){
return map.get(bean);
}
}