从0开始手写一个可用的SpringBoot
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);
}
}