【从0开始手写一个可用的springboot】AutumnMvc

1.写在前面的话:

1.1:项目描述:

不依赖TomCat,Servlet等技术实现的网络服务框架,参照了Mybatis,SpringMvc等设计理念从0手写了一个基于注解的springboot框架

1.2: 项目地址:

GitHub: Autumnmvc

1.3 :项目实现了什么

  • 实现了ioc容器,以及字段的依赖注入.注入实现类或者配置文件
  • 可以在接口上自动注入实现类
  • 使用Cglib实现简易的Aop,用户可自定义代理的方法以及自定义切面处理器
  • url-method映射,实现url与controller方法的路由表
  • controller控制器可以直接写形参,框架自动注入内容(Request/Response/请求参数等等)
  • 自适应controller返回值类型,依照返回值自动返回HTML/JS/ICON/JSON等
  • Get/Post请求的处理
  • 实现简易orm框架方便与数据库交互,写法和Mybatis保持一致
  • @Bean功能,对本不纳入框架管辖的类进行纳入处理
  • 用户通过重写autumnMvcConfig接口覆盖默认实现类,实现自定义首页面,Icon等
  • 类级别的条件注解的完整加入,用户可以自定义处理器,只需要实现condition接口覆盖match逻辑
  • 简易的redistemplate加入,可以连接redis
  • response类加入,用户可以选择自己来控制返回头和内容,例如进行setCookie操作
  • cookie,session加入,自动为新用户setCookie,设置JSESSIONID
  • 依照JSESSIONID的value查找对应的session

1.4:项目截图

1.4.1 :注入字段/配置文件

在这里插入图片描述

1.4.2:controller参数的自动注入

在这里插入图片描述

在这里插入图片描述

1.4.3 Mapper层代理接口并完成实体类映射

在这里插入图片描述

1.4.4 用户自定义Aop处理器

在这里插入图片描述

1.4.5 @Bean功能实现

在这里插入图片描述

1.4.6 条件注解的实现

在这里插入图片描述

在这里插入图片描述

1.4.7 用户实现接口覆盖框架默认逻辑

在这里插入图片描述

1.4.8 用户自定义过滤器

在这里插入图片描述

1.4.9 三级缓存解决循环依赖

在这里插入图片描述

1.4.10 框架默认配置文件

在这里插入图片描述

1.5:作者信息

大四实习,没啥事干完成了这个项目

1.6:项目缺陷

仅仅是练手项目和对Spring的拙劣模仿,很多Bug依然可以复现,而且对SpringBoot底层也不是那么了解,仅仅是依照我解除到的知识和Gpt4的帮助之下完成了这个项目,因为前期没有加入BeanDefinition导致后期代码充满了漫无目的的反射,影响效率

1.7: 项目依赖:

  • Redis驱动
 <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>5.1.0</version>
</dependency>
  • 由于Cglib官方库已经暂停更新,Java17不支持Cglib生成的子类,在Java17以下也会出现非法反射的警告,而且无法消除,因此使用Spring官方重写的Cglib,完美兼容Java17+,并且Api保持不变,无需重构
<!--动态代理库-->
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.15</version>
 </dependency>
  • Lombok
<!--        lombok-->
<dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.26</version>
           <scope>provided</scope>
</dependency>
  • 高效的注解扫描器
<!--        注解扫描器-->
<dependency>
           <groupId>org.reflections</groupId>
           <artifactId>reflections</artifactId>
           <version>0.10.2</version>
</dependency>
  • 数据库驱动/日志
<!--mysql驱动-->
<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
 </dependency>
<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
</dependency>
<dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.9</version>
 </dependency>

2 Ioc容器

2.1:把类纳入Ioc容器的管辖范围

在Java中我们可以在类上加入自定义注解来标注这个类,在使用自定义注解扫描器/其他工具类来对加上注解的类进行扫描
在这里插入图片描述
在本项目中使用reflections工具库加速注解扫描过程,如同springboot的写法一样,在Main方法中传递自身的class反射获取package,默认扫描范围便是main方法所在的包,在扫描完成后打包成Set作为原材料保存

public Set<Class<?>> findAnnotatedClassesList(String basePackage, List<Class<? extends Annotation>> annotationClasses) {
        Set<Class<?>> annotatedClasses = new HashSet<>();
        Reflections reflections = new Reflections(basePackage);
        for (Class<? extends Annotation> annotationClass : annotationClasses) {
            Set<Class<?>> annotatedTypes = reflections.getTypesAnnotatedWith(annotationClass);

            annotatedClasses.addAll(annotatedTypes);
        }
        return annotatedClasses;
    }

