JAVA 一个servlet处理多种请求,自动化前端数据封装成对象,简化数据库操作,你想要的都在这里!超级详细!

前言

上一篇写了数据库连接池的操作【点击查看】,连接完数据库不就该操作数据库了(dao层和service层),操作数据库之后不就该从页面获取的数据中封装对象,然后servlet调用service,然后通过service调用dao操作数据库了。
相信很多刚入门的小伙伴自己写这些的时候头都大了,一个servlet对应一个请求,servlet写多了名字都不好起了,还有数据库操作,从数据库取数据封装对象的时候简直是个折磨,明明代码就差那么一点,还得把相同的东西写那么多次,那么能不能简化这些操作呢?
当然是能的了,推动人们进步的就是懒惰,所以这些事情是有大佬完成的,直接拿来用也是可以的,但是咱们最起码不是还要试试自己能不能成呢?对吧,不能成也好歹懂点原理呗。

基础的很浅层次原理解析

场景分析

想想一个超市,妈妈让你去买酱油,但是你不知道酱油长什么样,那么你怎么办呢?肯定是直接找服务员了,直接让他给你取一瓶酱油就好了啊。
那么根据上面的场景带入一下,你的servlet就能类比成一个超市吧,处理请求的具体方法就相当于那瓶你不知道长什么样子的酱油吧,那么现在咱们还缺什么?一个认识所有酱油并且能帮你拿到酱油的服务员吧(前端的请求路径就是去超市的导航嘛,不同的servlet就相当于不同的超市吧)
和这个场景类似的就是前端页面的数据封装了,多个request.getParameter("")简直让人抓狂,那这个是不是就相当于你要多瓶不同的酱油并装到一个袋子里边?
再类比一下数据库的操作,那不同的sql语句就是你不同的要求,只要告诉不同的服务员不同的要求就好了。
根据以上的三个场景,就会发现万事俱备,只差服务员了,对吧?

实现原理

想一下,这个服务员应该是知道所有的东西长什么样,叫什么的,那么这个服务员怎么找呢?
在java中,类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。根据这个原理就不难发现,一个能够认识所有类、所有方法、所有属性的类(服务员)——反射
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

类名用途
Class类代表类的实体(类和接口)
Field类代表类的属性
Method类代表类的方法
Constructor类代表类的构造方法

那么有以上的了解了,下面就可以开始构建自己的服务员了。
值得注意的一点是:咱们平时创建类的时候都是new一个出来,那通过反射怎么创建类呢?
通过反射机制创建的类都是通过构造器创建的,所有就是你要常见的类必须有构造器方法

一个servlet处理多个请求

根据上面的长片大论的分析,相信很多人已经看得云里雾里了,那么下面就来实现一下吧
先说一下,前端请求到了一个servlet(超市)中都会进入两个方法,doPost和doGet,咱们一般的做法就是doGet调用doPost,那么这不想当于就只有一个门能进去超市了,那咱们在这个门这里设置一个服务员不就能满足所有客户(request)的请求了,让他们能拿到想要的酱油了,在根据之前说的反射机制来说,只要让客户(前端)说明想要什么就行了,所以前端得有个参数说明自己想要什么(调用哪个方法处理请求)
补充:为了更简洁一点,可以创建一个父类,写一个doPost方法(实现一个服务员),然后子类继承父类,就能继承到这个服务员,也就相当于自己也有个服务员了,当然也可以自己每个servlet中都写上doPost方法

 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 		//请求和响应乱码解决
        response.setContentType("text/html;charset=UTF-8");
        request.setCharacterEncoding("UTF-8");
        //从前端获取方法名(想要什么酱油)
        String methodName = request.getParameter("method");
        try {
            //通过方法名获取要执行的方法(服务员去拿到想要的酱油)
            //this.getClass().getDeclaredMethod()这个this代表是自己,
            //		getDeclaredMethod能够获取所有方法,包括private的
            Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
            //上调权限
            method.setAccessible(true);
            //执行
            method.invoke(this, request,response);
        } catch (Exception e) {
            e.printStackTrace();
            //抛出异常,方便上层捕获处理
            throw new RuntimeException(e);
        }
    }

