[适合初中级Java程序员修炼手册从0搭建整个Web项目](五)(上)

简介: 前言“文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206种一棵树最好的时间是十年前,其次是现在

six-finger-web


一个Web后端框架的轮子从处理Http请求【基于Netty的请求级Web服务器】 到mvc【接口封装转发)】,再到ioc【依赖注入】,aop【切面】,再到 rpc【远程过程调用】最后到orm【数据库操作】全部自己撸一个(简易)的轮子。

github


为啥要写这个轮子

其实是这样的,小六六自己平时呢?有时候喜欢看看人家的源码比如Spring,但是小六六的水平可能不怎么样,每次看都看得晕头转向,然后就感觉里面的细节太难了,然后我就只能观其总体的思想,然后我就想我如果可以根据各位前辈的一些思考,自己撸一个简单的轮子出来,那我后面去理解作者的思想是不是简单点呢?于是呢 six-finger-web就面世了,它其实就是我的一个学习过程,然后我把它开源出来,希望能帮助那些对于学习源码有困难的同学。还有就是可以锻炼一下自己的编码能力,因为平时我们总是crud用的Java api都是那些,久而久之,很多框架类的api我们根本就不熟练了,所以借此机会,锻炼一下。


特点

  • 内置由 Netty 编写 HTTP 服务器,无需额外依赖 Tomcat 之类的 web 服务(刚好小六六把Netty系列写完,顺便用下)
  • 代码简单易懂(小六六自己写不出框架大佬那种高类聚,低耦合的代码),能力稍微强一点看代码就能懂,弱点的也没关系,小六六有配套的从0搭建教程。
  • 支持MVC相关的注解确保和SpringMVC的用法类似
  • 支持Spring IOC 和Aop相关功能
  • 支持类似于Mybatis相关功能
  • 支持类似于Dubbo的rpc相关功能
  • 对于数据返回,只支持Json格式


絮叨

前面是已经写好的章节,下面我给大家来一一走一遍搭建流程

我来总结一下前面我们已经完成的,我们已经完成了基于Netty的Http服务器,和springmvc spring ioc相关的功能,今天呢?我们尝试着来写写spring的aop


总的结构

网络异常,图片无法展示
|


上面就是写完之后的结构,我们下面来一一查看我们的过程

网络异常,图片无法展示
|


其实这边小六六遇到了一些问题,但是没能解决,就先这样了吧。


JoinPoint


通知方法的其中一个入参就是JoinPoint,通过它我们可以拿到当前被代理的对象、方法、参数等,甚至可以设置一个参数用于上下文传递,从接口方法就能判断出来了。

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;
import java.lang.reflect.Method;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 16:26
 *通知方法的其中一个入参就是<kbd>JoinPoint</kbd>,通过它我们可以拿到当前被代理的对象、方法、参数等,甚至可以设置一个参数用于上下文传递,从接口方法就能判断出来了。
 */
public interface JoinPoint {
    Object getThis();
    Object[] getArguments();
    Method getMethod();
    void setUserAttribute(String key, Object value);
    Object getUserAttribute(String key);
}
复制代码


它的实现类就是前言中提到的外层拦截器对象,负责执行整个拦截器链,主要逻辑是:先遍历执行完拦截器链,最后才执行被代理的方法。这其实就是责任链模式的实现。

package com.xiaoliuliu.six.finger.web.spring.aop.intercept;
import com.xiaoliuliu.six.finger.web.spring.aop.aspect.JoinPoint;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:10
 */
public class MethodInvocation implements JoinPoint {
    /**
     *  @author: linliangkun
     *  @Date: 2020/10/26 17:12
     *  @Description: 被代理对象
     */
    private Object proxy;
    /**被代理的对象*/
    private Object target;
    /**被代理对象的class*/
    private Class<?> targetClass;
    /**被代理的方法*/
    private Method method;
    /**被代理的方法的入参*/
    private Object [] arguments;
    /**拦截器链*/
    private List<Object> interceptorsAndDynamicMethodMatchers;
    /**用户参数*/
    private Map<String, Object> userAttributes;
    /**记录当前拦截器执行的位置*/
    private int currentInterceptorIndex = -1;
    public MethodInvocation(Object proxy,
                            Object target,
                            Method method,
                            Object[] arguments,
                            Class<?> targetClass,
                            List<Object> interceptorsAndDynamicMethodMatchers) {
        this.proxy = proxy;
        this.target = target;
        this.targetClass = targetClass;
        this.method = method;
        this.arguments = arguments;
        this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
    }
    @Override
    public Object getThis() {
        return this.target;
    }
    @Override
    public Object[] getArguments() {
        return this.arguments;
    }
    @Override
    public Method getMethod() {
        return this.method;
    }
    @Override
    public void setUserAttribute(String key, Object value) {
        if (value != null) {
            if (this.userAttributes == null) {
                this.userAttributes = new HashMap<>();
            }
            this.userAttributes.put(key, value);
        }
        else {
            if (this.userAttributes != null) {
                this.userAttributes.remove(key);
            }
        }
    }
    @Override
    public Object getUserAttribute(String key) {
        return (this.userAttributes != null ? this.userAttributes.get(key) : null);
    }
    public Object proceed() throws Throwable{
        //拦截器执行完了,最后真正执行被代理的方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return this.method.invoke(this.target,this.arguments);
        }
        //获取一个拦截器
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
            MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice;
            //执行通知方法
            return mi.invoke(this);
        } else {
            //跳过,调用下一个拦截器
            return proceed();
        }
    }
}
复制代码


