AP计算机科学A(APcomputer science A)复习备考攻略视频教程
42759 人在学
写在前面
最近学习了一下spring的相关内容,所以也就想要照猫画虎地记录和实现一下spring的框架,通过阅读这些也希望能够消除对Spring框架的恐惧,其实细心阅读框架也很容易理解。
mini-spring尽量实现spring的核心功能。文章写得很仓促,可能不是很全面,在全部完成之后我们再来完善表达吧,见谅~
手写Spring之 实现Bean管理(IOC与DI)
写在前面
一、bean管理
1.1 什么是bean?
1.2 bean优势
二、依赖注入和控制反转
2.1 控制反转
2.2 依赖注入
三、实现
3.1 包结构的改变
3.2 具体实现
framework模块
test模块
3.3 测试
四、小结
五、框架总结
项目的源码我放在了github上:源码地址
我会在这里整理文章的系列目录:
从头开始实现一个小型spring框架——手写Spring之实现SpringBoot启动
从头开始实现一个小型spring框架——手写Spring之集成tomcat服务器
从头开始实现一个小型spring框架——控制器controller的实现
从头开始实现一个小型spring框架——实现Bean管理(IOC与DI)
一、bean管理
1.1 什么是bean?
bean是spring抽象出的概念
区别于普通的对象,bean的声明周期一般较长。
在整个虚拟机中都是可见的
普通的对象仅在当前类可见,在其他类中是引用不到的。因此对bean的维护成本要高于对普通对象的维护。
bean一般单例存在
正因为对bean的维护成本很高,因此bean单例存在。
1.2 bean优势
运行期效率高
bean在项目启动时初始化,使用时直接获取,提高了获取对象的性能;也减少了代码在维护上成本;减少了对象使用过后被抛弃的情况,降低虚拟机在gc时的压力。
统一维护,便于管理和拓展
不必处理链式的依赖
单例存在
二、依赖注入和控制反转
很多人认为控制反转和依赖注入是相同的,其实这是一个误区。
2.1 控制反转
IOC(Inversion of Control):思想
控制反转是一种设计的原则和思想,用来降低代码之间的耦合度。
2.2 依赖注入
DI(Dependency Injection):方式
依赖注入是实现IOC的一种方式。
采用依赖注入的方式,类A中只需要定义一个B的属性,不需要通过new来获取对象,二是通过bean容器将对象new出来,并注入到A对象的引用里。
控制正转方式:
控制反转创建的方式:
三、实现
因为我们已经在上一篇文章中实现了包扫描,所以可以非常方便获取到类的信息。
获取到类的信息后,我们就要根据是否存在需要注入的依赖来实例化 Bean对象了,我们来看具体的实现:
3.1 包结构的改变
直接看下图
更改后的内容添加了对依赖注入的实现,具体有
framework模块:
beans包下增加
Autowired自定义注解:用于标记需要进行注入的域
Bean自定义注解:标记类为需要实例化的Bean
test模块
service包增加
UserService :实现到controller的注入
3.2 具体实现
framework模块
Autowired标记注解
package com.qcby.beans;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
Bean注解
package com.qcby.beans;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
}
BeanFactory工厂
这是本次实现的核心代码,将Bean通过map的形式进行保存,提供getBean,initBean方法对Bean容器内的实例进行获取和初始化Bean容器
package com.qcby.beans;
import com.qcby.web.mvc.Controller;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author kevinlyz
* @ClassName BeanFactory
* @Description bean工厂
* @Date 2019-06-09 11:09
**/
public class BeanFactory {
//ConcurrentHashMap保存bean
public static Map,Object> classToBean = new ConcurrentHashMap<>();
//获取bean
public static Object getBean(Class cls){
return classToBean.get(cls);
}
//初始化
public static void initBean(List> classList) throws Exception {
List> toCreate = new ArrayList<>(classList);
//循环实例化bean,判断是否存在循环依赖
while (toCreate.size() != 0){
int remainSize = toCreate.size();
for (int i=0;i
if (finishCreate(toCreate.get(i))){
toCreate.remove(i);
}
}
if (toCreate.size() == remainSize){ //陷入死循环,抛出异常
throw new Exception("循环依赖异常");
}
}
}
//创建bean
private static boolean finishCreate(Class cls) throws IllegalAccessException, InstantiationException {
//不是bean直接返回true并删除
if (!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
return true;
}
//创建对象
Object bean = cls.newInstance();
//解决存在的依赖;获取域并判断是否被Autowired注解
for (Field field : cls.getDeclaredFields()){
if (field.isAnnotationPresent(Autowired.class)){ //存在Autowired注解
Class fieldType = field.getType();
Object reliantBean = BeanFactory.getBean(fieldType); //从Bean工厂内获取被依赖的bean
if (reliantBean == null){
return false;
}
field.setAccessible(true);
field.set(bean,reliantBean); //为bean设置依赖域
}
}
classToBean.put(cls,bean);
return true;
修改MiniApplication,对Bean工厂进行初始化
BeanFactory.initBean(classList);
完整代码为
package com.qcby.starter;
import com.qcby.beans.BeanFactory;
import com.qcby.core.ClassScanner;
import com.qcby.web.handler.HandlerManagger;
import com.qcby.web.server.TomcatServer;
import java.util.List;
/**
* @author kevinlyz
* @ClassName MiniApplication
* @Description 框架的入口类
* @Date 2019-06-04 19:21
**/
public class MiniApplication {
public static void run(Class cls,String[] args){
System.out.println("Hello mini-spring application!");
TomcatServer tomcatServer = new TomcatServer(args);
try {
tomcatServer.startServer();
List> classList = ClassScanner.scannClasses(cls.getPackage().getName());
BeanFactory.initBean(classList);
classList.forEach(it->System.out.println(it.getName()));
HandlerManagger.resolveMappingHandler(classList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改MappingHandler获取Bean的方式为从工厂中获取
Object ctl = BeanFactory.getBean(controller);
完整代码为
package com.qcby.web.handler;
import com.qcby.beans.Bean;
import com.qcby.beans.BeanFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author kevinlyz
* @ClassName MappingHandler
* @Description 包含uri,类的方法信息,类信息和参数
* @Date 2019-06-08 18:32
**/
public class MappingHandler {
private String uri;
private Method method;
private Class controller;
private String[] args;
public MappingHandler(String uri, Method method, Class controller, String[] args) {
this.uri = uri;
this.method = method;
this.controller = controller;
this.args = args;
}
public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
String reqUri = ((HttpServletRequest)req).getRequestURI();
if (!this.uri.equals(reqUri))
return false;
//相等则调用Handler的resolveMappingHandler方法,实例化并返回
Object[] parameters = new Object[args.length];
for(int i=0;i
parameters[i] = req.getParameter(args[i]);
}
Object ctl = BeanFactory.getBean(controller);
Object response = method.invoke(ctl,parameters);
res.getWriter().println(response.toString());
return true;
}
}
test模块
添加UserService
package com.qcby.service;
import com.qcby.beans.Bean;
/**
* @author kevinlyz
* @ClassName UserService
* @Description TODO
* @Date 2019-06-09 13:37
**/
@Bean
public class UserService {
public String getInfo(String name){
return name+" is handsome!!";
}
}
修改UserController
package com.qcby.controller;
import com.qcby.beans.Autowired;
import com.qcby.service.UserService;
import com.qcby.web.mvc.Controller;
import com.qcby.web.mvc.RequestMapping;
import com.qcby.web.mvc.RequestParam;
/**
* @author kevinlyz
* @ClassName UserController
* @Description TODO
* @Date 2019-06-08 17:14
**/
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/test")
public String test(@RequestParam("name") String name,@RequestParam("desc") String desc){
System.out.println("test访问了!");
System.out.println("name>>>>>>>"+name);
return userService.getInfo(name);
}
}
3.3 测试
再熟悉不过的流程了
构建:gradle build
运行:java -jar test/build/libs/test-1.0-SNAPSHOT.jar
然后在浏览器输入需要传递的参数,这里仅传输了一个那么用于测试,看到如下结果:
完事!!!
到此,我们的依赖注入也成功集成到了框架之中!
测试中增加了UserService并用@Bean标记,并在UserController中注入,成功完成测试.
第二篇对Web请求的流程进行了介绍和分析,分析了请求到Web服务器的传输过程。在此基础上,我们对项目中的包结构进行了扫描,并实现拦截了TestServlet中的请求,最终将Tomcat容器集成在了mini-spring框架之中。
第三篇是对控制器的核心实现。我们对容器内部的处理流程做了进一步的分析,分析其中存在的问题和Spring对请求方式处理上的改进。根据Spring的实现方式,我们对mini-spring框架进行了实现,ClassScanner获取项目的包结构,使用DispatcherServlet对/下的请求进行了拦截,基于Controller、RequestMapping、RequestParam三个注解实现信息的扫描和配置。
第四篇在第三篇包扫描的基础上进一步实现依赖注入,首先对控制反转和依赖注入的区别进行了分析,进而在BeanFactory中解决了容器中存在的循环依赖的问题,最终完成我们的mini-spring框架!
项目中还可以进一步完善,如Controller的参数注解的改进,依赖注入的性能,还可以结合数据持久层的框架,作进一步完善!
项目中首先对架构进行了介绍,将项目分为了framewok模块和test模块两个,分别负责框架的实现和测试,在简单的介绍了gradle的相关知识后,实现了SpringBoot的启动方式。
五、框架总结
到这里,我们的小型Spring框架的开发就彻底的结束了。其实项目中还存在着许多的缺陷和不足,在细节方面没有很细的雕琢,如果有大佬进行了改进和实现,欢迎在github里面提交代码呀。
小结
在这一篇我们使用反射的方式对依赖注入进行了实现,增加了Bean注解用于标记一个类是否是需要管理的Bean,Autowired注解用于注解在何处注入实例化后的Bean。以及核心的实现类BeanFactory工厂,对外提供了获取bean的getBean方法和初始化工厂的initBean的方法。同时修改MappingHandler获取Bean的方式为从工厂中获取。