实现了这个方法之后,你就可以一个servlet中写很多方法了,只要前端传入的方法名和类中的方法名一致即可,并且这个方法写的很粗糙,很多异常没处理,只能自己简单用用,真正的项目还得上框架.

自动化前端数据封装成对象

通过上面的分析,自己实现一个

根据以上的分析,你只需要在前端需要封装数据的时候,叫来服务员帮你封装就行了,这个是自己创建的服务员,下面的方法很粗糙只是自己知道后自己写着娱乐的,没有数据类型转换,还有很多没处理的异常

	/**
     * 将页面传来数据封装成bean
     * @param request 页面请求,用来获取页面传来的数据
     * @param t 要封装成的对象
     * @param <T> 泛型,不确定的参数类型
     * @return 封装好的bean
     */
	 public static <T> T paramsToBean(HttpServletRequest request, T t) {
	        try {
	            //获取所有需要封装的属性,包括私有属性
	            Field[] declaredFields = t.getClass().getDeclaredFields();
	            //利用反射循环赋值
	            for (Field field : declaredFields) {
	                //获取参数类型
	                Class<?> type = field.getType();
	                //获取参数名
	                String name = field.getName();
	                //通过参数名获取对应的前端变量
	                String value = request.getParameter(name);
	                //通过响应的参数名写出setter方法名
	                String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
	                //获取setter方法
	                Method method = t.getClass().getDeclaredMethod(methodName, type);
	                //调用setter方法
	                method.invoke(t, value);
	            }
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	        return t;
	    }

看一下大佬实现的

上面说了自己写的有很多缺陷,也有很多异常没有解决,就是自己娱乐的,真正用的时候还得用大佬的实现。
先附上要用到的jar包(版本号根据自己的需要吧,下面是maven连接):
commons-beanutils-x.x.x.jar:点击前往下载页面
commons-logging-x.x.x.jar:点击前往下载页面

	/**
     * 将页面传来数据封装成bean
     * @param request 页面请求,用来获取页面传来的数据
     * @param t 要封装成的对象
     * @param <T> 泛型,不确定的参数类型
     * @return 封装好的bean
     */
	public static <T> T paramsToBean(HttpServletRequest request,T t){
	        try {
	            //BeanUtils工具类,用来封装对象,会自动转型
	            //第二个是一个数据的map集合,存放了数据名和值
	            Map<String, String[]> parameterMap = request.getParameterMap();
	            BeanUtils.populate(t, parameterMap);
	        }catch (Exception e) {
	            e.printStackTrace();
	        }
	        return t;
	    }

至于更底层的源码还是要自己去看的,实现原理应该是一样的

简化数据库操作,智能化数据库的操作

自己的实现简直没法看,bug多得很,异常也没处理,效率也不高,所以就直接上大佬的操作吧

大佬的实现

导包-maven:

c3p0-x.x.x.x.jar:点击前往下载页面,数据库连接池
commons-dbutils-x.x.jar:点击前往下载页面,数据库操作的核心
mchange-commons-java-x.x.x.jar:点击前往下载页面,依赖包
mysql-connector-java-x.x.x.jar:点击前往下载页面,数据库连接驱动

数据库的核心方法

然后就是说一下大佬数据操作的核心,也是重点要用的方法

 private QueryRunner queryRunner = new QueryRunner();
方法作用
queryRunner.query查询数据并封装成java类,能查询一条数据,也能查询多条数据(封装成List),查询单个属性(封装成Object)
queryRunner.update增、删、改方法一体,能做这三种操作,并返回影响行数
queryRunner.batch批处理操作

操作的具体实现

传入sql语句,这就相当于你告诉服务员自己要什么,传入的其他参数就是附加条件了,比如说我要便宜或者贵的酱油,这个底层应该也是通过反射实现的,有兴趣研究的同学可以去看看源码。具体分析就直接写到注释里边了


     /**
     * 进行数据库操作
     */
    private static QueryRunner queryRunner = new QueryRunner();

    /**
     * 获取数据库中的一行,并封装成实体对象
     * @param sql sql语句
     * @param type 要获取的数据实体类型
     * @param params 可变参数,执行sql语句需要的参数不确定
     * @param <T> 返回类型
     * @return 返回封装好的实体对象
     */
    public static <T> T getBean(String sql,Class<T> type,Object ...params){
        Connection connection = null;
        T obj = null;
        try {
            connection = JDBCUitls.getConnection();
            //执行查询操作,并将返回值封装成想要的实体对象,例如user、student之类的
            //new BeanHandler<>(type),这个就是根据你要封装的数据类型(type)通过内部实现的方法封装成你想要的对象类型,通过反射为每个属性赋值,
            obj = queryRunner.query(connection, sql, new BeanHandler<>(type),params);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUitls.close(connection);
        }
        return obj;
    }

    /**
     * 获取数据库中的多行数据,并封装成实体链
     * @param sql sql数据
     * @param type 封装实体对象类型
     * @param params 执行sql需要的参数
     * @param <T> 返回值类型
     * @return 封装好的实体链
     */
    public static <T> List<T> getBeanList(String sql, Class<T> type, Object ...params){
        Connection connection = null;
        List<T> obj = null;
        try {
            connection = JDBCUitls.getConnection();
            obj = queryRunner.query(connection, sql, new BeanListHandler<>(type),params);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUitls.close(connection);
        }
        return obj;
    }

    /**
     * 执行增删改,并返回是否成功
     * @param sql 执行的sql
     * @param params 执行sql是需要的参数
     * @return true表示成功
     */
    public static boolean update(String sql, Object ...params){
        Connection connection = null;
        int update = 0;
        try {
            connection = JDBCUitls.getConnection();
            //返回值是影响的行数
            update = queryRunner.update(connection, sql, params);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUitls.close(connection);
        }
        return update>0;
    }

    /**
     * 获取数据库中的某一个属性
     * @param sql sql语句
     * @param params sql执行是使用的参数
     * @return 查到数据
     */
    public static Object getValue(String sql, Object ...params){
        Connection connection = null;
        Object result = null;
        try {
            connection = JDBCUitls.getConnection();
            //new ScalarHandler<>()要封装的属性类型,因为一般单个属性的类型不确定,所以不传入参数,让其返回Object类型,自己进行类型转换
            result = queryRunner.query(connection, sql, new ScalarHandler<>(), params);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUitls.close(connection);
        }
        return result;
    }
    /**
     * 批量处理sql的方法
     * @param sql 需要批量处理的sql
     * @param params 二维对象数组,第一维表示需要处理的次数,第二维表示每次处理时附加的参数
     */
    public void batch(String sql,Object[][] params){
        Connection connection = JDBCUtils.getConnection();
        try {
            queryRunner.batch(connection, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUitls.close(connection);
        }
    }

上面的方法一般来说就囊括了大部分的数据库操作了,一些特定的就需要自己写了,然后就是传入参数的一些介绍了,一般来说自己写sql语句最烦的就是拼串了,所以大佬的工具简化了这个操作,从下面的大佬的方法的源代码就可以看出来大佬是做了sql语句的拼串操作了,就是从可变参数params中取得要拼串的值,其他方法的源码也差不多.

private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
		//特殊情况判断
        if (conn == null) {
            throw new SQLException("Null connection");
        } else if (sql == null) {
            if (closeConn) {
                this.close(conn);
            }

            throw new SQLException("Null SQL statement");
        } else if (rsh == null) {
            if (closeConn) {
                this.close(conn);
            }

            throw new SQLException("Null ResultSetHandler");
        } else {
        	//拼串操作
            PreparedStatement stmt = null;
            ResultSet rs = null;
            Object result = null;

            try {
                stmt = this.prepareStatement(conn, sql);
                this.fillStatement(stmt, params);
                rs = this.wrap(stmt.executeQuery());
                result = rsh.handle(rs);
            } catch (SQLException var33) {
                this.rethrow(var33, sql, params);
            } finally {
                try {
                    this.close(rs);
                } finally {
                    this.close(stmt);
                    if (closeConn) {
                        this.close(conn);
                    }
                }
            }
            return result;
        }
    }

从上面可以看出,人家有个专门用来拼串的方法,这里就不展开介绍了,这一篇写的太长了应该分成好几篇的,但是写到这了就算了,简单说说吧
人家拼串是有规则的,你如果不传入那个可变参数,或者可变参数为null,人家就默认你自己拼好串了就不会再拼串而是直接执行你的sql语句了;你如果传入了可变参数呢,人家是怎么拼串的呢?人家规定你的sql语句中需要拼串的地方用"?“占位,然后人家会根据你传入的可变参数,按照顺序来把”?"替换掉
大概是这样的,下面是小例子,根据例子理解会简单

	/**
     * 通过账号查找用户
     * @param username 账号
     * @return 找到返回用户实体,没找到返回null
     */
    public User getUserByUserName(String username){
        String sql = "select id,user_name as username,password from users where user_name =?";
        return BaseDao.getBean(sql, User.class, username);
    }
    /**
     * 注册用户
     * @param user 要注册的用户
     * @return true表示注册成功
     */
    public boolean registerUser(User user){
        String sql = "insert into users(name,date,mobile,user_name,password)  values(?,?,?,?,?)";
        return BaseDao.update(sql, user.getName(),user.getDate(),user.getMobile(),
                    user.getUsername(),user.getPassword());
    }
    
	/**
     * 通过id删除用户
     * @param id id
     * @return true表示删除成功
     */
    public boolean deleteUser(String id){
        String sql = "delete from users where id ='"+id+"'";
        return BaseDao.update(sql);
    }

从上面的例子应该能看明白吧

注意事项

认真阅读就会发现,有一个注意事项,为什么它能直接帮你将数据库中取得的数据封装成你想要的对象呢?
再结合上面讲的反射,然后根据咱们传入的参数中有个是type——想要封装的对象的类型,然后就能联想到通过这个类型获取到类,然后获取到类里面的所有属性,然后根据属性名封装对象,事实上源码中也是这个思想,但是人家实现的时候考虑到了种种的复杂情况,然后你可继续思考一下,为什么能够根据属性名封装对象,肯定是调用setter方法,根据咱们实现的粗糙的前端封装对象的方法你也能看出是需要传入参数名和属性名一致才能拼接出正确的setter方法来给对象赋值,那么类比一下,这个是不是应该是查询出来的数据名和要疯装的数据名得一致,那么怎么一致呢?这个就简单了,你可以创建对象的时候根据数据库的字段名创建,也可以起别名实现名字一致。

总结

这一篇写的很长,原理讲的也是很浅,自己去实现的也很粗糙,但是最主要的是学习这种思想,完善全面考虑,然后根据上面的一系列的操作就会发现,从数据到前端,如果架构好的话,写代码真的很舒服,最起码的命名就可以看出如果从数据库到前端的命名都一致的话就能节省大量的错误机会吧,最后就是希望大佬们多多指教,有什么错误的地方一定要指出来,大家一起进步。

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是一个简单的示例代码: ```java public class MyServlet extends HttpServlet { // 重写doGet方法 @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String servletPath = req.getServletPath(); // 根据请求路径动态实例化不同的Servlet对象 HttpServlet servlet = null; if ("/hello".equals(servletPath)) { servlet = new HelloServlet(); } else if ("/world".equals(servletPath)) { servlet = new WorldServlet(); } // 调用相应的Servlet对象的service方法 if (servlet != null) { servlet.service(req, resp); } else { resp.sendError(HttpServletResponse.SC_NOT_FOUND, "404 Not Found"); } } } // HelloServlet类 public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("Hello, Servlet!"); } } // WorldServlet类 public class WorldServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("World, Servlet!"); } } ``` 在这个示例代码中,我们定义了一个MyServlet类,它继承自HttpServlet。在MyServlet中,我们重写了doGet方法,根据请求路径动态实例化不同的Servlet对象。具体地,如果请求路径是/hello,则实例化HelloServlet对象;如果请求路径是/world,则实例化WorldServlet对象。然后,我们调用相应的Servlet对象的service方法,处理HTTP请求。 这个示例代码展示了如何根据请求路径动态实例化不同的Servlet对象,实现了一个简单的Servlet路由器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值