MethodInterceptor


新建拦截器MethodInterceptor接口

package com.xiaoliuliu.six.finger.web.spring.aop.intercept;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:19
 */
public interface MethodInterceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}
复制代码


Advice


在实现拦截器MethodInterceptor的子类前,先新建Advice,作为不同通知方法的顶层接口。

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:22
 * 在实现拦截器<kbd>MethodInterceptor</kbd>的子类前,先新建<kbd>Advice</kbd>,作为不同通知方法的顶层接口。
 */
public interface Advice {
}
复制代码


接着写一个抽象子类来封装不同的通知类型的共同逻辑:调用通知方法前先将入参赋值,这样用户在写通知方法时才能拿到实际值。

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;
import java.lang.reflect.Method;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:23
 */
public abstract class AbstractAspectAdvice implements Advice{
    /**通知方法*/
    private Method aspectMethod;
    /**切面类*/
    private Object aspectTarget;
    public AbstractAspectAdvice(Method aspectMethod, Object aspectTarget) {
        this.aspectMethod = aspectMethod;
        this.aspectTarget = aspectTarget;
    }
    /**
     * 调用通知方法
     */
    public Object invokeAdviceMethod(JoinPoint joinPoint, Object returnValue, Throwable tx) throws Throwable {
        Class<?>[] paramTypes = this.aspectMethod.getParameterTypes();
        if (null == paramTypes || paramTypes.length == 0) {
            return this.aspectMethod.invoke(aspectTarget);
        } else {
            Object[] args = new Object[paramTypes.length];
            for (int i = 0; i < paramTypes.length; i++) {
                if (paramTypes[i] == JoinPoint.class) {
                    args[i] = joinPoint;
                } else if (paramTypes[i] == Throwable.class) {
                    args[i] = tx;
                } else if (paramTypes[i] == Object.class) {
                    args[i] = returnValue;
                }
            }
            return this.aspectMethod.invoke(aspectTarget, args);
        }
    }
}
复制代码


实现多种通知类型


拦截器本质上就是各种通知方法的封装,因此继承AbstractAspectAdvice,实现MethodInterceptor。下面分别实现前置通知、后置通知和异常通知。

前置通知

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:37
 */
import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInterceptor;
import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;
import java.lang.reflect.Method;
/**
 * 前置通知
 */
public class MethodBeforeAdviceInterceptor extends AbstractAspectAdvice implements MethodInterceptor {
    private JoinPoint joinPoint;
    public MethodBeforeAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
        super(aspectMethod, aspectTarget);
    }
    private void before(Method method, Object[] args, Object target) throws Throwable {
        super.invokeAdviceMethod(this.joinPoint, null, null);
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        this.joinPoint = mi;
        //在调用下一个拦截器前先执行前置通知
        before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}
复制代码


后置通知

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:40
 */
import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInterceptor;
import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;
import java.lang.reflect.Method;
/**
 * 后置通知
 */
public class AfterReturningAdviceInterceptor extends AbstractAspectAdvice implements MethodInterceptor {
    private JoinPoint joinPoint;
    public AfterReturningAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
        super(aspectMethod, aspectTarget);
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        //先调用下一个拦截器
        Object retVal = mi.proceed();
        //再调用后置通知
        this.joinPoint = mi;
        this.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }
    private void afterReturning(Object retVal, Method method, Object[] arguments, Object aThis) throws Throwable {
        super.invokeAdviceMethod(this.joinPoint, retVal, null);
    }
}
复制代码


异常通知

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;
/**
 * @author 小六六
 * @version 1.0
 * @date 2020/10/26 17:40
 */
