JavaWeb-Servlet-Reflect

来源:互联网 发布:为什么淘宝没有gta5 编辑:程序博客网 时间:2024/05/17 03:23

话说:
JavaWeb来实现新闻列表的CURD,最开始我们用最最上古的方式(页面里写JSP来处理请求、页面跳转);后来用Servlet进行了优化,今天继续走在优化的道路上吧。

目标


1 优化BaseDao类;优化查的方式。


* 优化BaseDao类*

1、为什么要优化?

因为遇到的问题啦。在CURD中,R是最不好优化的,因为R以后,需要一个中介来存储数据,在页面请求的时候,传送到页面。相比CUD,多了中间这个容器。最开始我们是把SQL语句和参数写死了的,之后我们优化到了下面这个程度:

以下是把新闻News存放到list集合中的方法

 /**     * 这里我们不把SQL写死,我们会在两个地方用到,1是newsShow.jsp用到,     * 另外一个是修改新闻的时候要用到,代码区别仅仅只是SQL语句不同。     */    public List<News> listShow(String sql, Object[] params) {        List<News> list = new ArrayList<>();        getConn();        try {            ps = conn.prepareStatement(sql);            /**             * 以下方法解决了参数个数和参数类型不同的问题             * 高度抽象,不论你给多少参数,什么类型,都可以处理。             */            //这里的params可能有,也可以没有比如select * from t_news和select * from t_news where id = ?            //所以最好判断一下            if(params != null && params.length>0) {                for(int i = 0;i<params.length;i++) {                    ps.setObject((i+1),params[i]);                }            }               //否则,就不用给参数赋值了,直接执行,这样逻辑才够严谨。           rs =  ps.executeQuery();            while (rs.next()) {                int id = rs.getInt("id");                String title = rs.getString(2);                String author = rs.getString("author");                News news = new News(id,title,author);                list.add(news);            }        } catch (SQLException e) {            e.printStackTrace();        }        return  list;    }

这样优化,挺好的,你只要给我一个SQL,参数,我就能查出结果。
比如你在页面想显示所有列表信息,就需要用到这条SQL语句:
select * from t_news;
如果,需要修改新闻,页面点击修改的时候,也需要用到这段代码,根据要修改的id把对应的那一条新闻(对象News)先放到页面上,用以下SQL语句
select * form t_news where id = ?
这样,这段代码完美避免了需要写两个方法的局面。

问题是:
今天,我们存放的是list,明天我需要存放另外一个对象,比如User,或者后天又换了一个对象……也就是对象天天变,怎么办?代码把对象写死了……………
假如,我们需要把User对象放到页面上,按照以上逻辑,需要继续写类似的代码:
先继续在news_db数据库创建t_user表并存放数据。

8:21 2017/10/5use news_db;create table t_user (id int(11) auto_increment primary key,nickname varchar(12),username varchar(12));insert into t_user (nickname,username) values ("不三不四","小狗"),("桃谷四仙","小猪"),("叮叮当当","小鹅"),("狗杂种","无名氏");

代码如下:

package com.hmc.jdbc.news.dao;import com.hmc.jdbc.news.model.User;import java.sql.SQLException;import java.util.ArrayList;import java.util.List;/** * User:Meice * 2017/10/4 */public class UserDao extends BaseDao {    //定义方法list()来把从MySQL中读取的数据存放进去    public List<User> listShow2 (String sql,Object[] params) {        List<User> list = new ArrayList<>();        //还是一样,类似News的list()方法:获取连接--》预编译SQL语句--》执行SQL语句--》遍历取值--》用JavaBean存放到User对象中        getConn();        //我们在数据库news_db中创建t_user;类似t_news。字段和User类保持一致(为反射埋下伏笔)        try {            ps = conn.prepareStatement(sql);            //SQL语句可能带参,也可能不带参,这里一并处理;以下表明带参.            if(params != null && params.length>0) {                //遍历参数,并且为参数赋值               for(int k=0;k<params.length;k++) {                   ps.setObject((k+1),params[k]);               }            }           //不带参、带参都继续执行以下代码            //执行SQL语句            rs = ps.executeQuery();            /**             *遍历取值,存放到User这个bean中,为什么要给List结合中直接添加对象?不能一个个属性添加?             *可以的,会累死你的!比如有七八个属性,list.add....             *JavaBean的作用就是处理这个问题,不论多少属性,直接作为个整体添加到类似list<E>这样的容器中去             */            while(rs.next()) {                int id = rs.getInt("id");                String nickname = rs.getString("nickname");                String username = rs.getString("username");                //这一步就是最后一公里的作用,也是JavaBean的核心                User user = new User(id,nickname,username);                //把所有属性封装到一个对象中添加到集合中,就很省力                list.add(user);            }           //因为user已经toSting了,而list集合存放也都是user,所以可以直接输出测试是否存进去            System.out.println(list);        } catch (SQLException e) {            e.printStackTrace();        }        //关闭连接,释放资源        closeJDBC(rs,ps,conn);        return list;    }    public static void  main(String[] args) {        UserDao ud = new UserDao();        String sql = "select * from t_user";        //这里我们可以赋值为{},当然也可直接赋值为null,更加省事.当然,测试带参也是没问题的。        // Object[] params = {};        ud.listShow2(sql,null);    }}

以上这段代码还可以有这种写法:

 //定义方法list()来把从MySQL中读取的数据存放进去public List<User> listShow2 (String sql,Object[] params){}//我们可以把Object[] params写成这样,也是可以的public List<User> listShow2 (String sql,Object... params) {}

你会发现,这个listShow2()方法和之前BaseDao的listShow()方法几乎一模一样,就是list里面存放的对象变了。我们想要的结果是,你在调用方法的时候,给我一个对象,我就知道去查这个对象,而不是写死这个对象。这也是今天要解决的个问题。

再次升级listShow()这个方法为listShow3()

 //再次优化查的方式,把List<E>对象也变得灵活抽象,不写死    public List<Object> listShow3(String sql ,Class<?> cls,Object...  params) {        //创建集合存放        List<Object> list = new ArrayList<>();        getConn();        try {            //预编译            ps = conn.prepareStatement(sql);            //如果带参,遍历赋值            if(params != null && params.length>0){                for(int i =0;i<params.length;i++) {                    ps.setObject((i+1),params[i]);                }            }            //执行SQL            rs = ps.executeQuery();            //获取元数据            ResultSetMetaData rsmd = rs.getMetaData();            //遍历            while (rs.next()) {                /**                 * 要解决的问题就在这里                 * 我们不知道调用该方法的人会传入什么类?那个类中有什么属性?                 * 也同样不知道该用什么类型来接收,也同样没法把属性作为一个对象传递给                 * list<E>集合?                 */                //实例化对象                Object obj = cls.newInstance();                //获取列数                int count = rsmd.getColumnCount();                //rs.getObject(这里面的列数通过remd获取);                    for(int k=1;k<=count;k++) {                        //获取列名                      String colName =  rsmd.getColumnName(k);                      //根据列名获取对象字段                        Field f = cls.getDeclaredField(colName);                        //给字段赋值;获取列值                       Object colVal = rs.getObject(colName);                       //给对象赋值                        f.setAccessible(true);//取消访问检查                        f.set(obj,colVal);                    }                list.add(obj);            }        } catch (SQLException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (NoSuchFieldException e) {            e.printStackTrace();        }        return  list;    }    public void test() {        String sql = "select * from t_goods";        List<Object> list = listShow3(sql, Goods.class,null);        System.out.println(list);    }

总结


这里有几个新知识点,和之前差别比较大
1、Class.newInstance()方法。这个方法创建一个对象的实例。这里为什么要用这个?因为对象不确定啊,你给我什么类,我就给你创建这个类的实例。之前版本的BaseDao()里面,我们遍历rs(ResultSet)后,是这么创建对象的

  while (rs.next()) {                int id = rs.getInt("id");                String title = rs.getString(2);                String author = rs.getString("author");                News news = new News(id,title,author);                list.add(news);            }

2、 rs.getMetaData()返回一个ResultSetMetaData对象rsmd
为什么要用到它?
我们知道ps.executeQuery(sql)返回的是一个ResultSet集合,一旦执行,后台就知道去数据库中的SQL语句中的这个表去遍历,这个结果集的next()就是一行一行取值,每一行表面是数据库的一行数据,本质就是JavaBean的一个对象;每一行包含了多个字段(对应实体类多个属性),我们遍历的目的就是把字段值取出来,然后赋值给我们JavaBean类的属性。如果有很多个字段(对应实体类中多个属性,所以要保持一致),不可能像上面代码那样一个个赋值,所以需要遍历,遍历就需要控制循环的条件,rsmd.getColumnCount()方法就可以获取数据库中字段个数。循环次数问题便解决了。
之前,因为新闻(News)就3个字段,int id ;String title;Sting author,所以像上面代码那样一个个赋值的。一旦属性多起来,会累坏宝宝的。
既然可以获取数据库该表的字段个数,当然也可以获取字段名: rsmd.getColumnName(k);给我字段位置k,就能知道所有字段名。

3、Class.getDeclaredField(String name)
这是类Class的一个方法,返回一个Field对象。这是今天最为重点的内容。Class我们不陌生了,之前的Class.forName(“com.mysql.jdbc.Driver”);就接触过。我们通过rsmd得到列名,通过这个就可以返回一个对象。
这里的核心就是Reflect,反射。前面rsmd之所以知道数据库表的字段,是因为你调用方法的时候,会给一个SQL语句,语句里面就需要指明数据表,Java内部这个rsmd就可以根据这个表,找到字段名和字段个数。但是,数据库的表和我们的实体类之间的关系怎么建立呢?靠的就是这个反射。
凭什么你的数据表字段就是我实体类的属性?申明一下,这个方法优化的前提是类的属性个数内容和数据库表的字段保持一致。也就是这样,才能正确映射过来。靠的就是Field这个类。

4、f.set(obj,colVal);
Field类有个方法,给我一个对象obj,给我属性值colVal,我就能为这个类属性赋值。这里本质就是我们实例化的一个过程。不过是反射中的实例化。
之前,我们是这么做的:

                int id = rs.getInt("id");               String title = rs.getString(2);                String author = rs.getString("author");                News news = new News(id,title,author);

现在,因为属性都是未知的,所以要通过这种方式实例化。这样一个活生生的对象就诞生啦!

5、f.setAccessible(true);//取消访问检查
在执行过程中,如果未设置这个,就会报错:
这里写图片描述

这是因为在使用反射对象的时候,Java会进行语言访问控制检查。本质就是一种安全策略吧。


执行过程中现在,我们调用这个方法listShow3(sql,class,Object… params)就可以把数据库中表的内容全部放到集合中啦。你给我什么类,都可以。(创建好表,创建好实体类,属性-字段保持一致)

调用我们新创建的User类,
结果如下:
这里写图片描述

同理,我们再次创建一个Goods JavaBean,并在数据库中创建对应的表,进行验证:
Goods类

package com.hmc.jdbc.news.model;/** * User:Meice * 2017/10/5 */public class Goods {    private  int id;    private String gName;    private String gType;    private String gFun;    public Goods() {    }    public Goods(int id, String gName, String gType, String gFun) {        this.id = id;        this.gName = gName;        this.gType = gType;        this.gFun = gFun;    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getgName() {        return gName;    }    public void setgName(String gName) {        this.gName = gName;    }    public String getgType() {        return gType;    }    public void setgType(String gType) {        this.gType = gType;    }    public String getgFun() {        return gFun;    }    public void setgFun(String gFun) {        this.gFun = gFun;    }    @Override    public String toString() {        return "Goods{" +                "id=" + id +                ", gName='" + gName + '\'' +                ", gType='" + gType + '\'' +                ", gFun='" + gFun + '\'' +                '}';    }}

数据库nes_db中创建对应的表t_goods

create table t_goods(id int(11) auto_increment primary key,gName varchar(11),gType varchar(11),gFun varchar(11));insert into t_goods (gName,gType,gFun) values  ("方便面","食品","方便快捷的填饱肚子"),("罐头","食品","发酵的感觉"),("钢笔","文具","写出潇洒的字");

调用方法验证:

 public void test() {        String sql = "select * from t_goods";        List<Object> list = listShow3(sql, Goods.class,null);        System.out.println(list);    }

结果如下:
这里写图片描述

此时此刻,心情是激动的,BaseDao的优化基本告一段落。
再会!国庆快乐。