private void componentScan(Class<?> mainClass, MyContext myContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InstantiationException {
        AnnotationScanner scanner = new AnnotationScanner();
        List<Class<? extends Annotation>> annotations = new ArrayList<>();
        annotations.add(MyController.class);
        annotations.add(MyService.class);
        annotations.add(MyComponent.class);
        annotations.add(MyMapper.class);
        annotations.add(MyConfig.class);
        Set<Class<?>> annotatedClasses = scanner.findAnnotatedClassesList(mainClass.getPackageName(), annotations);
        long startTime = System.currentTimeMillis();
        log.info("ioc容器开始初始化");
        myContext.initIocCache(annotatedClasses);
        long endTime = System.currentTimeMillis();
        log.info("容器花费了:" + (endTime - startTime) + " 毫秒实例化");
}

2.2:初始化Ioc容器

循环依赖是Spring项目中常见的问题,本质是因为A依赖B,B依赖A导致双方无法获得对方实例进行无限递归,导致程序爆栈退出,本项目和Spring一样采用了三级缓存解决了问题,通过二级缓存的提前暴露让对方获得未完全初始化的自己,因为在IOC容器中都是默认单例的,引用指向的内存地址不会变,因此即使现在拥有的是一个残缺的对象,在容器初始化后也都会被填充完毕

    @FunctionalInterface
    interface ObjectFactory<T> {
        T getObject() throws Exception;
    }


    private static volatile MyContext instance;

    private MyContext() {
    }

    public static MyContext getInstance() {
        if (instance == null) {
            synchronized (MyContext.class) {
                if (instance == null) {
                    instance = new MyContext();
                }
            }
        }
        return instance;
    }
    private  AopProxyFactory aopProxyFactory= new AopProxyFactory();
    private Map<String, Object> sharedMap = new HashMap<>();
    private Set<Class<?>> iocContainer;
    //xxx:一级缓存存储成熟bean
    private final Map<Class<?>, Object> singletonObjects = new ConcurrentHashMap<>();

    //xxx: 二级缓存提前暴露的还未完全成熟的bean,用于解决循环依赖
    private final Map<Class<?>, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    //xxx: 三级缓存:对象工厂,创建Jdk代理/CgLib代理/配置类Bean/普通bean
    private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();
    private Jdbcinit jdbcinit = new Jdbcinit();
    private Properties properties = jdbcinit.initProperties();

在第三级缓存中我们保存一些lambda表达式或者工厂对象.用来生产各种不同的Bean,例如Jdk动态代理生产的Mapper代理Bean,Cglib代理父类产生的子类Bean,用户使用@bean注解注入的成熟Bean,我们使用不同的共产生产这些对象放入第三级缓存,当需要被生产的时候执行lambda表达式得到空对象.Ioc容器初始化入口这这段,先去用扫描到的原材料类填充第三级缓存,接着对Bean进行初始化

   //xxx:初始化第三缓存
   public void initIocCache(Set<Class<?>> prototypeIocContainer) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
       this.iocContainer = prototypeIocContainer;
       //xxx:填充第三级缓存
       registerBeanDefinition(this.iocContainer);
       //xxx:缓存添加好后,遍历外界传递的Set,对Bean进行初始化
       for (Class<?> clazz : this.iocContainer) {
           initBean(clazz);
       }
   }

如果这个类是配置类则检查方法上是否有@AutunmnBean注解,把他的返回值类型作为Key,生产出来的对象作为Value放入第三缓存,如果没有配置类注解则直接填充第三缓存

   //xxx:遍历set去填充第三缓存
   public void registerBeanDefinition(Set<Class<?>> beanDefinitionMap) {
       for (Class<?> beanClass : beanDefinitionMap) {
           ObjectFactory<?> beanFactory = createBeanFactory(beanClass);
           singletonFactories.put(beanClass.getName(), beanFactory);
           //xxx:查找带@AutunmnBean的字段,生成工厂放入第三缓存
           if (beanClass.getAnnotation(MyConfig.class) != null) {
               Method[] methods = beanClass.getDeclaredMethods();
               for (Method method : methods) {
                   if (method.getAnnotation(AutunmnBean.class) != null) {
                       singletonFactories.put(method.getReturnType().getName(), createBeanFactory(beanClass,method));
                   }
               }
           }
       }
   }