import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInterceptor;
import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;
import java.lang.reflect.Method;
/**
 * 异常通知
 */
public class AfterThrowingAdviceInterceptor extends AbstractAspectAdvice implements MethodInterceptor {
    private String throwingName;
    public AfterThrowingAdviceInterceptor(Method aspectMethod, Object aspectTarget) {
        super(aspectMethod, aspectTarget);
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            //直接调用下一个拦截器,如果不出现异常就不调用异常通知
            return mi.proceed();
        } catch (Throwable e) {
            //异常捕捉中调用通知方法
            invokeAdviceMethod(mi, null, e.getCause());
            throw e;
        }
    }
    public void setThrowName(String throwName) {
        this.throwingName = throwName;
    }
}
复制代码


相关文章
|
9天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
7天前
|
关系型数据库 Java MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【9月更文挑战第6天】在Linux环境下安装JDK 1.8、Tomcat和MariaDB是搭建Java Web应用的关键步骤。本文详细介绍了使用apt-get安装OpenJDK 1.8、下载并配置Tomcat,以及安装和安全设置MariaDB(MySQL的开源分支)的方法。通过这些步骤,您可以快速构建一个稳定、高效的开发和部署环境,并验证各组件是否正确安装和运行。这为您的Java Web应用提供了一个坚实的基础。
22 0
|
13天前
|
前端开发 Java UED
JSF遇上Material Design:一场视觉革命,如何让传统Java Web应用焕发新生?
【8月更文挑战第31天】在当前的Web开发领域,用户体验和界面美观性至关重要。Google推出的Material Design凭借其独特的动画、鲜艳的颜色和简洁的布局广受好评。将其应用于JavaServer Faces(JSF)项目,能显著提升应用的现代感和用户交互体验。本文介绍如何通过PrimeFaces等组件库在JSF应用中实现Material Design风格,包括添加依赖、使用组件及响应式布局等步骤,为用户提供美观且功能丰富的界面。
21 0
|
13天前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
26 0
|
13天前
|
开发者 前端开发 开发框架
JSF与移动应用,开启全新交互体验!让你的Web应用轻松征服移动设备,让用户爱不释手!
【8月更文挑战第31天】在现代Web应用开发中,移动设备的普及使得构建移动友好的应用变得至关重要。尽管JSF(JavaServer Faces)主要用于Web应用开发,但结合Bootstrap等前端框架,也能实现优秀的移动交互体验。本文探讨如何在JSF应用中实现移动友好性,并通过示例代码展示具体实现方法。使用Bootstrap的响应式布局和组件可以确保JSF页面在移动设备上自适应,并提供友好的表单输入和提交体验。尽管JSF存在组件库较小和学习成本较高等局限性,但合理利用其特性仍能显著提升用户体验。通过不断学习和实践,开发者可以更好地掌握JSF应用的移动友好性,为Web应用开发贡献力量。
22 0
|
13天前
|
Java 前端开发 Apache
Apache Wicket与Spring MVC等Java Web框架大PK,究竟谁才是你的最佳拍档?点击揭秘!
【8月更文挑战第31天】在Java Web开发领域,众多框架各具特色。Apache Wicket以组件化开发和易用性脱颖而出,提高了代码的可维护性和可读性。相比之下,Spring MVC拥有强大的生态系统,但学习曲线较陡;JSF与Java EE紧密集成,但在性能和灵活性上略逊一筹;Struts2虽成熟,但在RESTful API支持上不足。选择框架时还需考虑社区支持和文档完善程度。希望本文能帮助开发者找到最适合自己的框架。
25 0
|
13天前
|
Java Spring 开发者
Java Web开发新潮流:Vaadin与Spring Boot强强联手,打造高效便捷的应用体验!
【8月更文挑战第31天】《Vaadin与Spring Boot集成:最佳实践指南》介绍了如何结合Vaadin和Spring Boot的优势进行高效Java Web开发。文章首先概述了集成的基本步骤,包括引入依赖和配置自动功能,然后通过示例展示了如何创建和使用Vaadin组件。相较于传统框架,这种集成方式简化了配置、提升了开发效率并便于部署。尽管可能存在性能和学习曲线方面的挑战,但合理的框架组合能显著提升应用开发的质量和速度。
25 0
|
12天前
|
数据库 开发者 Python
web应用开发
【9月更文挑战第1天】web应用开发
29 1
|
10天前
|
前端开发 JavaScript 持续交付
Web应用开发的方法
Web应用开发的方法
11 1
|
10天前
|
前端开发 JavaScript 持续交付
web应用开发
web应用开发
20 1