Foreword
Previous We talked about some of the features spring and analysis of what functions need to achieve, the preparatory work has been done, we began to realize that a specific function.
Container loading process
We know that in the spring refesh () method to do a lot of initialization work, it covers almost spring core processes
public void Refresh () throws BeansException, {IllegalStateException the synchronized ( the this .startupShutdownMonitor) { // previous refresh preparations, including setting start time flag is activated, the source initialization properties (property source) configuration prepareRefresh (); // a refreshing the subclass BeanFactory (if not create created), and returns BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory (); // prepare for BeanFactory ApplicationContext use prepareBeanFactory (beanFactory); the try { // subclass format by this method may be BeanFactory to modify postProcessBeanFactory (beanFactory); // instantiate and call all registered BeanFactoryPostProcessor objects invokeBeanFactoryPostProcessors (beanFactory); // instantiate and call all registered BeanPostProcessor objects registerBeanPostProcessors (beanFactory); // initialize the MessageSource initMessageSource (); // initialize event broadcaster initApplicationEventMulticaster (); // subclasses override this method to do extra work during the refresh OnRefresh (); // Register the application listener ApplicationListener registerListeners (); // instantiate all-lazy-the init bean non finishBeanFactoryInitialization (beanFactory); //Refresh completion, including the initialization LifecycleProcessor, publishing refresh completion event like finishRefresh (); } the catch (BeansException EX) { // the Destroy already after the dangling Created singletons to Avoid Resources. DestroyBeans (); // the Reset 'Active' In Flag. CancelRefresh (EX ); // the Propagate Exception to Caller. the throw EX; } } }
Do something more complicated, and we do realize the basic fine.
We CJDispatcherServlet class init method, to achieve the following business logic, will be able to initialize the spring function, you can use a dependency injection
@Override public void the init (the ServletConfig config) { // load the configuration // Get packet address to be scanned // scan to load class // instantiate the class to load // load dependency injection, assigns them // load map address }
Load configuration
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
loadConfig(contextConfigLocation);
Here will be the value obtained in web.xml init-param node
Specific points to application.properties profile file under the spring, there is only one line configuration
It can be configured to know the key name, which is designated to scan the package path
It represents the red box to define the scan all the classes
The second line is to create a method loadConfig, will pass into the package path
void loadConfig(String contextConfigLocation) { InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { properties.load(is); } catch (IOException e) { e.printStackTrace(); } finally { if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace (); } } } }
Note that the yellow part of the code, a member variable is used here
private Properties properties = new Properties () ;
like in the upper half part of the definition of the class, the role here is the content acquisition application.properties configuration file is loaded into the variable properties, for later use.
Gets packet address to be scanned
// get the packet address to scan String dirpath = properties.getProperty ( "scanner.package" );
Used herein key configuration directory address read
Scan to load the class
// scan to load class doScanner (dirpath);
We define a class doScanner scanning method, pass in the package directory address
1 void doScanner(String dirpath) { 2 URL url = this.getClass().getClassLoader().getResource("/" + dirpath.replaceAll("\\.", "/")); 3 File dir = new File(url.getFile()); 4 File[] files = dir.listFiles(); 5 for (File file : files) { 6 if (file.isDirectory()) { 7 doScanner(dirpath + "." + file.getName()); 8 continue; 9 } 10 11 //取文件名 12 String beanName = dirpath + "." + file.getName().replaceAll(".class", ""); 13 beanNames.add(beanName); 14 } 15 }
The second line was replaced escaped
The code is present within the job file in the specified path is read, if the folder, the recursive call, if the file name and path of the file into memory within the collection vessel
Note that the yellow part of the variable is defined outside a member variable
private List<String> beanNames = new ArrayList<>();
We add it in the top half of the class.
BeanName obtained as follows
From here we see that it has been defined to the notes we find out.
Instantiate the class to load
// instantiate the class to be loaded doInstance ();
Just now we've got the list of names defined class, now we need to instantiate one, and save them in ioc container.
Define a container loading classes using HashMap can do, it is set member variables, defined in the top half of the class
private Map <String, Object> ioc = new HashMap <> ();
then create a method doInstance
1 void doInstance() { 2 if (beanNames.isEmpty()) { 3 return; 4 } 5 for (String beanName : beanNames) { 6 try { 7 Class cls = Class.forName(beanName); 8 if (cls.isAnnotationPresent(JCController.class)) { 9 //使用反射实例化对象 10 Object instance = cls.newInstance(); 11 //默认类名首字母小写 12 beanName = firstLowerCase(cls.getSimpleName()); 13 //写入ioc容器 14 ioc.put(beanName, instance); 15 16 17 } else if (cls.isAnnotationPresent(JCService.class)) { 18 Object instance = cls.newInstance(); 19 JCService jcService = (JCService) cls.getAnnotation(JCService.class); 20 21 String alisName = jcService.value(); 22 if (null == alisName || alisName.trim().length() == 0) { 23 beanName = cls.getSimpleName(); 24 } else { 25 beanName = alisName; 26 } 27 beanName = firstLowerCase(beanName); 28 ioc.put(beanName, instance); 29 //如果是接口,自动注入它的实现类 30 Class<?>[] interfaces = cls.getInterfaces(); 31 for (Class<?> c : 32 interfaces) { 33 ioc.put(firstLowerCase(c.getSimpleName()), instance); 34 } 35 } else { 36 continue; 37 } 38 } catch (ClassNotFoundException e) { 39 e.printStackTrace(); 40 } catch (IllegalAccessException e) { 41 e.printStackTrace(); 42 } catch (InstantiationException e) { 43 e.printStackTrace(); 44 } 45 } 46 }
只要提供类的完全限定名,通过Class.forName静态方法,我们就能将类信息加载到内存中并且返回Class 对象,通过反射来实例化,见第10行代码,
我们通过循环beanNames集合,来实例化每个类,并将实例化后的对象装入HashMap中
注意:第12行将类名的首字母小写后存入map,该方法定义如下
1 String firstLowerCase(String str) { 2 char[] chars = str.toCharArray(); 3 chars[0] += 32; 4 return String.valueOf(chars); 5 }
这行代码会将字符串转成char数组,然后将数组中第一个字符转为大写,这里采用了一种比较巧妙的方式实现,tom老师采用了一种比较骚的操作
实例化完成后,ioc容器中的数据如下:
说明:
图片中可以看出,hashMap的key 都是小写,value已经是对象了 ,见红框。
这里为什么要把蓝框标记出来,是因为这是类中的字段属性,此时可以看到,虽然类已经被实例化了,可是属性还是null呢
我这里为了测试依赖注入,所以加了2个接口和2个实现类
接口定义如下:
public interface IHomeService { String sayHi(); String getName(Integer id,String no); String getRequestBody(Integer id, String no, GetUserInfo userInfo); } public interface IStudentService { String sayHi(); }
实现类:
@JCService public class StudentService implements IStudentService{ @Override public String sayHi(){ return "Hello world!"; } }
@JCService public class HomeService implements IHomeService{ @JCAutoWrited StudentService studentService; @Override public String sayHi() { return studentService.sayHi(); } @Override public String getName(Integer id,String no) { return "SB0000"+id; } @Override public String getRequestBody(Integer id, String no, GetUserInfo userInfo) { return "userName="+userInfo.getName()+" no="+no; } }
依赖实体:
public class GetUserInfo { public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public BigDecimal getGrowthValue() { return growthValue; } public void setGrowthValue(BigDecimal growthValue) { this.growthValue = growthValue; } private String name; private Integer age; private BigDecimal growthValue; }
加载依赖注入,给属性赋值
//加载依赖注入,给属性赋值 doAutoWrited();
现在我们实现依赖注入,需要定义一个无参的方法doAutoWrite
1 void doAutoWrited() { 2 for (Map.Entry<String, Object> obj : ioc.entrySet()) { 3 try { 4 for (Field field : obj.getValue().getClass().getDeclaredFields()) { 5 if (!field.isAnnotationPresent(JCAutoWrited.class)) { 6 continue; 7 } 8 JCAutoWrited autoWrited = field.getAnnotation(JCAutoWrited.class); 9 String beanName = autoWrited.value(); 10 if ("".equals(beanName)) { 11 beanName = field.getType().getSimpleName(); 12 } 13 14 field.setAccessible(true); 15 16 field.set(obj.getValue(), ioc.get(firstLowerCase(beanName))); 17 } 18 } catch (IllegalAccessException e) { 19 e.printStackTrace(); 20 } 21 22 } 23 24 25 }
这个方法是通过循环ioc里面的实体,反射找出字段,看看是否有需要注入的标记JCAutoWrited,如果加了标记,就反射给字段赋值,类型从ioc容器中获取
加载映射地址
//加载映射地址 doRequestMapping();
映射地址的作用是根据请求的url匹配method方法
1 void doRequestMapping() { 2 if (ioc.isEmpty()) { 3 return; 4 } 5 for (Map.Entry<String, Object> obj : ioc.entrySet()) { 6 if (!obj.getValue().getClass().isAnnotationPresent(JCController.class)) { 7 continue; 8 } 9 Method[] methods = obj.getValue().getClass().getMethods(); 10 for (Method method : methods) { 11 if (!method.isAnnotationPresent(JCRequestMapping.class)) { 12 continue; 13 } 14 String baseUrl = ""; 15 if (obj.getValue().getClass().isAnnotationPresent(JCRequestMapping.class)) { 16 baseUrl = obj.getValue().getClass().getAnnotation(JCRequestMapping.class).value(); 17 } 18 JCRequestMapping jcRequestMapping = method.getAnnotation(JCRequestMapping.class); 19 if ("".equals(jcRequestMapping.value())) { 20 continue; 21 } 22 String url = (baseUrl + "/" + jcRequestMapping.value()).replaceAll("/+", "/"); 23 urlMapping.put(url, method); 24 System.out.println(url); 25 } 26 } 27 }
这里其实就是根据对象反射获取到JCRequestMapping上面的value值
@JCRequestMapping("/sayHi")
取到的就是/sayHi
另外注意的是:黄色部分使用的变量是一个hashMap,在类上半部分定义的
private Map<String, Method> urlMapping = new HashMap<>();
这里面存的是 url 和对应的method对象。后面处理请求的时候要使用到的。
结尾
容器的初始化到这里就结束了,一共使用了4个容器来存放相关对象,后续servlet处理请求的时候会用到它们。
下一篇,将会继续完善它,通过请求来验证是否可以达到预期效果。另外会实现参数绑定,能处理各类请求并响应。