接着程序会判断使用哪一种工厂

    //xxx:判断使用哪种工厂
    private ObjectFactory<?> createBeanFactory(Class<?> beanClass) {
        if (beanClass.getDeclaredAnnotation(MyMapper.class) != null) {
            return () -> createMapperBeanInstance(beanClass);
        } else if (beanClass.getAnnotation(EnableAop.class) != null) {
            return () -> createAopBeanInstance(beanClass);
        }  else {
            return () -> createBeanInstance(beanClass);
        }

    }

    //xxx:判断器重载
    private ObjectFactory<?> createBeanFactory(Class<?> beanClass, Method method) {
        return () -> createAutumnBeanInstance(beanClass,method);
    }
   //xxx:Aop工厂
   private Object createAopBeanInstance(Class<?> beanClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
       String[] methods=beanClass.getAnnotation(EnableAop.class).getMethod();
       Class<?> clazz = beanClass.getAnnotation(EnableAop.class).getClassFactory();
       if(clazz==null || methods==null || methods.length==0){
           throw new IllegalArgumentException("检查Aop注解参数是否加全了");
       }
       try{
           return aopProxyFactory.create(clazz ,beanClass,methods);
       }catch (Exception e){
           throw new RuntimeException("解析注解错误,保证Aop配置类可以被实例化\n创建CgLibBean实例失败", e);
       }

   }
    //xxx:普通bean工厂
    private Object createBeanInstance(Class<?> beanClass) {
        try {
            return beanClass.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("创建普通bean实例失败,请检查你是否存在一个有参构造器,有的话创建一个无参构造器", e);
        }
    }

    //xxx:MapperBean工厂
    private Object createMapperBeanInstance(Class<?> beanClass) {
        try {
            return MapperUtils.init(beanClass);
        } catch (Exception e) {
            throw new RuntimeException("创建MapperBean实例失败", e);
        }
    }

此时第三级缓存填充完成,核心是依照类的不同切换不同的工厂,生产不同的原始对象.
下面进行依赖注入的实现,依赖注入的本质就是寻找容器中是否有这个原材料,进行getbean操作,进行一层一层的寻找并逐渐填充,主要区分为对@auto wired与@value的处理,一个是注入字段一个是注入配置文件,如果注入的是一个接口框架会自动寻找他的实现类进行注入,多个可执行的实现类出现则抛出异常,在类上可以加入条件注解指定处理器,框架会去执行match方法依照返回的布尔值进行判断是否注入

public Object getBean(Class<?> beanClass) {
        //xxx:寻找一级缓存
        Object singletonObject = singletonObjects.get(beanClass);
        //xxx:一级缓存找不到
        if (singletonObject == null) {
            //xxx:二级缓存寻找
            singletonObject = earlySingletonObjects.get(beanClass);
            //xxx:二级缓存中找不到
            if (singletonObject == null) {
                //xxx: 从三级缓存获取工厂方法
                ObjectFactory<?> singletonFactory = singletonFactories.get(beanClass.getName());
                if (singletonFactory != null) {
                    try {
                        singletonObject = singletonFactory.getObject();
                        //xxx:生产对象后从第三级移除,进入第二级缓存
                        earlySingletonObjects.put(beanClass, singletonObject);
                        singletonFactories.remove(beanClass.getName());
                    } catch (Exception e) {
                        throw new RuntimeException("创建Bean实例失败: " + beanClass.getName(), e);
                    }
                }
            }
        }

        return singletonObject;
    }
