手写springIOC、AOP

  • 一、核心思想
    • 1、IoC
      • 1.1 什么是IoC?
      • 1.2 IoC解决了什么问题
      • 1.3 IoC和DI的区别
    • 2、AOP
      • 2.1 什么是AOP?
      • 2.2 AOP解决的什么问题
      • 2.3 为什么叫面向切面编程
  • 二、手写实现IOC(包括mvc相关的逻辑)
    • 1、基本思路
    • 2、目录结构
    • 3、代码
      • 3.1 pom
      • 3.2 application.properties
      • 3.3 web.xml
      • 3.4 SZDispatcherServlet.java
      • 3.5 SZAutowired、SZControllerSZController、SZRequestMapping、SZService
      • 3.6 IDemoService.java、DemoServiceImpl.java、DemoController.java
    • 4、结果
    • 5、springICO循环依赖问题
      • 5.1什么是循环依赖
      • 5.2循环依赖处理机制
  • 三、手写实现AOP
    • 1、基本思路
    • 2、目录结构
    • 3、代码
      • 3.1 SZAopConfig.java、TestAspect.java、SZAdivce.java
      • 3.2 SZAdvisedSupport.java
      • 3.3 SZJdkDynamicAopProxy.java
    • 4、结果
  • 四、本文源码

思想部分抄自:https://blog.csdn.net/riemann_/article/details/106503900

一、核心思想

IoC 和 AOP 不是 spring 提出来的,在 spring 之前就已经存在,只不过更偏向理论化, spring 在技术层面把这两个思想做了非常好的实现。在手写 spring 中的 IoC 和 AOP 之前,我们先来了解 IoC 和 AOP 的思想。

1、IoC

1.1 什么是IoC?

IoC Inversion of Control (控制反转、反转控制),注意它是一个技术思想,不是技术实现。

描述的事情: Java 开发领域对象的创建、管理的问题。

传统开发模式:比如类A依赖类B,往往会在类A中 new 一个B的对象。

IoC 思想下开发模式:我们不再自己去 new 对象了,而是由 IoC 容器( Spring 框架)去帮助我们实例化对象并且管理它,我们需要哪个对象,去问IoC容器要即可。

我们丧失了一个权力(创建、管理对象的权力),得到一个福利(不用考虑对象的创建、管理等一系列事情)。

为什么叫控制反转?

控制:指的是对象创建(实例化、管理)的权力。

反转:控制权交给外部环境了( Spring 框架、 IoC容器)。

1.2 IoC解决了什么问题

IoC解决对象之间的耦合问题

1.3 IoC和DI的区别

DI:Dependency Injection(依赖注入)
如何理解:
IoC和DI描述的是同一件事情,只不过角度不一样罢了。

2、AOP

2.1 什么是AOP?

AOP:Aspect Oriented Programming 面向切面编程/面向方面编程
AOP是OOP的延续,我们从OOP说起
OOP三大特征:封装、继承、多态
OOP是一种垂直继承体系:


OOP编程思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的,比如下面的在顶级父类Animal中的多个方法中相同位置出现了重复代码,OOP就解决不了了。

横切逻辑代码:

横切逻辑代码存在什么问题:

  • 横切代码重复问题
  • 横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便。

AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离:

代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄无声息的把横切逻辑代码应用到原有的业 务逻辑中,达到和原来一样的效果,这个是比较难的。

2.2 AOP解决的什么问题

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

2.3 为什么叫面向切面编程

「切」 : 指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑。

「面」 : 横切逻辑代码往往要影响的是很多个方法,每一个方法都如同一个点,多个点构成面,有一个 面的概念在里面。

二、手写实现IOC(包括mvc相关的逻辑)

手写ioc和aop只是简单的实现了其功能,和spring对应的功能还是有很大区别的,主要就是为了感受其思想!!

1、基本思路

2、目录结构

3、代码

3.1 pom

    <dependencies><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version></dependency></dependencies>

3.2 application.properties

#用于扫描生成bean
scanPackage=com.sz.handwrittenspring#以下为aop相关配置
#切面表达式(即切点)
pointCut=public .* com.sz.handwrittenspring.service..*ServiceImpl..*(.*)
#切面类
aspectClass=com.sz.handwrittenspring.aop.aspect.TestAspect
#切面前置通知
aspectBefore=before
#切面后置通知
aspectAfter=after
#切面异常通知
aspectAfterThrow=afterThrowing
#切面异常类型
aspectAfterThrowingName=java.lang.Exception

