在我们的日常工作中,经常会用到Spring、Spring Boot、Spring Cloud、Struts、Mybatis、Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越来越轻松,我们用 Spring Boot 分分钟可以新建一个Web项目。
今天通过手写Spring框架,帮大家深入了解一下Spring的工作机制,文中涉及的代码只用来帮助大家理解Spring,不会在线上使用,有不严谨的地方还请大家掠过。
首先新建一个 HttpServlet 的实现类 MarsDispatcherServlet,用来接收请求。
public class MarsDispatcherServlet extends HttpServlet {
@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. 处理请求
}
@Override
public void init(ServletConfig config) throws ServletException {
}
Spring Mvc Education
marsmvc
com.mars.framework.servlet.MarsDispatcherServlet
contextConfigLocation
application.properties
1
marsmvc
/*
配置application.properties
scanPackage=com.mars.demo
这个比较好理解,仅配置了一项内容,意思是要扫描的包,随后我们会获取这个值去加载容器。
定义我们常用的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsController {
String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsRequestMapping {
String value() default "";
}
先列出框架在初始化的时候都要做那些事情
接下来我们一步步完成上面的操作
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("===================");
//1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
//2.扫描所有相关联的类
doScanner(contextConfig.getProperty("scanPackage"));
//3.初始化所有相关联的类,并且将其保存在IOC容器里面
doInstance();
//4.执行依赖注入(把加了@Autowired注解的字段赋值)
doAutowired();
//Spring 和核心功能已经完成 IOC、DI
//5.构造HandlerMapping,将URL和Method进行关联
initHandlerMapping();
System.out.println("Mars MVC framework initialized");
}
private Properties contextConfig = new Properties();
private void doLoadConfig(String location) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
contextConfig.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doInstance() {
if(classNames.isEmpty()) return;
for(String className: classNames) {
try {
Class> clazz = Class.forName(className);
if(clazz.isAnnotationPresent(MarsController.class)) {
Object instance = clazz.newInstance();
String beanName = lowerFirstCase(clazz.getSimpleName());
ioc.put(beanName, instance);
} else if (clazz.isAnnotationPresent(MarsService.class)) {
MarsService service = clazz.getAnnotation(MarsService.class);
//2.优先使用自定义命名
String beanName = service.value();
if("".equals(beanName.trim())) {
//1.默认使用类名首字母小写
beanName = lowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
//3.自动类型匹配(例如:将实现类赋值给接口)
Class> [] interfaces = clazz.getInterfaces();
for(Class> inter: interfaces) {
ioc.put(inter.getName(), instance);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//利用ASCII码的差值
private String lowerFirstCase(String str) {
char[] chars = str.toCharArray();
chars[0] = 32;
return String.valueOf(chars);
}
private void doAutowired() {
if(ioc.isEmpty()) return;
for(Map.Entry entry: ioc.entrySet()) {
//注入的意思就是把所有的IOC容器中加了@Autowired注解的字段赋值
//包含私有字段
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field : fields) {
//判断是否加了@Autowired注解
if(!field.isAnnotationPresent(MarsAutowired.class)) continue;
MarsAutowired autowired = field.getAnnotation(MarsAutowired.class);
String beanName = autowired.value();
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 initHandlerMapping() {
if(ioc.isEmpty()) return;
for(Map.Entry entry : ioc.entrySet()) {
Class> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(MarsController.class)) continue;
String baseUrl = "";
if(clazz.isAnnotationPresent(MarsRequestMapping.class)) {
MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class);
baseUrl = requestMapping.value();
}
Method[] methods = clazz.getMethods();
for(Method method : methods) {
if(!method.isAnnotationPresent(MarsRequestMapping.class)) continue;
MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class);
String regex = requestMapping.value();
regex = (baseUrl regex).replaceAll("/ ", "/");
Pattern pattern = Pattern.compile(regex);
handlerMapping.add(new Handler(entry.getValue(), method, pattern));
System.out.println("Mapping: " regex "," method.getName());
}
}
}
@MarsController
@MarsRequestMapping("/demo")
public class DemoApi {
@MarsAutowired
private DemoService demoService;
@MarsRequestMapping("/query")
public void query(HttpServletRequest req,
HttpServletResponse resp,
@MarsRequestParam("name") String name) {
System.out.println("name: " name);
String result = demoService.get(name);
try{
resp.getWriter().write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@MarsRequestMapping("/add")
public void add(HttpServletRequest req,
HttpServletResponse resp,
@MarsRequestParam("a") Integer a,
@MarsRequestParam("b") Integer b) {
try {
resp.getWriter().write(String.format("%d %d=%d", a, b, (a b)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
提供两个接口,一个通过请求名称返回响应的介绍内容,另一个将请求的两个Integer相加并返回。
public interface DemoService {
String get(String name);
}
@MarsService
public class DemoServiceImpl implements DemoService {
public String get(String name) {
return String.format("My name is %s.", name);
}
}
org.mortbay.jetty
jetty-maven-plugin
7.1.6.v20100715
9988
foo
5
8080
60000
/
点击 jetty:run 运行项目
浏览器访问: http://localhost:8080/demo/query?name=Mars
浏览器访问:http://localhost:8080/demo/add?a=10&b=20
这样一个完整的spring框架就已经手写出来了,大家也可以关注下我的宫众浩【java开发之路】
为大家准备好了2019最新的面试教程和架构师资料,感谢大家的支持与关注!