public void autowireBeanProperties(Object bean) throws IllegalAccessException, NoSuchFieldException {
        //xxx:是否为CgLib代理类?,是的话因为子类不能继承父类字段等注解,需要去父类身上查找
        Class<?> clazz = bean.getClass().getName().contains("$$")
                ? bean.getClass().getSuperclass() : bean.getClass();

        for (Field field : clazz.getDeclaredFields()) {
            //xxx:标记@auto wired进行对象/接口依赖注入
            if (field.isAnnotationPresent(MyAutoWired.class)) {
                injectDependencies(bean, field);
            }
            //xxx:进行配置文件注入
            else if (field.isAnnotationPresent(Value.class)) {
                injectValueAnnotation(bean, field);
            }
        }
        //xxx:依赖注入后更新缓存,这个bean从第二缓存移除,进入一级缓存,提前暴露期转为成熟的AutumnBean
        singletonObjects.put(bean.getClass(), bean);
        earlySingletonObjects.remove(bean.getClass());
    }

    private void injectDependencies(Object bean, Field field) throws IllegalAccessException, NoSuchFieldException {
        Class<?> fieldType = field.getType();
        //xxx:接口,还是不是mapper
        if (fieldType.isInterface() && fieldType.getAnnotation(MyMapper.class) == null) {
            //xxx:进入查找实现类环节
            injectInterfaceTypeDependency(bean, field);
        } else {
            //xxx:正常的Bean
            injectNormalDependency(bean, field);
        }
    }


    private void injectInterfaceTypeDependency(Object bean, Field field) throws IllegalAccessException, NoSuchFieldException {
        String packageName = (String) get("packageUrl");
        Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
        Set<Class<?>> subTypesOf = (Set<Class<?>>) reflections.getSubTypesOf(field.getType());
        Class<?> selectedImpl = null;
        for (Class<?> implClass : subTypesOf) {
            MyConditional myConditionalAnnotation = implClass.getAnnotation(MyConditional.class);
            if (myConditionalAnnotation != null) {
                Class<? extends Condition> conditionClass = myConditionalAnnotation.value();
                Object condition =  getBean(conditionClass);
                autowireBeanProperties(condition);
                Condition conditionAutoWired=(Condition) condition;
                conditionAutoWired.init();
                if (conditionAutoWired.matches(getInstance(), field.getType())) {
                    if (selectedImpl != null) {
                        throw new BeanCreationException("找到多个符合条件的实现:" + field.getType());
                    }
                    selectedImpl = implClass;
                }
            } else {
                if (selectedImpl == null) {
                    selectedImpl = implClass;
                }
            }
        }

        if (selectedImpl != null) {
            Object dependency = getBean(selectedImpl);
            field.setAccessible(true);
            field.set(bean, dependency);
        } else {
            throw new BeanCreationException("无法解析的依赖:" + field.getType());
        }
    }



    //xxx:注入一般Bean,依照字段查找类,从容器取出为字段赋值
    private void injectNormalDependency(Object bean, Field field) throws IllegalAccessException, NoSuchFieldException {
        Class<?> dependencyType = field.getType();
        Object dependency = getBean(dependencyType);
        if (dependency == null) {
            log.warn("无法解析的依赖:" + dependencyType.getName());
            return;
        }
        field.setAccessible(true);
        field.set(bean, dependency);
    }

    //xxx:注入配置文件
    private void injectValueAnnotation(Object instance, Field field) throws NoSuchFieldException {
        Value value = field.getAnnotation(Value.class);
        if (value == null || "".equals(value.value())) {
            log.error("没有传递内容,注入失败");
            return;
        }

        String propertyValue = properties.getProperty(value.value());
        if (propertyValue == null) {
            log.error("属性未找到,注入失败");
            return;
        }

        try {
            field.setAccessible(true);
            Object convertedValue = convertStringToType(propertyValue, field.getType());
            field.set(instance, convertedValue);
        } catch (Exception e) {
            log.error("依赖注入失败:" + e.getMessage());
        }
        }


    //xxx:配置文件编码器
    private Object convertStringToType(String value, Class<?> type) {
        if (Integer.TYPE.equals(type) || Integer.class.equals(type)) {
            return Integer.parseInt(value);
        } else if (Float.TYPE.equals(type) || Float.class.equals(type)) {
            return Float.parseFloat(value);
        } else if (String.class.equals(type)) {
            return value;
        }
        throw new IllegalArgumentException("不支持的类型: " + type);
    }

3 Jdk动态代理接口(仿制Mybaits实现)

如果执行object对象里的方法不进行代理,接着获取注解上的文本,依照传递的参数进行映射(因为方法参数后编译丢失,虽然可以加入编译参数解决但为了泛用性使用了形参注解执行名字)接着去执行这个sql得到返回集合,这时候进行判断方法返回值,如果是一个list就拿出他的泛型参数,把执行sql后的内容转化为这个list返回,不是list就返回数组的第0个

@Slf4j
@MyComponent
public class MapperUtils {
    static Jdbcinit jdbcinit = MyContext.getInstance().getBean(Jdbcinit.class);