3.3 web.xml

    <servlet><servlet-name>szmvc</servlet-name><servlet-class>com.sz.handwrittenspring.servlet.SZDispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>application.properties</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>szmvc</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>

3.4 SZDispatcherServlet.java

启动时加载init方法,方法内大致步骤:
1.加载配置文件
2.扫描相关的类
3.实例化相关的类,并且缓存到ico容器
4.完成依赖注入
5.初始化HandlerMapping

package com.sz.handwrittenspring.servlet;
import com.sz.handwrittenspring.annotation.SZAutowired;
import com.sz.handwrittenspring.annotation.SZController;
import com.sz.handwrittenspring.annotation.SZRequestMapping;
import com.sz.handwrittenspring.annotation.SZService;
import com.sz.handwrittenspring.aop.config.SZAopConfig;
import com.sz.handwrittenspring.aop.support.SZAdvisedSupport;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
public class SZDispatcherServlet extends HttpServlet {//ioc容器,存放实例化的对象private Map<String, Object> ioc = new HashMap<String, Object>();//获取配置文件内容private Properties contextConfig = new Properties();//存放扫描到的文件路径private List<String> classNames = new ArrayList<>();//存放http请求与方法的对应关系private Map<String, Method> handlerMapping = new HashMap<>();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//6.完成url的调度try {doDispatch(req, resp);} catch (Exception e) {e.printStackTrace();resp.getWriter().write("500 Exception Detail :" + Arrays.toString(e.getStackTrace()));}}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);Map<String, String[]> parameterMap = req.getParameterMap();//硬编码,感受下思想就行(对应SZDispatcherServlet类中的query方法)String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());method.invoke(ioc.get(beanName), new Object[]{req, resp, parameterMap.get("name")[0]});}@Overridepublic void init(ServletConfig config) throws ServletException {//1.加载配置文件doLoadConfig(config.getInitParameter("contextConfigLocation"));//2.扫描相关的类doScanner(contextConfig.getProperty("scanPackage"));//3.实例化相关的类,并且缓存到ico容器doInstance();//4.完成依赖注入doAutowired();//5.初始化HandlerMappingdoInitHandlerMapping();System.out.println("SZ spring framework Initialization is complete");}private void doInitHandlerMapping() {if (ioc.isEmpty()) return;try {for (Map.Entry<String, Object> entry : ioc.entrySet()) {Class<?> clazz = entry.getValue().getClass();if (!clazz.isAnnotationPresent(SZController.class)) continue;String baseUrl = "";if (clazz.isAnnotationPresent(SZRequestMapping.class)) {SZRequestMapping requestMapping = clazz.getAnnotation(SZRequestMapping.class);baseUrl = requestMapping.value();}for (Method method : clazz.getMethods()) {if (!method.isAnnotationPresent(SZRequestMapping.class)) continue;SZRequestMapping requestMapping = method.getAnnotation(SZRequestMapping.class);String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");if (handlerMapping.containsKey(url)) {throw new Exception("The handlerMapping is exist");}handlerMapping.put(url, method);System.out.println("Mapped" + url + "," + method);}}} catch (Exception e) {e.printStackTrace();}}private void doAutowired() {if (ioc.isEmpty()) return;for (Map.Entry<String, Object> entry : ioc.entrySet()) {Field[] fields = entry.getValue().getClass().getDeclaredFields();for (Field field : fields) {if (!field.isAnnotationPresent(SZAutowired.class)) continue;SZAutowired autowired = field.getAnnotation(SZAutowired.class);String beanName = autowired.value().trim();if ("".equals(beanName)) {beanName = field.getType().getName();}//暴力强制访问field.setAccessible(true);try {//传说中的依赖注入field.set(entry.getValue(), ioc.get(beanName));} catch (IllegalAccessException e) {e.printStackTrace();}}}}private void doInstance() {if (classNames.isEmpty()) return;try {for (String className : classNames) {Class clazz = Class.forName(className);if (clazz.isAnnotationPresent(SZController.class)) {String beanName = toLowerFirstCase(clazz.getSimpleName());Object instance = clazz.newInstance();ioc.put(beanName, instance);} else if (clazz.isAnnotationPresent(SZService.class)) {//1.默认是类名首字母小写String beanName = toLowerFirstCase(clazz.getSimpleName());//2.自定义命名SZService service = (SZService) clazz.getAnnotation(SZService.class);if (!"".equals(service.value())) {beanName = service.value();}Object instance = clazz.newInstance();//--------这是aop相关的,可以先忽略这块代码--------/*SZAdvisedSupport config = instantionAopConfig();config.setTargetClass(clazz);config.setTarget(instance);if (config.ponitCutMatch()) {instance = new SZJdkDynamicAopProxy(config).getProxy();}*///-------------------------------------------------ioc.put(beanName, instance);//3.如果是接口for (Class i : clazz.getInterfaces()) {if (ioc.containsKey(i.getName())) {throw new Exception("The beanNames is exist");}ioc.put(i.getName(), instance);}}}} catch (Exception e) {e.printStackTrace();}}private SZAdvisedSupport instantionAopConfig() {SZAopConfig config = new SZAopConfig();config.setPointCut(contextConfig.getProperty("pointCut"));config.setAspectClass(contextConfig.getProperty("aspectClass"));config.setAspectBefore(contextConfig.getProperty("aspectBefore"));config.setAspectAfter(contextConfig.getProperty("aspectAfter"));config.setAspectAfterThrow(contextConfig.getProperty("aspectAfterThrow"));config.setAspectAfterThrowingName(contextConfig.getProperty("aspectAfterThrowingName"));return new SZAdvisedSupport(config);}/*** 类首字母小写** @param simpleName 类名* @return 结果*/private String toLowerFirstCase(String simpleName) {char[] chars = simpleName.toCharArray();chars[0] += 32;return String.valueOf(chars);}private void doScanner(String scanPackage) {URL 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);}}}private void doLoadConfig(String contextConfigLocation) {//获取配置文件的文件流InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);try {contextConfig.load(is);} catch (IOException e) {e.printStackTrace();} finally {if (null != is) {try {is.close();} catch (IOException e) {e.printStackTrace();}}}}
}

