手写spring基础版本,一窥探spring如何运行。
搭建spring环境,web.xml配置servlet,书写application.xml信息,需要依赖反转的类加上注解,控制层增加@controller注解。
控制层,业务层等在使用对象时候都只是声明了,并未new出来一个对象,做码农都知道需要对象要new出来,这个时候jvm才分配堆内存的。在使用过程居然不报NullPointerException这个异常,那就说明哪个功能模块已经替咱们做了这部分的工作。答案是spring。
书写springv1版本
1.DispatcherServlet -->书写自己的Servlet 继承自HttpServlet
2.web.xml 配置手写servlet信息
3.配置信息文件:基础包名下需要被扫描到
4.书写注解类:@SongService、@SongController、@SongRequestMapping、@SongRequestParam、@SongAutowired;至于注解上的Target等信息沿用spring上的就可以(此处只为把代码码出来)。
下面来看下项目整体
1.项目结构
使用maven,引入servlet的jar包
书写自定义servlet类
1.继承HttpServlet,实现其中init(),doPost(),doGet()三个方法。
2.init()工作
3.想想如何把这几者分部工作内容串起来呢?
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件信息 - 确定扫描基类
doLoadConfig(config.getInitParameter("contextConfigLocation").replace("classpath:",""));
//2.扫描相关的类
doScanner(contextConfig.getProperty("scanPackage"));
//3.初始化扫描到相关的类,并且将他们放入到IOC容器之中
doInstance();
//4.完成依赖注入
doAutowired();
//5.初始化HandlerMapping
initHanlderMapping();
System.err.println("init 完成!!!");
}
准备工作:
- 加载配置文件到内存中;
- 配置文件读取哪些基类需要被扫描 (简版:配置文件只配置哪些基类被扫描到),加载符合条件到集合;
- 基类扫描集合再次通过是否添加了注解条件来过滤存放在某个容器 - 是叫ioc容器吗?
1.遍历扫描类的集合,是否添加了自定义注解,是通过反射实例化出来添加ioc容器中;
2.需要注意注解自定义beanName的别称逻辑; - ioc容器对象(key-value)是实例化,但里面对应字段还没做到与对象之间的对应关系,因此需要去做字段的实例化,不然调用过程中就会出现空指针错误;
- url - method,如何做到映射关联?
1.遍历ioc容器,找到是控制层的类,找到url定义的注解,基础的url就拿到,然后遍历该类method下定义的url,组装起来,如果出现重复的url,抛出异常;
2.添加url-method关系到集合中;
public class SongDispatcherServlet extends HttpServlet {
//保存配置文件内容
private Properties contextConfig = new Properties();
//保存所有扫描到的类名
private List classNames = new ArrayList();
//传说中的ioc容器,为此来揭开它神秘的面纱
private Map ioc = new HashMap();
//保存url 很Method的对应关系
private Map handlerMapping = new HashMap();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//真正执行-
try {
doDispath(req,resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Excetion! Detail :"+Arrays.toString(e.getStackTrace()));
}
}
/**
* 通过req加载url - method
* @param req
* @param resp
*/
private void doDispath(HttpServletRequest req, HttpServletResponse resp) throws Exception{
//绝对路径
String url = req.getRequestURI();
//处理成相对路径
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath,"").replaceAll("/+","/");
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 not found!!!");
return;
}
Method method = this.handlerMapping.get(url);
//通过反射拿到信息
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//为了方便这块写死,后期需要优化
Map paramMap = req.getParameterMap();
method.invoke(ioc.get(beanName),new Object[]{req,resp,paramMap.get("name")[0]});
}
@Override
public void init(ServletConfig config) throws ServletException {
//1.加载配置文件信息 - 确定扫描基类
doLoadConfig(config.getInitParameter("contextConfigLocation").replace("classpath:",""));
//2.扫描相关的类
doScanner(contextConfig.getProperty("scanPackage"));
//3.初始化扫描到相关的类,并且将他们放入到IOC容器之中
doInstance();
//4.完成依赖注入
doAutowired();
//5.初始化HandlerMapping
initHanlderMapping();
System.err.println("init 完成!!!");
}
private void initHanlderMapping() {
if(ioc.isEmpty()){return;}
for (Map.Entry entry :
ioc.entrySet()) {
Class> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(SongController.class)){continue;}
String baseURL= "";
if(clazz.isAnnotationPresent(SongRequestMapping.class)){
SongRequestMapping requestMapping = clazz.getAnnotation(SongRequestMapping.class);
baseURL = requestMapping.value();
}
//默认获取所有public方法
for (Method method :
clazz.getMethods()) {
if(!method.isAnnotationPresent(SongRequestMapping.class)){
continue;
}
SongRequestMapping requestMapping = method.getAnnotation(SongRequestMapping.class);
String url = ("/" + baseURL + "/" + requestMapping.value()).replaceAll("/+","/");
handlerMapping.put(url,method);
System.err.println("Mapped:"+url+","+method);
}
}
}
private void doAutowired() {
if(ioc.isEmpty()){return;}
for (Map.Entry entry : ioc.entrySet()){
//Declared 所有的,特定的 字段,包括private/protected/default
//正常来说,普通的oop编程只能拿到public的属性
Field [] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field :
fields) {
if(!field.isAnnotationPresent(SongAutowired.class)){
continue;
}
SongAutowired songAutowired = field.getAnnotation(SongAutowired.class);
String beanName = songAutowired.value().trim() ;
if("".equals(beanName)){
beanName = field.getType().getName();
}
//private 在accessible=true 才能被操作
field.setAccessible(true);
try{
//用反射机制,动态给字段赋值 -- 实例化
field.set(entry.getValue(),ioc.get(beanName));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
private void doInstance() {
//初始化,为DI做准备
if(classNames.isEmpty()){return;}
try {
for (String className : classNames) {
Class> clazz = Class.forName(className);
//什么样的类才需要初始化
//加了注解的类,才能被初始化,如何判断?
//1.默认类名首字母小写
String beanName = toLowerFirstCase(clazz.getSimpleName());
if(clazz.isAnnotationPresent(SongController.class)){
Object instance = clazz.newInstance();
//Spring默认类名首字母小写
ioc.put(beanName,instance);
}else if(clazz.isAnnotationPresent(SongService.class)){
Object instance = clazz.newInstance();
//2.自定义的beanName
SongService service = clazz.getAnnotation(SongService.class);
if(!"".equals(service.getClass())){
beanName = service.value();
}
//3.根据类型自动赋值
for (Class> i : clazz.getInterfaces()) {
if(ioc.containsKey(i.getName())){
throw new Exception("The " + i.getName() + "is exists !!!");
}
ioc.put(i.getName(),instance);
}
}else{
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//如果类名本身是小写就会有问题
//这个方法本身就是大写转小写。只关注方法本身
private String toLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
//大小写字母的ASCII码相差32
chars[0] += 32;
return String.valueOf(chars);
}
private void doScanner(String scanPackage) {
//scanPackage = com.song.springv1
//转换为文件路径,实际上就是把.替换成/就OK
//classpath
URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
File classPath = new File(url.getFile());
for (File file : classPath.listFiles()) {
if(file.isDirectory()){
doScanner(scanPackage + "." + file.getName());
}else {
if (!file.getName().endsWith(".class")) {
continue;
}
String className = (scanPackage + "." + file.getName().replace(".class", ""));
classNames.add(className);
}
}
}
/**
* 加载配置文件信息
*/
private void doLoadConfig(String contextConfigLocation) {
//web.xml读取配置文件信息
InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try{
//并且将其读取出来的放到Properties
//把配置文件内容读出来到内存中
contextConfig.load(fis);
}catch (Exception ex){
ex.printStackTrace();
}finally {
if(null!=fis){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
准备画个图,懒得画。有空并记得就补上。
源码地址:https://github.com/spp1987/dreamProject.git