    //xxx jdk动态代理,代理接口
    public static <T> T init(Class<T> targetClass) {
        return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class<?>[]{targetClass},
                new InvokeHandler());
    }

    static class InvokeHandler implements InvocationHandler {
        //xxx 把sql执行后的内容依照接口的类型自动注入,并返还
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //xxx :检查是否调用object方法,是的话反射执行,不进行代理
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            String sql = getSqlFromAnnotation(method);
            if (sql == null || sql.isEmpty()) {
                log.warn("sql为空");
                throw new IllegalStateException("sql为空");
            }
            sql = parseSql(sql, method, args);
            log.info(sql);
            Class<?> clazz = method.getReturnType();
            ResultSet r;
            MySelect select = method.getAnnotation(MySelect.class);
            if (select != null) {
                r = jdbcinit.querySql(sql);
            } else {
                return jdbcinit.executeUpdate(sql, clazz);
            }
            if (Collection.class.isAssignableFrom(clazz)) {
                Type returnType = method.getGenericReturnType();
                Class<?> clazzListType = null;
                if (returnType instanceof ParameterizedType) {
                    Type actualType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
                    clazzListType = Class.forName(actualType.getTypeName());
                }
                Field[] fields = clazzListType.getDeclaredFields();
                String[] fieldNames = new String[fields.length];
                for (int i = 0; i < fields.length; i++) {
                    fieldNames[i] = fields[i].getName();
                }
                log.info(Arrays.toString(fieldNames));
                return Mybaits.reflexByClass(clazzListType, r, fieldNames);
            } else {
                Field[] fields = clazz.getDeclaredFields();
                String[] fieldNames = new String[fields.length];
                for (int i = 0; i < fields.length; i++) {
                    fieldNames[i] = fields[i].getName();
                }
                List<?> result = Mybaits.reflexByClass(clazz, r, fieldNames);
                if(result.isEmpty()){
                    return null;
                }
                return result.get(0);
            }
        }
    }

    private static String parseSql(String sql, Method method, Object[] args) {
        Parameter[] parameters = method.getParameters();
        Map<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            MyParam myParam = parameters[i].getAnnotation(MyParam.class);
            if (myParam != null) {
                if (args[i].getClass()==Integer.class){
                    paramMap.put(myParam.value(), args[i]);
                }else{
                    paramMap.put(myParam.value(), "'"+args[i]+"'");
                }

            }
        }
        Pattern pattern = Pattern.compile("#\\{([^}]+)\\}");
        Matcher matcher = pattern.matcher(sql);
        StringBuilder buffer = new StringBuilder();
        while (matcher.find()) {
            String paramName = matcher.group(1);
            String paramValue = String.valueOf(paramMap.getOrDefault(paramName, ""));
            matcher.appendReplacement(buffer, paramValue);
        }
        matcher.appendTail(buffer);

        return buffer.toString();
    }

    private static String getSqlFromAnnotation(Method method) {
        MySelect select = method.getAnnotation(MySelect.class);
        if (select != null) {
            return select.value();
        }

        MyInsert insert = method.getAnnotation(MyInsert.class);
        if (insert != null) {
            return insert.value();
        }

        MyUpdate update = method.getAnnotation(MyUpdate.class);
        if (update != null) {
            return update.value();
        }

        MyDelete delete = method.getAnnotation(MyDelete.class);
        if (delete != null) {
            return delete.value();
        }
        return null;
    }
}
@MyComponent
public class Mybaits {
    public static <T> List<T> reflexByClass(Class<T> clazz, ResultSet resultSet, String[] fieldList) throws SQLException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, InstantiationException {
        List<T> collectList = new ArrayList<>();
        while (resultSet.next()) {
            T targetObject = clazz.getDeclaredConstructor().newInstance();
            for (String fieldName : fieldList) {
                Object fieldValue=null;
                Field field = clazz.getDeclaredField(fieldName);
                Class<?> fieldType = field.getType();
                if (fieldType.equals(String.class)) {
                    fieldValue = resultSet.getString(fieldName);
                } else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
                    fieldValue = resultSet.getInt(fieldName);
                } else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) {
                    fieldValue = resultSet.getDouble(fieldName);
                }
                field.setAccessible(true);
                field.set(targetObject, fieldValue);
            }
            collectList.add(targetObject);
        }
        return collectList;
    }
}

@Slf4j
@MyComponent
public class Jdbcinit {
    
    public  ResultSet querySql(String sql) throws SQLException {
        Properties properties=initProperties();
        Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("user"), properties.getProperty("password"));
        Statement statement = connection.createStatement();
        return statement.executeQuery(sql);
    }
    public <T> T executeUpdate(String sql, Class<T> clazz) throws SQLException {
        Properties properties = initProperties();
        try (Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("user"), properties.getProperty("password"));
             Statement statement = connection.createStatement()) {
            int affectedRows=0;
            try{
                affectedRows = statement.executeUpdate(sql);
            }catch (SQLIntegrityConstraintViolationException e){
                log.info("SQL执行失败,检查主键约束",e);
            }
            if (clazz.equals(Integer.class) || clazz.equals(int.class)) {
                return clazz.cast(affectedRows);
            } else if (clazz.equals(Boolean.class) || clazz.equals(boolean.class)) {
                return clazz.cast(affectedRows > 0);
            } else {
                throw new IllegalArgumentException("错误的返回值类型,只接受int/Integer/Boolean/boolean");
            }
        } catch (Exception e) {
            throw new SQLException("更新出错", e);
        }
    }


    public   Properties initProperties(){
        Properties properties = new Properties();
        try {
            FileInputStream fileInputStream = new FileInputStream("src/main/resources/test.properties");
            properties.load(fileInputStream);
            fileInputStream.close();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return null;
        }
        return properties;
    }

}

4:Web服务实现:

4.1:简易的使用Socket开启网络服务

    private final ThreadWatcher threadWatcher = new ThreadWatcher();
    private final ThreadServer threadServer = new ThreadServer();
    private ExecutorService threadPool;
    private ServerSocket serverSocket;
    private final MyContext myContext = MyContext.getInstance();
    private final SessionManager sessionmanager=myContext.getBean(SessionManager.class);
    @MyAutoWired
    HtmlResponse htmlResponse;
    @MyAutoWired
    AnnotationScanner annotationScanner;
    @MyAutoWired
    JsonFormatter jsonFormatter;
    @Value("port")
    private Integer port;
    @Value("threadPoolNums")
    private Integer threadNums;


    public void init() throws Exception {
        threadServer.registerObserver(threadWatcher);
        threadPool = Executors.newFixedThreadPool(threadNums);
        Map<String, String> sharedMap = (Map<String, String>) myContext.get("urlmapping");
        try {
            serverSocket = new ServerSocket(port);
            log.info("服务于" + port + "端口启动");
            log.info("http://localhost:" + port + "/");
            while (!serverSocket.isClosed()) {
                final Socket clientSocket = serverSocket.accept();
                InputStream is = clientSocket.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader reader = new BufferedReader(isr);
                StringBuilder headerBuilder = new StringBuilder();
                String line;
                String contentType="";
                String boundary="";
                int contentLength = 2048;
                while ((line = reader.readLine()) != null && !line.isEmpty()) {
                    headerBuilder.append(line).append("\n");
                    if (line.startsWith("GET")) {
                       break;
                    }
                    if (line.startsWith("Content-Length: ")) {
                        contentLength = Integer.parseInt(line.substring("Content-Length: ".length()).trim());
                    }
                    if(line.startsWith("Content-Type: ")){
                        String[] parts = line.split(";");
                        contentType = parts[0].trim();
                        for (String part : parts) {
                            if (part.trim().startsWith("boundary=")) {
                                boundary = part.trim().substring("boundary=".length());
                                break;
                            }
                        }

                    }
                }
                if (contentLength == -1) {
                    throw new RuntimeException("POST方法你不带长度?");
                }
                char[] bodyChars = new char[contentLength];
                int charsRead = reader.read(bodyChars);
                String requestBody = new String(bodyChars, 0, charsRead);
                int finalContentLength = contentLength;
                String finalContentType = contentType;
                String finalBoundary = boundary;
                threadPool.execute(() -> {
                    try {
                        processRequest(clientSocket, sharedMap, String.valueOf(headerBuilder),requestBody, finalContentLength, finalContentType, finalBoundary);
                    } catch (IOException e) {
                        exceptionPrinter(e, "IO异常");
                    } catch (NoAvailableUrlMappingException e) {
                        exceptionPrinter(e, "没有可用的映射表,请在控制器加入需要映射的url-method");
                    } catch (Exception e) {
                        exceptionPrinter(e, "未知异常");
                    }
                });
            }


        } catch (BindException e) {
            log.error("端口"+port+"被占用");
            log.error(e.toString());
        }catch (Exception e){
            serverSocket.close();
            log.error(e.toString());
        }
    }

4.2 url-method映射器,自动注入Controller方法参数

public Object invokeMethod(String classurl, String methodName,Request request,Response response) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> clazz = Class.forName(classurl);
        Object instance = myContext.getBean(clazz);
        Method domethod=null;
        List<Object> objectList = new ArrayList<>();
        if (classurl.contains("$$")) {
            clazz = clazz.getSuperclass();
        }
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                domethod = method;
                Parameter[] parameters = method.getParameters();
                for (Parameter parameter : parameters) {
                    MyRequestParam myRequestParam = parameter.getAnnotation(MyRequestParam.class);
                    if (parameter.getType().equals(Request.class)) {
                        objectList.add(request);
                    }
                    if (parameter.getType().equals(MyMultipartFile.class)) {
                        objectList.add(request.getMyMultipartFile());
                    }
                    if(parameter.getType().equals(Response.class)){
                        objectList.add(response);
                    }
                    if (myRequestParam != null) {
                        if (!myRequestParam.value().isEmpty()) {
                            objectList.add(useUrlGetParam(myRequestParam.value(), request));
                        }
                    }
                }
            }
        }
        return domethod.invoke(instance, objectList.toArray());
    }
    public void processRequest(Socket clientSocket, Map sharedMap,String payload,String body,Integer lenth,String contentType,String boundary) throws IOException {
            boolean urlmark = true;
            Request request = new Request(payload,body,lenth);
            if("multipart/form-data".equals(contentType)){
                request.setContentType("multipart/form-data");
                request.setBoundary(boundary);
            }
            String baseurl = request.getUrl();
            if (sharedMap != null) {
                String str = extractPath(baseurl);
                if (sharedMap.containsKey(str)) {
                    request.setParameters(baseurl);
                    urlmark = false;
                    str = (String) sharedMap.get(str);
                    int lastIndex = str.lastIndexOf(".");
                    String classurl = str.substring(0, lastIndex);
                    Filter filter = (Filter) myContext.getBean(annotationScanner.initFilterChain());
                    if (filter.doChain(request)) {
                        htmlResponse.redirectLocationWriter(clientSocket, "https://www.baidu.com/");
                    } else {
                        String methodName = str.substring(lastIndex + 1);
                        try {
                            Object result = invokeMethod(classurl, methodName, request,new Response(htmlResponse,clientSocket));
                            if (result != null) {
                                handleSocketOutputByType(result.getClass(), clientSocket, result,request);
                            } else {
//                                htmlResponse.outPutMessageWriter(clientSocket, 200, "返回值为空");
                            }

                        } catch (InvocationTargetException | ClassNotFoundException | NoSuchMethodException |
                                 IllegalAccessException e) {
                            Throwable cause = e.getCause();
                            log.warn("异常来自" + methodName);
                            cause.printStackTrace(System.err);
                            htmlResponse.outPutMessageWriter(clientSocket, 500, e.getCause().toString(), null);
                        }
                    }
                }
            } else {
                throw new NoAvailableUrlMappingException("空的映射表,请在controller上添加url映射");
            }
            if (urlmark) {
                log.warn(baseurl);
                log.warn("404");
                htmlResponse.redirectLocationWriter(clientSocket, "/404");
            }

        }

