當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
Spring容器初始化实现V2 版本
生活随笔
收集整理的這篇文章主要介紹了
Spring容器初始化实现V2 版本
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在V1 版本上進了優化,采用了常用的設計模式(工廠模式、單例模式、委派模式、策略模式),將init()方法中的代碼進行封裝。按照之前的實現思路,先搭基礎框架,再填肉注血,具體代碼如下:
//初始化階段 @Override public void init(ServletConfig config) throws ServletException {//1、加載配置文件doLoadConfig(config.getInitParameter("contextConfigLocation"));//2、掃描相關的類doScanner(contextConfig.getProperty("scanPackage"));//3、初始化掃描到的類,并且將它們放入到ICO容器之中doInstance();//4、完成依賴注入doAutowired();//5、初始化HandlerMappinginitHandlerMapping();System.out.println("GP Spring framework is init.");}聲明全局的成員變量,其中IOC 容器就是注冊時單例的具體案例:
//保存application.properties配置文件中的內容 private Properties contextConfig = new Properties();//保存掃描的所有的類名 private List<String> classNames = new ArrayList<String>();//傳說中的IOC容器,我們來揭開它的神秘面紗 //為了簡化程序,暫時不考慮ConcurrentHashMap // 主要還是關注設計思想和原理 private Map<String,Object> ioc = new HashMap<String,Object>();//保存url和Method的對應關系 private Map<String,Method> handlerMapping = new HashMap<String,Method>();實現doLoadConfig()方法:
//加載配置文件 private void doLoadConfig(String contextConfigLocation) {//直接從類路徑下找到Spring主配置文件所在的路徑//并且將其讀取出來放到Properties對象中//相對于scanPackage=com.gupaoedu.demo 從文件中保存到了內存中InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);try {contextConfig.load(fis);} catch (IOException e) {e.printStackTrace();}finally {if(null != fis){try {fis.close();} catch (IOException e) {e.printStackTrace();}}} }實現doScanner()方法:
//掃描出相關的類 private void doScanner(String scanPackage) {//scanPackage = com.gupaoedu.demo ,存儲的是包路徑//轉換為文件路徑,實際上就是把.替換為/就OK了//classpathURL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/"));File classPath = new File(url.getFile());for (File file : classPath.listFiles()) {if(file.isDirectory()){doScanner(scanPackage + "." + file.getName());}else{if(!file.getName().endsWith(".class")){ continue;}String className = (scanPackage + "." + file.getName().replace(".class",""));classNames.add(className);}} }實現doInstance()方法,doInstance()方法就是工廠模式的具體實現:
private void doInstance() {//初始化,為DI做準備if(classNames.isEmpty()){return;}try {for (String className : classNames) {Class<?> clazz = Class.forName(className);//什么樣的類才需要初始化呢?//加了注解的類,才初始化,怎么判斷?//為了簡化代碼邏輯,主要體會設計思想,只舉例 @Controller和@Service,// @Componment...就一一舉例了if(clazz.isAnnotationPresent(GPController.class)){Object instance = clazz.newInstance();//Spring默認類名首字母小寫String beanName = toLowerFirstCase(clazz.getSimpleName());ioc.put(beanName,instance);}else if(clazz.isAnnotationPresent(GPService.class)){//1、自定義的beanNameGPService service = clazz.getAnnotation(GPService.class);String beanName = service.value();//2、默認類名首字母小寫if("".equals(beanName.trim())){beanName = toLowerFirstCase(clazz.getSimpleName());}Object instance = clazz.newInstance();ioc.put(beanName,instance);//3、根據類型自動賦值,投機取巧的方式for (Class<?> i : clazz.getInterfaces()) {if(ioc.containsKey(i.getName())){throw new Exception("The “" + i.getName() + "” is exists!!");}//把接口的類型直接當成key了ioc.put(i.getName(),instance);}}else {continue;}}}catch (Exception e){e.printStackTrace();}}為了處理方便,自己實現了toLowerFirstCase 方法,來實現類名首字母小寫,具體代碼如下:
//如果類名本身是小寫字母,確實會出問題 //但是我要說明的是:這個方法是我自己用,private的 //傳值也是自己傳,類也都遵循了駝峰命名法 //默認傳入的值,存在首字母小寫的情況,也不可能出現非字母的情況//為了簡化程序邏輯,就不做其他判斷了,大家了解就OK //其實用寫注釋的時間都能夠把邏輯寫完了 private String toLowerFirstCase(String simpleName) {char [] chars = simpleName.toCharArray();//之所以加,是因為大小寫字母的ASCII碼相差32,// 而且大寫字母的ASCII碼要小于小寫字母的ASCII碼//在Java中,對char做算學運算,實際上就是對ASCII碼做算學運算chars[0] += 32;return String.valueOf(chars); }實現doAutowired()方法:
//自動依賴注入 private void doAutowired() {if(ioc.isEmpty()){return;}for (Map.Entry<String, Object> entry : ioc.entrySet()) {//Declared 所有的,特定的 字段,包括private/protected/default//正常來說,普通的OOP編程只能拿到public的屬性Field[] fields = entry.getValue().getClass().getDeclaredFields();for (Field field : fields) {if(!field.isAnnotationPresent(GPAutowired.class)){continue;}GPAutowired autowired = field.getAnnotation(GPAutowired.class);//如果用戶沒有自定義beanName,默認就根據類型注入//這個地方省去了對類名首字母小寫的情況的判斷,這個作為課后作業//小伙伴們自己去完善String beanName = autowired.value().trim();if("".equals(beanName)){//獲得接口的類型,作為key待會拿這個key到ioc容器中去取值beanName = field.getType().getName();}//如果是public以外的修飾符,只要加了@Autowired注解,都要強制賦值//反射中叫做暴力訪問, 強吻field.setAccessible(true);try {//用反射機制,動態給字段賦值field.set(entry.getValue(),ioc.get(beanName));} catch (IllegalAccessException e) {e.printStackTrace();}}}}實現initHandlerMapping()方法,handlerMapping 就是策略模式的應用案例:
//初始化url和Method的一對一對應關系 private void initHandlerMapping() {if(ioc.isEmpty()){ return; }for (Map.Entry<String, Object> entry : ioc.entrySet()) {Class<?> clazz = entry.getValue().getClass();if(!clazz.isAnnotationPresent(GPController.class)){continue;}//保存寫在類上面的@GPRequestMapping("/demo")String baseUrl = "";if(clazz.isAnnotationPresent(GPRequestMapping.class)){GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);baseUrl = requestMapping.value();}//默認獲取所有的public方法for (Method method : clazz.getMethods()) {if(!method.isAnnotationPresent(GPRequestMapping.class)){continue;}GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);//優化// //demo///queryString url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+","/");handlerMapping.put(url,method);System.out.println("Mapped :" + url + "," + method);}}}到這里位置初始化階段就已經完成,接下實現運行階段的邏輯,來看doPost/doGet 的代碼:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req,resp); }@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//6、調用,運行階段try {doDispatch(req,resp);} catch (Exception e) {e.printStackTrace();resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace()));}}doPost()方法中,用了委派模式,委派模式的具體邏輯在doDispatch()方法中:
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {//絕對路徑String url = req.getRequestURI();//處理成相對路徑String contextPath = req.getContextPath();url = url.replaceAll(contextPath,"").replaceAll("/+","/");if(!this.handlerMapping.containsKey(url)){resp.getWriter().write("404 Not Found!!!");return;}Method method = this.handlerMapping.get(url);//從reqest中拿到url傳過來的參數Map<String,String[]> params = req.getParameterMap();//獲取方法的形參列表Class<?> [] parameterTypes = method.getParameterTypes();Object [] paramValues = new Object[parameterTypes.length];for (int i = 0; i < parameterTypes.length; i ++) {Class parameterType = parameterTypes[i];//不能用instanceof,parameterType它不是實參,而是形參if(parameterType == HttpServletRequest.class){paramValues[i] = req;continue;}else if(parameterType == HttpServletResponse.class){paramValues[i] = resp;continue;}else if(parameterType == String.class){GPRequestParam requestParam = (GPRequestParam)parameterType.getAnnotation(GPRequestParam.class);if(params.containsKey(requestParam.value())) {for (Map.Entry<String,String[]> param : params.entrySet()){String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll("\\s",",");paramValues[i] = value;}}}}//投機取巧的方式//通過反射拿到method所在class,拿到class之后還是拿到class的名稱//再調用toLowerFirstCase獲得beanNameString beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());method.invoke(ioc.get(beanName),paramValues); }?
總結
以上是生活随笔為你收集整理的Spring容器初始化实现V2 版本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring容器初始化实现V1 版本
- 下一篇: Spring容器初始化实现V3 版本