3.5 SZAutowired、SZControllerSZController、SZRequestMapping、SZService

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZAutowired {String value() default "";
}
-----------------------------------------------------------------------------------@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZController {String value() default "";
}
-----------------------------------------------------------------------------------@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZRequestMapping {String value() default "";
}
-----------------------------------------------------------------------------------@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SZService {String value() default "";
}

3.6 IDemoService.java、DemoServiceImpl.java、DemoController.java

public interface IDemoService {String get(String name);
}
------------------------------------------------------------------------------------@SZService
public class DemoServiceImpl implements IDemoService {@Overridepublic String get(String name) {System.out.println("Invoker DemoService get method!!");
//        int a = 1 / 0;return "My name is " + name + ",from service";}
}
------------------------------------------------------------------------------------@SZController
@SZRequestMapping("/demo")
public class DemoController {@SZAutowiredprivate IDemoService iDemoService;@SZRequestMapping("/query")public void query(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,String name) {String result = iDemoService.get(name);try {httpServletResponse.getWriter().write(result);} catch (IOException e) {e.printStackTrace();}}
}

4、结果

项目启动后,访问controller地址:
/demo/query?name=sz

iDemoService.get(name)执行成功, 即成功注册了bean,并且进行了依赖注入。

5、springICO循环依赖问题

5.1什么是循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。
比如,A依赖于B,B又依赖于A。
Spring中循环依赖场景有:

