• day17_Listener与Filter学习笔记


    一、Listener监听器(了解)

    Javaweb开发中的监听器,是用于监听web常见对象的。
      例如web的常见对象有:ServletContext、HttpServletRequest、HttpSession
      监听它们的创建与销毁属性变化session绑定javaBean

    1、监听机制

      事件:就是一个事情。
      事件源:产生这个事情的源头。
      监听器:用于监听指定的事件的对象。(关联事件和事件源)
      注册监听:要想让监听器可以监听到事件产生,必须对其进行注册。

    2、Javaweb开发中常见监听器

    2.1、监听域对象的创建与销毁

      若要监听ServletContext对象的创建与销毁       则需要写一个监听器类实现 ServletContextListener 接口
      若要监听HttpSession对象的创建与销毁         则需要写一个监听器类实现 HttpSessionListener 接口
      若要监听HttpServletRequest对象的创建与销毁     则需要写一个监听器类实现 ServletRequestListener 接口

    2.2、监听域对象的属性变化

      若要监听ServletContext对象的属性变化       则需要写一个监听器类实现 ServletContextAttributeListener 接口
      若要监听HttpSession对象的属性变化         则需要写一个监听器类实现 HttpSessionAttributeListener 接口
      若要监听HttpServletRequest对象的属性变化     则需要写一个监听器类实现 ServletRequestAttributeListener 接口

    2.3、监听session绑定javaBean

      若要监听javaBean对象是否绑定到了session域    则需要写一个javaBean实现 HttpSessionBindingListener 接口
      若要监听javaBean对象的活化与钝化            则需要写一个javaBean实现 HttpSessionActivationListener 接口

    3、监听器的快速入门

      关于创建一个监听器的步骤:
        1、创建一个监听器类,实现指定的监听器接口。
        2、重写接口中的方法。
        3、在web.xml文件中对监听器进行注册

    3.1、关于域对象的创建与销毁的演示

    1. ServletContext对象的创建与销毁
      ServletContext对象是在服务器启动时创建的,在服务器关闭时销毁的。

    public class MyServletContextListener implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println("ServletContext对象被创建了"); // 服务器一启动,ServletContext对象就被创建了
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            System.out.println("ServletContext对象被销毁了"); // 服务器一关闭,ServletContext对象就被销毁了
        }
    }

    2. HttpSession对象的创建与销毁
    HttpSession session = request.getSession();
    Session的销毁方式:
      1、默认超时30分钟后销毁
      2、关闭服务器时销毁
      3、调用invalidate()方法
      4、setMaxInactiveInterval(int interval) 可以设置超时时间
    问题:直接访问一个jsp页面时,是否会创建Session?
    答:会创建,因为我们默认情况下是在jsp页面中直接使用Session内置对象的。

    public class MyHttpSessionListener implements HttpSessionListener {
        @Override
        public void sessionCreated(HttpSessionEvent se) {
            System.out.println("HttpSession对象被创建了");
        }

        @Override
        public void sessionDestroyed(HttpSessionEvent se) {
            System.out.println("HttpSession对象被销毁了");
        }
    }

    index.jsp

      <body>
        <%
            session.invalidate(); // 手动使session销毁
         %>

      </body>

    3. HttpServletRequest对象的创建与销毁
    Request对象是发送请求时服务器就会去创建它,当响应产生时,request对象就会被销毁。

    public class MyServletRequestListener implements ServletRequestListener {
        @Override
        public void requestDestroyed(ServletRequestEvent sre) {
            System.out.println("ServletRequest被销毁了");
        }

        @Override
        public void requestInitialized(ServletRequestEvent sre) {
            System.out.println("ServletRequest被创建 了");
        }
    }

    3.2、演示Request域对象中的属性变化

    在java的监听机制中,它的监听器中的方法都是有参数的参数就是事件对象,而我们可以通过事件对象直接获取事件源。

    public class MyServletRequestListener implements ServletRequestAttributeListener {
        @Override
        public void attributeAdded(ServletRequestAttributeEvent srae) {
            System.out.println("ServletRequest添加属性了");
        }

        @Override
        public void attributeRemoved(ServletRequestAttributeEvent srae) {
            System.out.println("ServletRequest移除属性了");
        }

        @Override
        public void attributeReplaced(ServletRequestAttributeEvent srae) // 参数代表事件对象
            System.out.println("ServletRequest替换属性了");
            // srae.getServletRequest(); // 通过事件对象直接获取事件源
            // System.out.println(srae.getName() + " " + srae.getValue()); // org.apache.catalina.ASYNC_SUPPORTED  true
        }
    }

    index.jsp

      <body>
        
    <%
            // session.invalidate(); // 手动使session销毁
            request.setAttribute("name""tom");
            request.setAttribute("name""luna");
            request.removeAttribute("name");
         %>

      </body>

    3.3、演示session绑定javaBean

    1、javaBean对象自动感知被绑定到session中
    HttpSessionBindingListener 这个接口是由javaBean实现的,并且不需要在web.xml文件中进行注册。

    public class User implements HttpSessionBindingListener {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name{
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age{
            this.age = age;
        }

        @Override
        public void valueBound(HttpSessionBindingEvent event
    {
            System.out.println("向session中绑定了一个user对象");
        }

        @Override
        public void valueUnbound(HttpSessionBindingEvent event
    {
            System.out.println("从session中将user对象移除");
        }
    }

    index.jsp

      <body>
        
    <%
            // session.invalidate(); // 手动使session销毁

            // request.setAttribute("name""tom");
            // request.setAttribute("name""luna");
            // request.removeAttribute("name");

            User user = new User();
            user.setName("luna");
            user.setAge(25);
            // 绑定到session
            session.setAttribute("u", user); 
            // 解除绑定
            session.removeAttribute("u"); 
         %>

      </body>

    2、javabean对象可以活化或钝化到session中
      HttpSessionActivationListener,如果javaBean实现了这个接口,那么当我们正常关闭服务器时,session中的javaBean对象就会被钝化到我们指定的文件中
      当下一次再启动服务器,因为我们已经将对象写入到文件中,这时就会自动将javaBean对象活化到session中。

    public class User implements SerializableHttpSessionBindingListenerHttpSessionActivationListener {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name{
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age{
            this.age = age;
        }

        @Override
        public void valueBound(HttpSessionBindingEvent event
    {
            System.out.println("向session中绑定了一个user对象");
        }

        @Override
        public void valueUnbound(HttpSessionBindingEvent event
    {
            System.out.println("从session中将user对象移除");
        }

        @Override
        public void sessionDidActivate(HttpSessionEvent se
    {
            System.out.println("钝化");
        }

        @Override
        public void sessionWillPassivate(HttpSessionEvent se
    {
            System.out.println("活化");
        }
    }

    我们还需要个context.xml文件来配置钝化时存储的文件,在META-INF目录下创建一个context.xml文件,输入以下代码:

    <Context>
        <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
            <Store className="org.apache.catalina.session.FileStore" directory="it315"/>
        </Manager>
    </Context>

    4、监听器的案例

    4.1、案例_定时销毁session

    1、怎样可以将每一个创建的session全都保存起来呢?
      答:我们可以做一个HttpSessionListener,当session对象创建时,就将这个session对象装入到一个集合中,然后将集合List<HttpSession>保存到ServletContext域中。
    2、怎样判断session是否过期了呢?
      答:在HttpSession中有一个方法public long getLastAccessedTime(),它可以得到session对象最后使用的时间。然后可以使用invalidate方法销毁
      我们上面的操作需要使用任务调度功能
      在java中有一个Timer定时器类,定时器测试代码:
    TestTimer.java

    public class TestTimer {
        public static void main(String[] args{
            // 创建一个计时器对象
            Timer t = new Timer();

            // 调度任务功能
            t.schedule(new TimerTask() { // 要执行的任务

                @Override
                public void run() 
    {
                    System.out.println(new Date().toLocaleString());
                }
            }, 50001000); // 5秒后开始,每隔1秒执行一次
        }
    }

    监听ServletContext代码:

    public class MyServletContextListener implements ServletContextListener {

        @Override
        public void contextInitialized(ServletContextEvent sce) {
            // 通过事件对象得到事件源(ServletContext)
            ServletContext application = sce.getServletContext();

            // 创建一个集合用于存储所有的session对象(需要考虑并发问题,因为我们在web中,它一定是一个多线程的)
            final List<HttpSession> list = Collections.synchronizedList(new ArrayList<HttpSession>());

            // 把集合放到application域中
            application.setAttribute("sessions", list);

            // 创建一个计时器对象
            Timer t = new Timer();

            // 调度任务
            t.schedule(new TimerTask() {

                @Override
                public void run() {
                    System.out.println("开始扫描了...");

                    for (Iterator iterator = list.iterator(); iterator.hasNext();) {
                        HttpSession session = (HttpSession) iterator.next();

                        long l = System.currentTimeMillis() - session.getLastAccessedTime();
                        if (l > 5000) { // 如果时间大于5秒,就把session销毁
                            System.out.println("session移除了" + session.getId());

                            session.invalidate(); // 把session销毁
                            iterator.remove();
                        }
                    }
                    /*
                    for (HttpSession session : list) { 
                        long l = System.currentTimeMillis() - session.getLastAccessedTime();
                        if (l > 5000) { // 如果时间大于5秒,就把session销毁
                            session.invalidate(); // 把session销毁
                            list.remove(session); // 从集合中移除 
                        } 
                    }
                    */

                }
            }, 20005000); // 延迟2秒后执行,每间隔5秒执行一次
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce) {

        }

    }

    监听Session代码:

    public class MySessionListener implements HttpSessionListener {

        @Override
        public void sessionCreated(HttpSessionEvent se) {
            // 通过事件源得到HttpSession对象
            HttpSession session = se.getSession();

            // 得到application对象(ServletContext对象)
            ServletContext application = session.getServletContext();

            // 得到session对象,并放入到list集合中
            List<HttpSession> list = (List<HttpSession>) application.getAttribute("sessions");

            list.add(session);

            System.out.println("添加了" + session.getId());
        }

        @Override
        public void sessionDestroyed(HttpSessionEvent se) {

        }
    }

    配置注册监听代码:

        <listener>
            <listener-class>com.itheima.test.MyServletContextListener</listener-class>
        </listener>
        <listener>
            <listener-class>com.itheima.test.MySessionListener</listener-class>
        </listener>

    二、Filter过滤器(重要)

    Javaweb中的过滤器可以拦截所有访问web资源的请求响应操作。

    1、Filter快速入门

    步骤:
      1、创建一个类实现Filter接口。
      2、重写接口中方法,其中doFilter方法是真正过滤用的。
      3、在web.xml文件中进行配置。
    注意:在实现Filter接口的类中重写的doFilter方法内如果没有执行这句代码chain.doFilter(request, response);那么浏览器访问服务器的资源是不会被访问到的。
    示例代码如下:

    public class MyFilter1 implements Filter{

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain)
     throws IOException, ServletException 
    {
            System.out.println("MyFilter1拦截开始了"); // 对浏览器发送的请求进行过滤

            // 放行
            chain.doFilter(request, response);

            System.out.println("MyFilter1拦截结束了"); // 对服务器返回的响应进行过滤
        }

        @Override
        public void destroy() {

        }

    }

    在web.xml文件中进行配置。

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">

      <display-name>day17_01_filter</display-name>

      <filter>
          <filter-name>MyFilter1</filter-name>
          <filter-class>com.itheima.filter.MyFilter1</filter-class>
      </filter>

      <filter-mapping>
          <filter-name>MyFilter1</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>


      <servlet>
        <servlet-name>ServletDemo1</servlet-name>
        <servlet-class>com.itheima.servlet.ServletDemo1</servlet-class>
      </servlet>

      <servlet-mapping>
        <servlet-name>ServletDemo1</servlet-name>
        <url-pattern>/servlet/demo1</url-pattern>
      </servlet-mapping>

      <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
      </welcome-file-list>

    </web-app>

    测试代码
      测试代码一:http://localhost:8080/day17_01_filter/index.jsp
      测试代码二:http://localhost:8080/day17_01_filter/servlet/demo1

    public class ServletDemo1 extends HttpServlet {

        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            System.out.println("ServletDemo1");
        }

        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            doGet(request, response);
        }
    }

    2、FilterChain过滤器链

      FilterChain 是 servlet 容器(即服务器)为开发人员提供的对象,它提供了对某一资源的已过滤请求调用链的视图。
      过滤器使用 FilterChain 调用链中的下一个过滤器(由我们配置的web.xml的先后顺序决定),如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源。
      问题:怎样可以形成一个Filter链?
        答:只要多个Filter对同一个资源进行拦截就可以形成Filter链。
      问题:怎样确定Filter的执行顺序?
        答:由web.xml中的<filter-mapping>的先后顺序来确定。
      示例代码如下图所示:

    3、Filter的生命周期

    Servlet的生命周期
      实例化 --> 初始化 --> 服务 --> 销毁
    第一次访问服务器资源的时候,Servlet就进行实例化和初始化,只调用一次。(二者不同的地方)
      只要应用在(即应用没有卸载),service服务就一直在。
      只要应用卸载了或者服务器停止了,Servlet就销毁了。

    Filter的生命周期
    服务器启动时,会创建Filter对象(即调用构造方法进行实例化),并调用init方法,只调用一次。(二者不同的地方)
      当浏览器访问服务器资源时,路径与Filter的拦截路径匹配后,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法。
      当服务器关闭或应用卸载时,会调用Filter中的destroy方法来进行销毁操作。

    4、FilterConfig过滤器参数

      在Filter的init方法上有一个参数,类型就是FilterConfig。
      FilterConfig它是Filter的配置对象,它可以完成下列功能:
        1、获取Filter名称。
        2、获取Filter初始化参数。
        3、获取ServletContext对象。
    如下图所示:


      问题:怎样在Filter中获取一个FIlterConfig对象呢?
    答:如下图所示:

    如下图所示:

    5、Filter的配置文件书写格式

    基本配置

        <filter>
            <filter-name>Filter名称</filter-name>
            <filter-class>Filter类的包名.类名</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>Filter名称</filter-name>
            <url-pattern>路径</url-pattern>
        </filter-mapping>

    代码如下:

    关于其它配置
    配置1:

    1.  <url-pattern>
            完全匹配        以"/demo1"开始,不包含通配符*。
            目录匹配        以”/”开始 ,以*结束。
            扩展名匹配      *.xxx  不能写成 /*.xxx。
        </url-pattern>

    配置2:

    2.  <servlet-name>
            它是对指定的servlet名称的servlet进行拦截的。
        </servlet-name>

    截图如下:


    配置3:
    3.  <dispatcher>
            可以取的值有  REQUEST  FORWARD  ERROR  INCLUDE    
            它的作用是:当以什么方式去访问web资源时,进行拦截操作。

            1.REQUEST 当从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置,它也是默认值。
            2.FORWARD 它描述的是请求转发的拦截方式配置。
            3.ERROR 如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。(了解即可)
            4.INCLUDE 如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。跟FORWORD差不多。(了解即可)
        </dispatcher>

    截图如下:


    代码如下:

    三、自动登陆

    1、创建数据库与数据表

    CREATE DATABASE day17;
    USE day17;

    CREATE TABLE USER(
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(100),
        PASSWORD VARCHAR(100)
    )

    INSERT INTO USER VALUES(NULL"tom""123");

    2、自动登陆功能实现步骤
      1、当用户登陆成功后,判断是否勾选了自动登陆,如果勾选了,就将用户名与密码持久化存储到cookie中。
      2、做一个Filter,对需要自动登陆的资源进行拦截。
    自动登录思路图:

    四、MD5加密

    在mysql中可以对数据进行md5加密,是不可逆(单向)加密。
       Md5(字段)
       UPDATE USER SET PASSWORD=MD5(PASSWORD) WHERE id=1;
      在java中也提供了md5加密。md5工具类(MD5Utils类)
    示例代码如下:

    public class MD5Utils {
        /**
         * 使用md5的算法给某数据进行加密
         */

        public static String md5(String plainText{
            byte[] secretBytes = null;
            try {
                secretBytes = MessageDigest.getInstance("md5").digest(
                        plainText.getBytes());
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("没有md5这个算法!");
            }
            String md5code = new BigInteger(1, secretBytes).toString(16);
            for (int i = 0; i < 32 - md5code.length(); i++) {
                md5code = "0" + md5code;
            }
            return md5code;
        }
    }

    五、全局的编码过滤器

    分析
      我们之前做的过滤操作,只能对post请求是ok的,get请求就不行了。


    怎样可以做成一个通用的,可以处理post、get所有的请求的?
    在java中怎样可以对一个方法进行功能增强
    法1. 继承该方法所在的类
      该方法不好,因为不想要的东西我也得继承。
    法2. 装饰设计模式
      1)、创建一个装饰类让它与被装饰类实现同一个接口或继承同一个父类
      2)、在装饰类中有一个被装饰类的引用(即:定义一个与被包装类相同对象的引用(字段))
      3)、定义一个构造方法,把被包装的对象传过来
      4)、重写要增强的方法(即:对于不需要改写方法,直接调用;对于需要改写方法,写自己的方法体)

    问题我们获取请求参数有以下方法:
      1、getParameter
      2、getPrameterValues
      3、getParameterMap
        这三个方法都可以获取请求参数。
    分析后,我们知道 getParametergetParameterValues 方法可以依赖于 getParamterMap 方法来实现。
    login.jsp

        <form action="${pageContext.request.contextPath }/servlet/loginServlet" method="post">
            username:<input type="text" name="username"><br/>
            username1:<input type="text" name="username1"><br/>
            <input type="submit" value="登录">
        </form>

    LoginServlet.java

    public class LoginServlet extends HttpServlet {

        @Override
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException 
    {
            // request.setCharacterEncoding("UTF-8"); // 每一个servlet都写这句代码,不好

            String name = request.getParameterValues("username")[0];
            String name1 = request.getParameter("username1");

            System.out.println(name);   
            System.out.println(name1);
        }

        @Override
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException 
    {
            doGet(request, response);
        }
    }

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
      <display-name>day17_03_Globalfilter</display-name>

      <filter>
          <filter-name>MyFilter</filter-name>
          <filter-class>com.itheima.filter.MyFilter</filter-class>
      </filter>

      <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/*</url-pattern>  
      </filter-mapping>


      <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>com.itheima.servlet.LoginServlet</servlet-class>
      </servlet>

      <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/servlet/loginServlet</url-pattern>
      </servlet-mapping>

      <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
      </welcome-file-list>

    </web-app>

    MyFilter.java

    public class MyFilter implements Filter {

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {

            HttpServletRequest req = (HttpServletRequest) request;
            // HttpServletResponse resp = (HttpServletResponse) response;

            // req.setCharacterEncoding("UTF-8"); // 只能解决post的提交方式
            // resp.setContentType("text/html; charset=utf-8");

            req = new MyRequest(req); // 装饰一把(把处理乱码的动作放在装饰类代码里)

            chain.doFilter(req, response);
        }

        @Override
        public void destroy() {

        }
    }

    // 装饰设计模式 + 适配器
    // 实现与被包装对象相同的接口
    // 定义一个与被包装类相同对象的引用(字段)
    // 定义一个构造方法,把被包装的对象传过来
    // 对于不需要改写方法,直接调用
    // 对于需要改写方法,写自己的方法体
    class MyRequest extends HttpServletRequestWrapper {
        HttpServletRequest request;

        public MyRequest(HttpServletRequest request) {
            super(request); // 是因为父类没有无参数构造
            this.request = request;
        }

        // 一个一个的乱码处理,不好
    //    @Override public String getParameter(String name) { 
    //        name = request.getParameter(name); // 可能会出现乱码 
    //        try { 
    //            return new String(name.getBytes("iso-8859-1"), "UTF-8"); // 处理乱码
    //        } catch (UnsupportedEncodingException e) {
    //            e.printStackTrace(); 
    //        } 
    //        return null; 
    //        
    //    }

        @Override
        public String getParameter(String name) {
            Map<StringString[]> map = getParameterMap();
            return map.get(name)[0];
        }

        @Override
        public String[] getParameterValues(String name) {
            Map<StringString[]> map = getParameterMap();
            return map.get(name);
        }

        private boolean flag = true;
        @Override
        public Map<StringString[]> getParameterMap() {
            // 得到原始的map集合
            Map<StringString[]> map = request.getParameterMap(); // 可能会出现乱码

            if (flag) { // 处理乱码
                // 将map集合中的String[]得到,解决每一个元素的乱码问题
                for (Map.Entry<StringString[]> m : map.entrySet()) {
                    String[] values = m.getValue();
                    for (int i = 0; i < values.length; i++) {
                        try {
                            values[i] = new String(values[i].getBytes("iso-8859-1"), "UTF-8");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                }
                flag = false;
            }

            return map;
        }
    }
  • 相关阅读:
    python之read()方法
    python之高阶函数
    python之lambda表达式的应用
    DevExpress.XtraGrid.view.gridview 说明文
    C# SQL时间格式
    GridControl自动定位至符合条件的行
    用sql命令修改数据表
    用C#编程从数据库中读取图片数据导进Excel文件的方法(如何从数据库中读取保存的文件,直接打开,中间不保存到本地)
    DevExpress中GridControl的属性设置及动态绑定数据和全选取消全选
    C# 导出数据到Excel模板中
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/9119641.html
Copyright © 2020-2023  润新知