4.3:返回值判断器

//xxx:依照方法的返回值来确定选择哪种返回器
    public void handleSocketOutputByType(Class<?> classType, Socket clientSocket, Object result, Request request) throws IOException, IllegalAccessException {
        Cookie cookie = request.getCookieByName("userSession");
        if(cookie!=null){
            cookie=null;
        }else{
            String uuid=String.valueOf(UUID.randomUUID());
            cookie=new Cookie("userSession",uuid);
            MySession newSession = new MySession(uuid);
            sessionmanager.getSessions().put(uuid, newSession);

        }
        if (classType == View.class) {
            htmlResponse.outPutHtmlWriter(clientSocket, ((View) result).getHtmlName(), cookie);
        } else if (classType == Icon.class) {
            htmlResponse.outPutIconWriter(clientSocket, ((Icon) result).getIconName(), cookie);
        } else if (Map.class.isAssignableFrom(classType)) {
            htmlResponse.outPutMessageWriter(clientSocket, 200, jsonFormatter.toJson(result), cookie);
        } else if (classType.isPrimitive() ||
                classType.equals(String.class) ||
                classType.equals(Boolean.class) ||
                classType.equals(Integer.class) ||
                classType.equals(Character.class) ||
                classType.equals(Byte.class) ||
                classType.equals(Short.class) ||
                classType.equals(Double.class) ||
                classType.equals(Long.class) ||
                classType.equals(Float.class)) {
            htmlResponse.outPutMessageWriter(clientSocket, 200, result.toString(), cookie);
        } else {
            htmlResponse.outPutMessageWriter(clientSocket, 200, jsonFormatter.toJson(result), cookie);
        }
    }

4.4:Http返回报文生成

@MyComponent
public class HtmlResponse {
    @MyAutoWired
    ResourceFinder resourceFinder;
    @MyAutoWired
    CrossOriginBean crossOriginBean;




    //xxx:http返回报文(直接返回拼接的html文本,Content-Type: text/html)
    public void outPutMessageWriter(Socket socket, int statusCode, String responseText,Cookie cookie) throws IOException {
        String  CrossOrigin=crossOriginBean.getOrigins();
        String responseTextWithHtml = "<html><body>" + "<h3 style='color:red'>" + responseText + "</h3>" + "</body></html>";
        byte[] responseBytes = responseTextWithHtml.getBytes(StandardCharsets.UTF_8);

        StringBuilder responseHeader = new StringBuilder();
        responseHeader.append("HTTP/1.1 ").append(statusCode).append(" OK\r\n");
        responseHeader.append("Server: liTangDingZhen\r\n");
        responseHeader.append("Content-Type: text/html;charset=UTF-8\r\n");
        responseHeader.append("Content-Length: ").append(responseBytes.length).append("\r\n");
        responseHeader.append("Connection: close\r\n");
        responseHeader.append("Access-Control-Allow-Origin: ").append(CrossOrigin).append("\r\n");
        if (cookie != null) {
            responseHeader.append("Set-Cookie: ")
                    .append(cookie.getCookieName()).append("=")
                    .append(cookie.getCookieValue()).append(";");
            if (cookie.getPath() != null) {
                responseHeader.append(" Path=").append(cookie.getPath()).append(";");
            }

            if (cookie.getMaxAge() > 0) {
                responseHeader.append(" Max-Age=").append(cookie.getMaxAge()).append(";");
            }
            responseHeader.append("\r\n");
        }
        responseHeader.append("\r\n");
        try (OutputStream out = socket.getOutputStream()) {
            out.write(responseHeader.toString().getBytes(StandardCharsets.UTF_8));
            out.write(responseBytes);
        }
    }