  • 构造器的循环依赖(构造器注入)
  • Field 属性的循环依赖(set注入)(我们上面手写的springioc就是知使用了Field 属性进行注入,只不过我们只是简单的实现其功能,并不支持循环依赖)

5.2循环依赖处理机制

• 单例 bean 构造器参数循环依赖(无法解决)
• prototype 原型 bean循环依赖(无法解决)
• 单例bean通过setXxx或者@Autowired进行循环依赖的解决方案:


beanA初始化时,图中1 先将该beanA放入 Map<String, ObjectFactory<?>> singletonFactories 中,然会给beanA的属性赋值时,如果需要注入beanB,则在singletonFactories 中找有没有beanB,如果有则注入,如果没有则创建beanB。

在创建beanB时依旧会将beanB放入Map<String, ObjectFactory<?>> singletonFactories 中,给beanB的属性赋值时,如果需要注入beanA,则在singletonFactories 中寻找beanA注入,此时beanA已经在map中了,所以成功注入beanA,并且创建beanB成功。

最后再将beanB从singletonFactories中取出来,然后注入给beanA,这样beanA和beanB都完成了对象初始化操作,解决了循环依赖问题。

三、手写实现AOP

1、基本思路


AdvisedSupport 专门用来解析AOP配置的类

AopConfig 封装AOP的配置信息(把键值对封装成对象)

Advice 通知。 封装了要织入的切面类,回调的方法

JdkDynamicAopProxy 用来生成代理类

这几个类是aop主要的类,我们模仿这几个类,实现aop相关功能。

2、目录结构


aop实现的代码写在了ioc实例化的方法中(3.4 doInstance()方法中注释的那段代码),因为要把代理类注入到容器中,所以在这个位置实现aop的相关功能。

3、代码

3.1 SZAopConfig.java、TestAspect.java、SZAdivce.java

@Data
public class SZAopConfig {private String pointCut;private String aspectClass;private String aspectBefore;private String aspectAfter;private String aspectAfterThrow;private String aspectAfterThrowingName;
}
---------------------------------------------------------------------------public class TestAspect {public void before() {System.out.print("Invoker Before Method!!\n");}public void after() {System.out.print("Invoker After Method!!\n");}public void afterThrowing() {System.out.print("出现异常!!\n");}
}
---------------------------------------------------------------------------@Data
public class SZAdivce {private Object aspect;private Method adviceMethod;private String throwName;public SZAdivce(Object aspect, Method adviceMethod) {this.aspect = aspect;this.adviceMethod = adviceMethod;}
}

3.2 SZAdvisedSupport.java

public class SZAdvisedSupport {private Class targetClass;private Object target;private SZAopConfig config;private Pattern pointCutClassPattern;Map<Method, Map<String, SZAdivce>> methodCache;public SZAdvisedSupport(SZAopConfig config) {this.config = config;}public boolean ponitCutMatch() {return pointCutClassPattern.matcher(this.targetClass.getName()).matches();}public Map<String, SZAdivce> getAdvices(Method method, Class targetClass) throws Exception {Map<String, SZAdivce> cache = methodCache.get(method);if (cache == null) {Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());cache = methodCache.get(m);this.methodCache.put(method, cache);}return cache;}private void parse() {//public .* com.sz.handwirttenspring.service..*Service..*(.*)String pointCut = config.getPointCut().replaceAll("\\.", "\\\\.").replaceAll("\\\\.\\*", ".*").replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));try {methodCache = new HashMap<>();//先缓存所有的通知回调方法Map<String, Method> aspectMethods = new HashMap<>();Class aspectClass = Class.forName(this.config.getAspectClass());for (Method method : aspectClass.getMethods()) {aspectMethods.put(method.getName(), method);}Pattern pointCutPattern = Pattern.compile(pointCut);for (Method method : this.targetClass.getMethods()) {String methodString = method.toString();if (methodString.contains("throws")) {methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();}Matcher matcher = pointCutPattern.matcher(methodString);if (matcher.matches()) {Map<String, SZAdivce> advices = new HashMap<>();//前置通知if (!(config.getAspectBefore() == null || "".equals(config.getAspectBefore()))) {advices.put("before", new SZAdivce(aspectClass.newInstance(), aspectMethods.get(config.getAspectBefore())));}//后置通知if (!(config.getAspectAfter() == null || "".equals(config.getAspectAfter()))) {advices.put("after", new SZAdivce(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfter())));}//异常通知if (!(config.getAspectAfterThrow() == null || "".equals(config.getAspectAfterThrow()))) {SZAdivce adivce = new SZAdivce(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfterThrow()));adivce.setThrowName(config.getAspectAfterThrowingName());advices.put("afterThrowing", adivce);}methodCache.put(method, advices);}}} catch (Exception e) {e.printStackTrace();}}public Class getTargetClass() {return targetClass;}public void setTargetClass(Class targetClass) {this.targetClass = targetClass;parse();}public Object getTarget() {return target;}public void setTarget(Object target) {this.target = target;}public SZAopConfig getConfig() {return config;}public void setConfig(SZAopConfig config) {this.config = config;}
}

3.3 SZJdkDynamicAopProxy.java

public class SZJdkDynamicAopProxy implements InvocationHandler {private SZAdvisedSupport config;public SZJdkDynamicAopProxy(SZAdvisedSupport config) {this.config = config;}public Object getProxy() {return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.config.getTargetClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Map<String, SZAdivce> adivces = this.config.getAdvices(method, this.config.getTargetClass());Object returnValue = null;try {invokeAdvice(adivces.get("before"));returnValue = method.invoke(this.config.getTarget(), args);invokeAdvice(adivces.get("after"));} catch (Exception e) {invokeAdvice(adivces.get("afterThrowing"));}return returnValue;}private void invokeAdvice(SZAdivce adivce) {try {adivce.getAdviceMethod().invoke(adivce.getAspect());} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}
}

4、结果

启动项目后,再次访问/demo/query?name=sz,控制台打印:

测试下出现异常时的情况(放开 DemoServiceImpl 中 get() 的 int a = 1 / 0;)

四、本文源码

https://github.com/sunzhensky/handWrittenSpring.git

手写springIOC、AOP相关推荐

