JavaEE练习项目--快递e栈(第二天)

    科技2022-07-10  102

    JavaEE练习项目–快递e栈(第二天)

    内容地址需求分析链接地址建表,工具类书写链接地址Jdbc工具类链接地址三层架构,阿里云短信链接地址前端Ajax链接地址

    1.创建数据库

    管理员表

    用户表

    快递表

    快递员表

    2.接口编写

    每个对应的数据表都有一个对应的接口,初此之外,对于基本的增删改查操作,独立创建一个BaseCrud接口,让其他接口去继承这个接口。

    BaseCrud

    public interface BaseCrud<T> { /** * 用于查询数据库中的全部快递(总数+新增),待取件快递(总数+新增) * @return [{size:总数,day:新增},{size:总数,day:新增}] */ List<Map<String,Integer>> console(); /** * 查询所有 * @param limit 是否分页的标记,true表示分页。false表示查询所有快递 * @param offset SQL语句的起始索引 * @param pageNumber 页查询的数量 * @return 快递的集合 */ List<T> findAll(boolean limit, int offset, int pageNumber); /** * 更新 * @param t t * @return 修改的结果,true表示成功,false表示失败 */ boolean update(T t); /** * 根据id删除 * @param id 要删除的快递id * @return 删除的结果,true表示成功,false表示失败 */ boolean delete(int id); /** * 插入数据 * @param t t * @return boolean */ boolean insert(T t); /** * 找到通过ID编号 * @param id ID编号 * @return {@link T} */ T findById(Integer id); }

    对于其他的接口,只需要继承基本的增删改查接口,然后自己再拓展功能即可

    public interface BaseUserDao extends BaseCrud<User> { /** * 根据用户手机号码,查询他所有的快递信息 * * @param userPhone 手机号码 * @return 查询的快递信息列表 */ User findByUserPhone(String userPhone); }

    3.中央处理器框架

    我们知道一个servlet只能接受一个或者一组url,但是我们可以通过其他方式来拓展serlvet的功能,方式有很多。比如:每一次请求的参数中都附带一个action参数,参数值为方法名,我们通过反射机制,可以调用对应的方法。再比如spring的解决方案,先将能处理请求的类都写进配置文件,在tomcat启动时,加载配置文件,然后将这些方法都加载进一个容器,我们接受请求时会去容器中选中是否有可以处理请求的处理器,有则调用处理器的方法,没有则返回错误提示。

    这里我们以springmvc的方式来编写我们的控制器。

    DispatherServlet

    我们可以在init方法加载资源,然后service调用是,从我们的容器中去找,找到就处理,没有就返回错误信息,我么的容器的查找处理器的方法应该是静态的。

    public class DispatcherServlet extends HttpServlet { @Override public void init(){ 加载配置文件 } @Override protected void service(HttpServletRequest req, HttpServletResponse resp){ 寻找处理器,并处理请求 }

    init

    我们知道,servlet的web.xml的配置文件中,可以定义初始化参数,我们可以通过ServletConfig来获取这个参数,我们可以把我们的配置文件的全限定类名作为参数,然后初始化时用类加载机制去加载我们的资源文件

    public void init(ServletConfig config) throws ServletException { //获取初始化参数 String path = config.getInitParameter("contentConfigLocation"); InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path); //加载资源文件 HandlerMapping.load(is); }

    web.xml

    我们的处理器只处理.do结尾的请求,这么做的好处就是不会拦截静态资源,但是对于不是.do结尾的请求,我们只能返回404了!

    我们初始化参数contentConfigLocation的值为application.properties,默认是在src路径下的。

    <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>com.dulao.mvc.DispatcherServlet</servlet-class> <init-param> <param-name>contentConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>

    applicaton.properties文件

    这些处理器中都是一些返回值为String,参数为HttpServletRequest和HttpServletResponse的方法,这样我们可以集中使用反射机制来获得这些方法。

    #配置处理器 admin=com.dulao.controller.AdminController express=com.dulao.controller.ExpressController user=com.dulao.controller.UserController courier=com.dulao.controller.CourierController

    举个例子:

    我们的负责管理员登录功能的控制器中写了一个login()方法,那么使用反射的getMethod(HttpServletRequset.class,HttpServletResponse.class)就可以找到这些方法了。但是我们怎么知道这个方法是接收什么请求的呢?

    public class AdminController { public String login(HttpServletRequest request, HttpServletResponse response) { ... } }

    如果这个时候,我们再添加一个方法,那么我们如何知道该方法是用于处理什么请求的呢?可以用方法名,但是方法名有局限性,比如/express/find.do这样的请求,肯定不适合的。我们的spring是通过@RequestMapping注解的方式来绑定请求uri的,所以我们这里也用这种方式,我们来定义几个注解

    public class AdminController { public String login(HttpServletRequest request, HttpServletResponse response) { ... } public String logout(HttpServletRequest request, HttpServletResponse response) { ... } }

    @ResponseBody

    该注解表示我们的返回的结果是json数据,value是url路径。

    @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented /** * 注解的作用: * 被此注解添加的方法, 会被用于处理请求。 * 方法返回的内容,会以文字形式返回到客户端 */ public @interface ResponseBody { String value(); }

    @ResponseView

    该注解表示我们的返回的结果是视图对象,应该使用重定向技术去跳转界面。

    @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented /** * 注解的作用: * 被此注解添加的方法, 会被用于处理请求。 * 方法返回的内容,会直接重定向 */ public @interface ResponseView { String value(); }

    ResponseType枚举类型

    由于我们的注解只是为了类加载的时候识别用的,定义该枚举是作为我们处理器的成员变量方便识别。

    public enum ResponseType { /** * 文本 */ TEXT, /** * 视图 */ VIEW; }

    添加注解之后:

    这样我们就知道了方法的所绑定请求url了。

    @ResponseBody("/admin/login.do") public String courierLogin(HttpServletRequest request,HttpServletResponse response){ } @ResponseBody("/admin/logout.do") public String logout(HttpServletRequest request, HttpServletResponse response) { }

    现在我们基本思路有了,就差定义一个处理器的容器了:

    HandlerMapping

    代码比较长,我这边简单说一下思路:

    我们将请求的路径,方法,已经该方法的对象封装为一个MVCMapping静态内部类,然后将请求url(注解中配置的元数据)作为键,该内部类作为值,存入Map中,然后我们使用get方法获取我们的MVCMapping处理器对象。对于不同的注解,我们传入不同的枚举类型。 public class HandlerMapping { private static Map<String,MVCMapping> data = new HashMap<>(); public static MVCMapping get(String uri){ return data.get(uri); } public static void load(InputStream is ){ Properties ppt = new Properties(); try { ppt.load(is); } catch (IOException e) { e.printStackTrace(); } //获取配置文件中描述的一个个的类 Collection<Object> values = ppt.values(); for (Object cla:values) { String className = (String) cla; try { //加载配置文件中描述的每一个类 Class c = Class.forName(className); //创建这个类的对象 Object obj = c.getConstructor().newInstance(); //获取这个类的所有方法 Method[] methods = c.getMethods(); for (Method m:methods) { Annotation[] as = m.getAnnotations(); if(as != null){ for(Annotation annotation:as){ if(annotation instanceof ResponseBody){ //说明此方法,用于返回字符串给客户端 MVCMapping mapping = new MVCMapping(obj,m,ResponseType.TEXT); Object o = data.put(((ResponseBody) annotation).value(),mapping); if(o != null){ //存在了重复的请求地址 throw new RuntimeException("请求地址重复:"+((ResponseBody) annotation).value()); } }else if(annotation instanceof ResponseView){ //说明此方法,用于返回界面给客户端 MVCMapping mapping = new MVCMapping(obj,m,ResponseType.VIEW); Object o = data.put(((ResponseView) annotation).value(),mapping); if(o != null){ //存在了重复的请求地址 throw new RuntimeException("请求地址重复:"+((ResponseView) annotation).value()); } } } } } } catch (Exception e) { e.printStackTrace(); } } } /** * 映射对象,每一个对象封装了一个方法,用于处理请求 */ public static class MVCMapping{ private Object obj; private Method method; private ResponseType type; public MVCMapping() { } public MVCMapping(Object obj, Method method, ResponseType type) { this.obj = obj; this.method = method; this.type = type; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public ResponseType getType() { return type; } public void setType(ResponseType type) { this.type = type; } } }

    中央处理器寻找处理器

    @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 获取用户请求的uri /xx.do String uri = req.getRequestURI(); //寻找处理器 HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri); if( mapping == null){ resp.sendError(404,"MVC:映射地址不存在:"+uri); return; } //执行处理器的方法 Object obj = mapping.getObj(); Method method = mapping.getMethod(); Object result = null; try { result = method.invoke(obj, req, resp); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } //根据枚举类型,执行不同的操作。 switch (mapping.getType()){ case TEXT: resp.getWriter().write((String)result); break; case VIEW: resp.sendRedirect((String)result); break; default: break; } }

    最后上个图加深理解:

    Processed: 0.018, SQL: 8