├── README.md
└── mini-spring
├── mini-spring2.iml
├── test
├── target
│ └── test-1.0-SNAPSHOT.jar
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── shenghao
│ │ ├── service
│ │ └── SalaryService.java
│ │ ├── Application.java
│ │ └── controller
│ │ └── SalaryController.java
└── pom.xml
├── framework
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── shenghao
│ │ ├── beans
│ │ ├── Bean.java
│ │ ├── AutoWired.java
│ │ └── BeanFactory.java
│ │ ├── web
│ │ ├── mvc
│ │ │ ├── Controller.java
│ │ │ ├── RequestMapping.java
│ │ │ └── RequestParam.java
│ │ ├── handler
│ │ │ ├── MappingHandler.java
│ │ │ └── HandlerManager.java
│ │ ├── servlet
│ │ │ └── DispatcherServlet.java
│ │ └── server
│ │ │ └── TomcatServer.java
│ │ ├── starter
│ │ └── MiniApplication.java
│ │ └── core
│ │ └── ClassScanner.java
└── pom.xml
└── pom.xml
/README.md:
--------------------------------------------------------------------------------
1 | # mini-spring
2 |
3 | 本项目实现了简单的IOC,MVC功能
4 |
5 |
--------------------------------------------------------------------------------
/mini-spring/mini-spring2.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mini-spring/test/target/test-1.0-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyj24/mini-spring/HEAD/mini-spring/test/target/test-1.0-SNAPSHOT.jar
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/beans/Bean.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.beans;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.TYPE)
8 | public @interface Bean {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/mvc/Controller.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.mvc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.TYPE)
8 | public @interface Controller {
9 | }
10 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/beans/AutoWired.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.beans;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.FIELD)
8 | public @interface AutoWired {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/mini-spring/test/src/main/java/com/shenghao/service/SalaryService.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.service;
2 |
3 | import com.shenghao.beans.Bean;
4 |
5 | @Bean
6 | public class SalaryService {
7 | public Integer calSalary(Integer experience){
8 | return experience * 5000;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/mvc/RequestMapping.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.mvc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.METHOD)
8 | public @interface RequestMapping {
9 | String value();
10 | }
11 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/mvc/RequestParam.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.mvc;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Documented
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Target(ElementType.PARAMETER)
8 | public @interface RequestParam {
9 | String value();
10 | }
11 |
--------------------------------------------------------------------------------
/mini-spring/test/src/main/java/com/shenghao/Application.java:
--------------------------------------------------------------------------------
1 | package com.shenghao;
2 |
3 | import com.shenghao.starter.MiniApplication;
4 |
5 | public class Application {
6 |
7 | public static void main(String[] args) {
8 | System.out.println("Hello world!");
9 | MiniApplication.run(Application.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mini-spring/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 | pom
7 | com.shenghao
8 | mini-spring2
9 | 1.0-SNAPSHOT
10 |
11 | framework
12 | test
13 |
14 |
15 |
--------------------------------------------------------------------------------
/mini-spring/test/src/main/java/com/shenghao/controller/SalaryController.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.controller;
2 |
3 | import com.shenghao.beans.AutoWired;
4 | import com.shenghao.service.SalaryService;
5 | import com.shenghao.web.mvc.Controller;
6 | import com.shenghao.web.mvc.RequestMapping;
7 | import com.shenghao.web.mvc.RequestParam;
8 |
9 | @Controller
10 | public class SalaryController {
11 |
12 | @AutoWired
13 | private SalaryService salaryService;
14 |
15 | @RequestMapping("/getSalary")
16 | public Integer getSalary(@RequestParam("name")String name,
17 | @RequestParam("experience")String experience){
18 | return salaryService.calSalary(Integer.parseInt(experience));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/starter/MiniApplication.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.starter;
2 |
3 | import com.shenghao.beans.BeanFactory;
4 | import com.shenghao.core.ClassScanner;
5 | import com.shenghao.web.handler.HandlerManager;
6 | import com.shenghao.web.server.TomcatServer;
7 |
8 | import java.util.List;
9 |
10 | public class MiniApplication {
11 |
12 | public static void run(Class> cls, String[] args){
13 | System.out.println("Hello mini-spring!");
14 | TomcatServer tomcatServer = new TomcatServer(args);
15 | try {
16 | //启动tomcat
17 | tomcatServer.startServer();
18 | //扫描启动类下所有的.class文件
19 | System.out.println(cls.getPackage().getName());
20 | List> classList = ClassScanner.scanClass(cls.getPackage().getName());
21 | //初始化bean工厂
22 | BeanFactory.initBean(classList);
23 | //解析所有.class文件,获得mappingHandler集合
24 | HandlerManager.resolveMappingHandler(classList);
25 | } catch (Exception e) {
26 | e.printStackTrace();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/mini-spring/framework/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | jar
6 |
7 | mini-spring2
8 | com.shenghao
9 | 1.0-SNAPSHOT
10 |
11 | 4.0.0
12 |
13 | framework
14 |
15 |
16 |
17 | org.apache.maven.plugins
18 | maven-compiler-plugin
19 |
20 | 8
21 | 8
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | org.apache.tomcat.embed
30 | tomcat-embed-core
31 | 8.5.55
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/handler/MappingHandler.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.handler;
2 |
3 | import com.shenghao.beans.BeanFactory;
4 | import org.apache.catalina.servlet4preview.http.HttpServletRequest;
5 | import sun.rmi.transport.ObjectTable;
6 |
7 | import javax.servlet.ServletRequest;
8 | import javax.servlet.ServletResponse;
9 | import java.io.IOException;
10 | import java.lang.reflect.InvocationTargetException;
11 | import java.lang.reflect.Method;
12 |
13 | public class MappingHandler {
14 |
15 | private String uri;
16 | private Method method;
17 | private Class> controller;
18 | private String[] args;
19 |
20 | MappingHandler(String uri, Method method, Class> cls, String[] args){
21 | this.uri = uri;
22 | this.method = method;
23 | this.controller = cls;
24 | this.args = args;
25 | }
26 |
27 | public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
28 | //拿到请求的uri
29 | String requestUri = ((HttpServletRequest)req).getRequestURI();
30 | if(!uri.equals(requestUri)){//如果和自身uri不同就跳过
31 | return false;
32 | }
33 | Object[] parameters = new Object[args.length];
34 | for(int i = 0; i < args.length; i++){
35 | parameters[i] = req.getParameter(args[i]);
36 | }
37 | Object ctl = BeanFactory.getBean(controller);
38 | Object response = method.invoke(ctl, parameters);
39 | res.getWriter().println(response.toString());
40 | return true;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/servlet/DispatcherServlet.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.servlet;
2 |
3 | import com.shenghao.web.handler.HandlerManager;
4 | import com.shenghao.web.handler.MappingHandler;
5 |
6 | import javax.servlet.*;
7 | import java.io.IOException;
8 | import java.lang.reflect.InvocationTargetException;
9 |
10 | public class DispatcherServlet implements Servlet {
11 |
12 | @Override
13 | public void init(ServletConfig servletConfig) throws ServletException {
14 |
15 | }
16 |
17 | @Override
18 | public ServletConfig getServletConfig() {
19 | return null;
20 | }
21 |
22 | @Override
23 | public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
24 | // servletResponse.getWriter().println("test");
25 | System.out.println("测试有多少个mappingHandler:" + HandlerManager.mappingHandlerList.size());
26 | for(MappingHandler mappingHandler : HandlerManager.mappingHandlerList){
27 | try{
28 | if (mappingHandler.handle(servletRequest, servletResponse)){
29 | return;
30 | }
31 | } catch (IllegalAccessException e) {
32 | e.printStackTrace();
33 | } catch (InstantiationException e) {
34 | e.printStackTrace();
35 | } catch (InvocationTargetException e) {
36 | e.printStackTrace();
37 | }
38 | }
39 | }
40 |
41 | @Override
42 | public String getServletInfo() {
43 | return null;
44 | }
45 |
46 | @Override
47 | public void destroy() {
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/server/TomcatServer.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.server;
2 |
3 | import com.shenghao.web.servlet.DispatcherServlet;
4 | import org.apache.catalina.Context;
5 | import org.apache.catalina.LifecycleException;
6 | import org.apache.catalina.core.StandardContext;
7 | import org.apache.catalina.startup.Tomcat;
8 |
9 | /**
10 | * 内嵌Tomcat服务器
11 | */
12 | public class TomcatServer {
13 | private Tomcat tomcat;//自带一个tomcat实例
14 | private String[] args;
15 |
16 | public TomcatServer(String[] args){
17 | this.args = args;
18 | }
19 |
20 | public void startServer() throws LifecycleException {
21 | tomcat = new Tomcat();
22 | tomcat.setPort(6699);//设置tomcat在操作系统上监听的端口号
23 | tomcat.start();//启动tomcat
24 |
25 | Context context = new StandardContext();//tomcat中的容器是分层级的,存放servlet的是context容器,先初始化一个
26 | context.setPath("");//设置context的路径为空
27 | context.addLifecycleListener(new Tomcat.FixContextListener());//注册一个监听器
28 |
29 | DispatcherServlet servlet = new DispatcherServlet();//创建DispatcherServlet对象,所有请求都先经过此处
30 | Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true);//把dispatcherServlet注册进Tomcat中,并设置为异步
31 | context.addServletMappingDecoded("/", "dispatcherServlet");//并为DispatcherServlet设置请求路径,设置为根目录意味着所有请求都会到这里
32 |
33 | tomcat.getHost().addChild(context);//把context注册到host中,host是tomcat中更高一级的容器
34 |
35 | Thread awaitThread = new Thread("tomcat_await_thread"){
36 | @Override
37 | public void run() {
38 | TomcatServer.this.tomcat.getServer().await();//设置tomcat线程一直等待,不然的话启动完就会关闭
39 | }
40 | };
41 | awaitThread.setDaemon(false);//设置为非守护线程
42 | awaitThread.start();//启动
43 | }
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/mini-spring/test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | jar
6 |
7 | mini-spring2
8 | com.shenghao
9 | 1.0-SNAPSHOT
10 |
11 | 4.0.0
12 | test
13 |
14 |
15 |
16 | com.shenghao
17 | framework
18 | 1.0-SNAPSHOT
19 |
20 |
21 |
22 |
23 |
24 | org.apache.maven.plugins
25 | maven-shade-plugin
26 | 1.2.1
27 |
28 |
29 | package
30 |
31 | shade
32 |
33 |
34 |
35 |
36 | com.shenghao.Application
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/web/handler/HandlerManager.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.web.handler;
2 |
3 | import com.shenghao.web.mvc.Controller;
4 | import com.shenghao.web.mvc.RequestMapping;
5 | import com.shenghao.web.mvc.RequestParam;
6 |
7 | import java.lang.reflect.Method;
8 | import java.lang.reflect.Parameter;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | public class HandlerManager {
13 |
14 | public static List mappingHandlerList = new ArrayList<>();
15 |
16 | /**
17 | * 处理类文件集合,挑出MappingHandler
18 | * @param classList
19 | */
20 | public static void resolveMappingHandler(List> classList){
21 | System.out.println("测试有多少个.class文件:" + classList.size());
22 | for(Class> cls : classList){
23 | if(cls.isAnnotationPresent(Controller.class)){//MappingHandler会在controller里面
24 | parseHandlerFromController(cls);//继续从controller中分离出一个个MappingHandler
25 | }
26 | }
27 | }
28 |
29 | private static void parseHandlerFromController(Class> cls) {
30 | //先获取该controller中所有的方法
31 | Method[] methods = cls.getDeclaredMethods();
32 | //从中挑选出被RequestMapping注解的方法进行封装
33 | for(Method method : methods){
34 | if(!method.isAnnotationPresent(RequestMapping.class)){
35 | continue;
36 | }
37 | String uri = method.getDeclaredAnnotation(RequestMapping.class).value();//拿到RequestMapping定义的uri
38 | List paramNameList = new ArrayList<>();//保存方法参数的集合
39 | for(Parameter parameter : method.getParameters()){
40 | if(parameter.isAnnotationPresent(RequestParam.class)){//把有被RequestParam注解的参数添加入集合
41 | paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
42 | }
43 | }
44 | String[] params = paramNameList.toArray(new String[paramNameList.size()]);//把参数集合转为数组,用于反射
45 | MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);//反射生成MappingHandler
46 | mappingHandlerList.add(mappingHandler);//把mappingHandler装入集合中
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/beans/BeanFactory.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.beans;
2 |
3 | import com.shenghao.web.mvc.Controller;
4 |
5 | import java.lang.reflect.Field;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.concurrent.ConcurrentHashMap;
10 |
11 | /**
12 | * 初始化bean
13 | * 获得bean
14 | */
15 | public class BeanFactory {
16 |
17 | //保存Bean实例的映射集合
18 | private static Map, Object> classToBean = new ConcurrentHashMap<>();
19 |
20 | /**
21 | * 根据class类型获取bean
22 | * @param cls
23 | * @return
24 | */
25 | public static Object getBean(Class> cls){
26 | return classToBean.get(cls);
27 | }
28 |
29 | /**
30 | * 初始化bean工厂
31 | * @param classList 需要一个.class文件集合
32 | * @throws Exception
33 | */
34 | public static void initBean(List> classList) throws Exception {
35 | //先创建一个.class文件集合的副本
36 | List> toCreate = new ArrayList<>(classList);
37 | //循环创建bean实例
38 | while(toCreate.size() != 0){
39 | int remainSize = toCreate.size();//记录开始时集合大小,如果一轮结束后大小没有变证明有相互依赖
40 | for(int i = 0; i < toCreate.size(); i++){//遍历创建bean,如果失败就先跳过,等下一轮再创建
41 | if(finishCreate(toCreate.get(i))){
42 | toCreate.remove(i);
43 | }
44 | }
45 | if(toCreate.size() == remainSize){//有相互依赖的情况先抛出异常
46 | throw new Exception("cycle dependency!");
47 | }
48 | }
49 | }
50 |
51 | private static boolean finishCreate(Class> cls) throws IllegalAccessException, InstantiationException {
52 | //创建的bean实例仅包括Bean和Controller注释的类
53 | if(!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
54 | return true;
55 | }
56 | //先创建实例对象
57 | Object bean = cls.newInstance();
58 | //看看实例对象是否需要执行依赖注入,注入其他bean
59 | for(Field field : cls.getDeclaredFields()){
60 | if(field.isAnnotationPresent(AutoWired.class)){
61 | Class> fieldType = field.getType();
62 | Object reliantBean = BeanFactory.getBean(fieldType);
63 | if(reliantBean == null){//如果要注入的bean还未被创建就先跳过
64 | return false;
65 | }
66 | field.setAccessible(true);
67 | field.set(bean, reliantBean);
68 | }
69 | }
70 | classToBean.put(cls, bean);
71 | return true;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/mini-spring/framework/src/main/java/com/shenghao/core/ClassScanner.java:
--------------------------------------------------------------------------------
1 | package com.shenghao.core;
2 |
3 | import java.io.IOException;
4 | import java.net.JarURLConnection;
5 | import java.net.URL;
6 | import java.util.ArrayList;
7 | import java.util.Enumeration;
8 | import java.util.List;
9 | import java.util.jar.JarEntry;
10 | import java.util.jar.JarFile;
11 |
12 | public class ClassScanner {
13 | public static List> scanClass(String packageName) throws IOException, ClassNotFoundException {
14 | //用于保存结果的容器
15 | List> classList = new ArrayList<>();
16 | //把文件名改为文件路径
17 | String path = packageName.replace(".", "/");
18 | //获取默认的类加载器
19 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
20 | //通过文件路径获取该文件夹下所有资源的URL
21 | Enumeration resources = classLoader.getResources(path);
22 |
23 | int index = 0;//测试
24 |
25 | while(resources.hasMoreElements()){
26 | System.out.println(++index);//测试
27 |
28 | //拿到下一个资源
29 | URL resource = resources.nextElement();
30 | //先判断是否是jar包,因为默认.class文件会被打包为jar包
31 | if(resource.getProtocol().contains("jar")){
32 |
33 | System.out.println("拿到一个jar包");//测试
34 |
35 | //把URL强转为jar包链接
36 | JarURLConnection jarURLConnection = (JarURLConnection)resource.openConnection();
37 | //根据jar包获取jar包的路径名
38 | String jarFilePath = jarURLConnection.getJarFile().getName();
39 | //把jar包下所有的类添加的保存结果的容器中
40 | classList.addAll(getClassFromJar(jarFilePath, path));
41 | }else{//也有可能不是jar文件,先放下
42 | //todo
43 | }
44 | }
45 | return classList;
46 | }
47 |
48 | /**
49 | * 获取jar包中所有路径符合的类文件
50 | * @param jarFilePath
51 | * @param path
52 | * @return
53 | */
54 | private static List> getClassFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
55 | List> classes = new ArrayList<>();//保存结果的集合
56 | JarFile jarFile = new JarFile(jarFilePath);//创建对应jar包的句柄
57 | Enumeration jarEntries = jarFile.entries();//拿到jar包中所有的文件
58 | while(jarEntries.hasMoreElements()){
59 | JarEntry jarEntry = jarEntries.nextElement();//拿到一个文件
60 | String entryName = jarEntry.getName();//拿到文件名,大概是这样:com/shenghao/test/Test.class
61 | if (entryName.startsWith(path) && entryName.endsWith(".class")){//判断是否是类文件
62 | String classFullName = entryName.replace("/", ".")
63 | .substring(0, entryName.length() - 6);
64 | classes.add(Class.forName(classFullName));
65 | }
66 | }
67 | return classes;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------