  1. 手写 springIoc 注解版 ,实现@Service (beng),@Resource (依赖注入)

    手写springIoc 注解版 代码demo https://pan.baidu.com/s/1jyvLMDrg_bfpKmhtrTTZSQ 提取码:5ju1 代码目录结构 1.pom.xml < ...

  2. 手写springioc注解注入对象基本实现

    还是要养成写注释的习惯,首先一个代码想让人家看懂的情况下,记住一点,重构加设计模式,其实设计模式也比较好,达到别人可易读性,这是我要跟你讲的,而且你们不写注释是一个不好的习惯,你们一定要养成我善于重构 ...

  3. 手写Spring-ioc 注入 jdk反射实现 绝对满足你的好奇心哦 解决怎么我写了一个注解就可以直接注入了?

    Spring-Ioc原理 解析 我们学完Spring后,大都就直接接着学习之后的内容啦,但是我想偶尔回过头来看一看,才能走的更远啊. 温故而知新. 关于Spring是怎么实现的?怎么我写了一个注解就可 ...

  4. 用Maven手写SpringIOC(简易版)

    这里我是实现了如何获取配置文件中的bean对象 一.文件结构 二.需要的pom.xml依赖 <dependencies><dependency><groupId>d ...

  5. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)...

    一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...

  6. 两万字吐血总结,代理模式及手写实现动态代理(aop原理,基于jdk动态代理)

    代理模式及手写实现动态代理 一.代理模式 1. 定义 2. 示例 (1)静态代理 (2)动态代理 3. 通用类图 4. 代理模式的优点 二.jdk动态代理实现原理 1. jdk动态代理源码分析(通过该 ...

  7. 手写 Spring 事务、IOC、DI 和 MVC

    Spring AOP 原理 什么是 AOP? AOP 即面向切面编程,利用 AOP 可以对业务进行解耦,提高重用性,提高开发效率 应用场景:日志记录,性能统计,安全控制,事务处理,异常处理 AOP 底 ...

  8. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

  9. JAVA项目代码手写吗_一个老程序员是如何手写Spring MVC的

    见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十多 ...

最新文章

  1. 华为交换机配置命令 华为QuidWay交换机配置命令手册
  2. .Toolkit 增补
  3. jinja2的url_for 和数据块
  4. MyBatis及Spring事务初学总结
  5. [你必须知道的.NET]第三十二回,,深入.NET 4.0之,Tuple一二
  6. 《Java 7程序设计入门经典》一3.7 for循环
  7. COM, COM+ and .NET 的区别
  8. 简单团队-爬取豆瓣电影T250-项目进度
  9. cocos2dx基础篇(2)——Win32移植到Android
  10. ruby hash方法_Ruby中带有示例的Hash.keys方法
  11. 【hiho挑战赛24 ABC】贪心和期望dp惨烈的后缀自动机
  12. Facebook广告兴趣定位终极指南经验分享
  13. Linux 搜索 查找find命令 详解
  14. mongodb用户管理和服务安装
  15. OpenCV人工智能图像处理学习笔记2 opencv初识图片保存像素理解
  16. html在ie中img地址为https,关于IE10以下的img标签问题解决
  17. dokuwiki之新增/删除页面(文章)(一)
  18. 什么是MES系统,实施难度大不大?
  19. 【问题解决】Invalid bound statement(not found)
  20. 魔百和E900V22C_905L3A(B)_5621DS-安卓9.0-纯净语音

热门文章

  1. 如何隐藏table 中的指定列?
  2. 南卫理公会大学计算机科学,南卫理公会大学计算机科学研究生语言及申请要求-费用-课程设置...
  3. 原生js代码实现图片放大境效果
  4. MOD8ID加密芯片的使用以及示例讲解
  5. 腾讯音乐、网易云音乐殊途同归?
  6. java计算机毕业设计自习室座位预约管理源码+mysql数据库+系统+lw文档+部署
  7. 介绍一下我大二开发的游戏:地下城冒险
  8. Android熄屏与亮屏控制
  9. 计算机模拟需要什么配置电脑,城市天际线配置要求 最低电脑配置要求
  10. Superset 制作 地图 柱状图 饼状图