    //xxx:302重定向
    public void redirectLocationWriter(Socket socket, String location) throws IOException {
        String responseBody = "<html><body><h1>页面重定向/拦截器拦截</h1></body></html>";
        byte[] responseBodyBytes = responseBody.getBytes(StandardCharsets.UTF_8);
        StringBuilder responseHeader = new StringBuilder();
        responseHeader.append("HTTP/1.1 302 Found\r\n");
        responseHeader.append("Location: ").append(location).append("\r\n");
        responseHeader.append("Content-Type: text/html; charset=UTF-8\r\n");
        responseHeader.append("Content-Length: ").append(responseBodyBytes.length).append("\r\n");
        responseHeader.append("Connection: close\r\n");
        responseHeader.append("\r\n");
        try (OutputStream out = socket.getOutputStream()) {
            out.write(responseHeader.toString().getBytes(StandardCharsets.UTF_8));
            out.write(responseBodyBytes);
        }
    }

    //xxx:http返回报文(返回找到的html文件,Content-Type: text/html)
    public void outPutHtmlWriter(Socket socket, String htmlUrl, Cookie cookie) throws IOException {
        String CrossOrigin = crossOriginBean.getOrigins();
        String filePath = resourceFinder.getHtmlLocation(htmlUrl).replaceFirst("^/", "");
        Path path = Path.of(filePath);
        byte[] responseBytes = Files.readAllBytes(path);
        StringBuilder responseHeader = new StringBuilder();
        responseHeader.append("HTTP/1.1 ").append(200).append(" OK\r\n");
        responseHeader.append("Server: liTangDingZhen\r\n");
        responseHeader.append("Content-Type: text/html;charset=UTF-8\r\n");
        responseHeader.append("Content-Length: ").append(responseBytes.length).append("\r\n");
        responseHeader.append("Connection: close\r\n");
        responseHeader.append("Access-Control-Allow-Origin: ").append(CrossOrigin).append("\r\n");
        if (cookie != null) {
            responseHeader.append("Set-Cookie: ")
                    .append(cookie.getCookieName()).append("=")
                    .append(cookie.getCookieValue()).append(";");
            if (cookie.getPath() != null) {
                responseHeader.append(" Path=").append(cookie.getPath()).append(";");
            }

            if (cookie.getMaxAge() > 0) {
                responseHeader.append(" Max-Age=").append(cookie.getMaxAge()).append(";");
            }
            responseHeader.append("\r\n");
        }

        responseHeader.append("\r\n");
        OutputStream out = socket.getOutputStream();
        out.write(responseHeader.toString().getBytes(StandardCharsets.UTF_8));
        out.write(responseBytes);
    }


    //xxx:http返回报文(返回找到的iocn,Content-Type: image/x-icon)
    public void outPutIconWriter(Socket socket, String htmlUrl,Cookie cookie) throws IOException {
        String filePath = resourceFinder.getIconLocation(htmlUrl).replaceFirst("^/", "");
        Path path = Path.of(filePath);
        byte[] responseBytes = Files.readAllBytes(path);
        StringBuilder responseHeader = new StringBuilder();
        responseHeader.append("HTTP/1.1 ").append(200).append(" OK\r\n");
        responseHeader.append("Server: liTangDingZhen\r\n");
        responseHeader.append("Content-Type: image/x-icon\r\n");
        responseHeader.append("Content-Length: ").append(responseBytes.length).append("\r\n");
        responseHeader.append("Connection: close\r\n");
        responseHeader.append("Access-Control-Allow-Origin: *\r\n");
        if (cookie != null) {
            responseHeader.append("Set-Cookie: ")
                    .append(cookie.getCookieName()).append("=")
                    .append(cookie.getCookieValue()).append(";");
            if (cookie.getPath() != null) {
                responseHeader.append(" Path=").append(cookie.getPath()).append(";");
            }

            if (cookie.getMaxAge() > 0) {
                responseHeader.append(" Max-Age=").append(cookie.getMaxAge()).append(";");
            }
            responseHeader.append("\r\n");
        }
        responseHeader.append("\r\n");
        try (OutputStream out = socket.getOutputStream()) {
            out.write(responseHeader.toString().getBytes(StandardCharsets.UTF_8));
            out.write(responseBytes);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值