文章中的代码下载:https://github.com/yangbishang/springIoc
Spring框架中最重要也是最广为人知的就是AOP和IOC了吧,AOP我已经讲过了,今天我们就讲讲IOC,对于一些基本概念我就不赘述了,而且讲了也很难深刻的理解,今天我们就自己编写一个简易的框架来实现IOC
首先我们先看看代码的框架:
我们这就讲讲DispatcherServlet部分,不懂的可以看我github代码和视频。
我们这个框架主要分为三个部分,流程也是按这三个部分走的:
- 找到bean
- 载入并注册bean
- 注入bean
1 找到bean
找到bean在什么地方,是对BeanDefinition的资源定位,是由ResourceLoader通过统一的Resource接口来完成,这个接口对各中形式的Resource都提供了统一接口,比如Xml,比如annotation。而这些都是由ResourceLoader来完成的
/**
*
* 找到bean
*
* */
private void scanBase(String basePackages) { //"com.yy"
//file:/D:/IdeaProject/springIoc/springIoc/springioc/target/springioc/WEB-INF/classes/com/yy/
URL url = this.getClass().getClassLoader().getResource("/" + replacePath(basePackages));//getResource全部是带斜杠的,所以我们要把点换成斜杠
String path = url.getFile(); ///D:/IdeaProject/springIoc/springIoc/springioc/target/springioc/WEB-INF/classes/com/yy/
File file = new File(path); //生成一个文件
String [] strFiles = file.list(); //得到文件底下的所有的文件名
for(String strFile: strFiles) {
File eachFile = new File(path + strFile);
if(eachFile.isDirectory()) { //如果是目录,就递归继续向里面查找
scanBase(basePackages +"."+eachFile.getName());
}else {
System.out.println("class name" + eachFile.getName());
classNames.add(basePackages +"." + eachFile.getName());
}
}
}
//将字符串中的.换成/
String replacePath(String path) {
return path.replaceAll("\\.","/");
}
扫描这个项目,将所有类的文件名.Class存在l链表classNames中
2 载入并注册bean
找到bean后,将bean注册到我们的IOC容器中。Spring是通过一些ApplicationContext来完成的,比如FileSystemXmlApplicationContext, ClassPathXmlApplicationContext以及我们最常见的XmlWebApplicationContext,读取之后将bean注册到IOC容器中,简单来说,就是把读取的bean都放到一个map中。
/**
*
* 载入并注册bean
*
* */
//把classNames循环一遍,看里面有哪些类,然后把它生成出来加载进去
private void filterAndInstance() throws Exception {
if( classNames.size() == 0) {
return;
}
//循环获取类名
for(String className : classNames) {
Class clazz = Class.forName(className.replace(".class", ""));
if(clazz.isAnnotationPresent(Controller.class)) { //如果clazz字节码上面带了一个Annotation,并且Annotion是controller,就实例化一个controller对象出来
//获取bean实例
Object instance = clazz.newInstance();
//获取注解的value---将controller上的名字取出来(fish)
String key = ((Controller)clazz.getAnnotation(Controller.class)).value();
//将bean交付给IOC
instanceMap.put(key, instance); //eg:("fish" , FishController)
}else if(clazz.isAnnotationPresent(Service.class)) { //如果clazz字节码上面带了一个Annotation,并且Annotion是Service
//获取bean实例
Object instance = clazz.newInstance();
//获取注解的value
String key = ((Service)clazz.getAnnotation(Service.class)).value();
//将bean交付给IOC
instanceMap.put(key, instance); //eg:("fishServiceImpl" , FishServiceImpl)
}else {
continue;
}
}
}
然后根据classNams中的文件名反射得到@Controller和@Service 所注释的类的Class对象,然后将对象放入了instanceMap中,这些Class对象所对应的key就是@注释中的值。其实说白了就是就是将Controller层和Service层中的类的Class对象存在了在Map中,然后用@注释名来当Map的key。
3 注入bean
当我们要用bean时,由IOC容器自动的注入进去。
/**
*
* 注入bean
*
* */
//把ioc容器里的bean注入到指定地方(instanceMap中的值注入到@qualifier)p--DI
//把instanceMap循环一遍,把它每一个对象字节码中的file取出来,看看里面有没有Qualifier的注解,如果有的话,把qualifier中的value取出来,然后把value对应的对象注入到这里
private void springDi() throws IllegalArgumentException, IllegalAccessException {
if(instanceMap.size() == 0) {
return;
}
/**
* 循环获取实例
* */
for(Map.Entry<String, Object> entry: instanceMap.entrySet()) {
//获取所有的类变量
Field[] fields = entry.getValue().getClass().getDeclaredFields();
//查看上面有没有qualifer的标识,如果有qualifer的标识,把它的value取出来,通过这个值我们就可以拿到它的实例
for(Field field: fields) {
//包含Qualifer注解
if(field.isAnnotationPresent(Qualifier.class)){
String key = ((Qualifier)field.getAnnotation(Qualifier.class)).value();
field.setAccessible(true);
//注入qualifier----即把fishService注入进去了
field.set(entry.getValue(), instanceMap.get(key)); //set(Object,value)将指定对象参数上的此Field对象表示的字段设置为指定的新值。obj:其字段应被修改的对象 ; value:修改了 obj的新值
}
//autowired
}
}
}
循环遍历出每个instanceMap中Class对象的变量,判断这个变量上面有没有@Qualifer注解,有的化则拿到@qualifer的注解值,然后通过这个值在instanceMap中对应的Class对象更新(注入)到参数上去。
其实关于IOC部分就已经弄完了,当然我们还要处理一下springMVC部分
//把路径给生成出来(springMVC)
private void mvc() {
if(instanceMap.size() == 0) {
return;
}
//循环获取实例
for(Map.Entry<String, Object> entry: instanceMap.entrySet()) {
//将含有controller的所有实例都取出来
if(entry.getValue().getClass().isAnnotationPresent(Controller.class)) {//entry得到对象,得到字节码,看看是否含有controller注解
String ctlUrl = ((Controller)entry.getValue().getClass().getAnnotation(Controller.class)).value(); //将@Controller(“值”)中的值取出来
Method[] methods = entry.getValue().getClass().getMethods();//将controller中的带有@RequestMapping注解的方法取出来
for(Method method :methods) { //循环遍历方法,看看方法上面是否有@RequestMapping
if(method.isAnnotationPresent(RequestMapping.class)) {
String reqUrl = ((RequestMapping)method.getAnnotation(RequestMapping.class)).value(); //将@requestMapping(“值”)中的值取出来
String dispatchUrl = "/"+ ctlUrl +"/" + reqUrl; // /fish/get
methodMap.put(dispatchUrl, method); //每个url对应的方法存好
}
}
}else {
continue;
}
}
}
好,上面就将客户端访问路径与方法存入了methodMap中了
最后我们进去servlet中看看客户端是怎样通过输入路径调用到方法的
@Override
//把发送过来的url取到,然后和methodMap里面的url相比较,如果有,就把url对应的method执行即可
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// localhost:8080/springioc/fish/get
String uri = request.getRequestURI(); // /springioc/fish/get
String projetname = request.getContextPath(); // /springioc web项目的根路径
String path = uri.replaceAll(projetname, ""); // //fish/get
Method method = methodMap.get(path);
//把controller的value所对应的实例取出来(fish)
String className = uri.split("/")[2];
//把实例生成出来
FishController fishController = (FishController)instanceMap.get(className);
//调用实例
try {
method.invoke(fishController, new Object[] {request,response,null});
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}