IOC是Spring两大特征之一,今天我们就来用最最最土的方式模拟下它。本文全是基础,不涉及设计模式,适合初级程序员阅读。
到底什么是IOC、DI
IOC(控制反转),不是什么技术,而是一种设计思想。Ioc意味着,将你设计好的对象交给容器控制,而不是传统的,在你的对象内部直接控制。
所以控制反转就是说把创建对象的控制权进行转移,以前创建对象的主动权和创建时机都是有自己控制的,而现在把这种权利交给第三方,比如IOC容器,它是一个专门用来创建对象的工厂,有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合。
DI(依赖注入)
一种IoC的实现方式。即将依赖对象的创建和绑定工作转移到第三者(调用方)
想象下Spring加载过程
假设有一个URL请求,http://demo/get/uid/1
当这个请求到达服务器之后,首先获取url,接着找到url所对应的处理方法,最后把url参数传给这个方法,然后在调用下这个方法。
问题是:如何通过通过url找到这个方法的映射,拢共分几步?
分六个步骤
第一步:自定义几个注解
我们需要自定义几个注解来标识我们bean对象:
1. @Controller 用来标识controller层。
2. @Service用来标识Sevice层。
3. @Qualifier 用来标识类中属性。
4. @RequestMapping 用来标识方法。
这些注解中都有一个默认的value属性。
第二步:收集bean
把整个工程文件遍历一遍,通过IO读取所有java文件,java的name放在一个list容器里,就称他为beanName集合吧。
第三步:注册bean
遍历beanName集合,获取java文件的类名,用反射的方法通过名字,获取该类的Class,判断Class是否包含Controller或Service注解,若包含,则通过反射的方式把当前Class生成bean对象,用Controller或Service注解中的value值做key来标识。放到一个map容器里,就叫他instanceMap容器吧。
第四步:注入bean
遍历instanceMap容器,instanceMap存的都是实例对象,我们遍历每个对象,获取当前对象内的所有变量fields,然后在遍历这个fields,看当前field是否包含Qualifier注解,包含的话,就用Qualifier注解设置的value当做key去instanceMap容器里找到bean实例对象,然后把这个实例对象设置到当前的field中。
第五步:收集RequestMapping方法
遍历instanceMap容器,找到带有Controller注解的类,获取该类的所有方法methods,再遍methods方法,找到带有RequestMapping注解的方法对象,用RequestMapping中的value加上Controller注解中的value拼接成url,当做key,把当前方法对象放到一个map容器,就把这个容器叫做methodMap容器吧。
第六步:实现url映射
当一个请求到达Servlet时,获取这个请求的url,通过字符串分割获取url中的参数,把参数设置到request.Attribute中,删除url中的参数,做key去methodMap获取方法对象,再用反射的方法,将request,response做参数,调用这个方法,获取返回值,这个返回值就是我们要返回给客户端的页面,在通过request转发的方式,请页面返回。
记住这六步,下面看代码。
Coding1:自定义几个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
String value() ;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Qualifier {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
String value();
}
Coding2:收集bean
basePackages为配置扫描的包。
就像spring配置文件中的<context:component-scan base-package="" />
public void scanClassName(String basePackages){
//获取扫描注解所在的路径
URL url =this.getClass().getResource("/"+replacePath(basePackages));
File files = new File(url.getFile());
String[] fileList = files.list();
for(String file: fileList){
File eachFile = new File(url.getFile()+file);
if(eachFile.isDirectory()){
scanClassName(basePackages+"."+file);
}else{
//将java类的名称放在classList中
classlist.add(basePackages+"."+eachFile.getName());
}
}
}
Coding3:注册bean
public void filterAndInstance() throws Exception {
for(String current:classlist){
//将.class后缀去掉,获取文件名,用反射获取class
Class clazz = Class.forName(current.replace(".class",""));
//判断是否包含Controller注解
if(clazz.isAnnotationPresent(Controller.class)){
//创建对象
Object object = clazz.newInstance();
String key = ((Controller)clazz.getAnnotation(Controller.class)).value();
instanceMap.put(key,object);
}else if(clazz.isAnnotationPresent(Service.class)){
Object object = clazz.newInstance();
String key = ((Service)clazz.getAnnotation(Service.class)).value();
instanceMap.put(key,object);
}else{
continue;
}
}
}
Coding4:注入bean
public void springDi() throws IllegalAccessException {
for(Map.Entry entry: instanceMap.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field :fields){
if(field.isAnnotationPresent(Qualifier.class)){
field.setAccessible(true);
String key = field.getAnnotation(Qualifier.class).value();
field.set(entry.getValue(),instanceMap.get(key));
}
}
}
}
Coding5:收集RequestMapping方法
private void mvc() throws ServletException {
if(instanceMap == null){
throw new ServletException("instanceMap is null");
}
for(Map.Entry entry : instanceMap.entrySet()){
if(entry.getValue().getClass().isAnnotationPresent(Controller.class)){
String ctlUrl = entry.getValue().getClass().getAnnotation(Controller.class).value();
Method[] methods = entry.getValue().getClass().getMethods();
for(Method current: methods){
if (current.isAnnotationPresent(RequestMapping.class)){
String methodUrl = current.getAnnotation(RequestMapping.class).value();
methodMap.put("/"+ctlUrl+"/"+methodUrl,current);
}
}
}
}
}
Coding6:实现url映射
private void methodInvoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getServletPath();
url = url.replace(".html","");
String className = url;
Method method = methodMap.get(className);
if(method==null){
req.getRequestDispatcher("/404.jsp").forward(req, resp);
}else {
Object o = instanceMap.get(className.split("/")[1]);
String forwod = null;
try {
forwod = (String) method.invoke(o, new Object[]{req, resp});
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
req.getRequestDispatcher("/" + forwod + ".jsp").forward(req, resp);
}
}