尚学堂JAVA高级学习笔记

尚学堂JAVA高级学习笔记

目录

写在前面

学习链接:Java 视频教程全集

课件链接:Java课件

声明:全是本人边学习边手打的,希望对大家有帮助。

第1章 手写webserver

1. 灵魂反射

  • 反射Reflection:把java类中的各种结构(方法、属性、构造器、类名)映射成一个个的java对象。利用反射技术可以对一个类进行解剖,反射是框架设计的灵魂。

  • 获取Class对象(三种方式):推荐使用Class.forName(“完整路径”)

  • 可以动态创建对象:clz.getConstructor().newInstance()

  • 练习

    package com.sxt.Server_study01;
    
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ReflectTest.java
     * @time: 2019/11/29 14:55
     * @desc:
     */
    
    public class ReflectTest {
        public static void main(String[] args){
            // 1. 对象.getClass() | 买iphone照着做
            Iphone iphone = new Iphone();
            Class clz = iphone.getClass();
            // 2. 类.class()     | 买通工程师给图纸
            clz = Iphone.class;
            // 3. Class.forName("包名.类名")   | 偷图纸
            try {
                clz = Class.forName("com.sxt.Server_study01.Iphone");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            // 创建对象
            try {
                Iphone iphone2 = (Iphone)clz.newInstance();
                System.out.println(iphone2);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            // 创建对象推荐方式
            try {
                Iphone iphone3 = (Iphone)clz.getConstructor().newInstance();
                System.out.println(iphone3);
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }
    
    class Iphone{
        public Iphone(){
        }
    }
    

2. 高效解析xml

  • XML:Extensible Markup Language,可扩展标记语言,作为数据的一种存储格式或用于存储软件的参数,程序解析此配置文件,就可以达到不修改代码就能更改程序的目的。

  • 利用Sax解析XML

  • xml例子

    <?xml version="1.0" encoding="UTF-8"?>
    <persons>
        <person>
            <name>至尊宝</name>
            <age>9000</age>
        </person>
        <person>
            <name>白晶晶</name>
            <age>7000</age>
        </person>
    </persons>
    
  • 制作Person类

    package com.sxt.Server_study01;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Person.java
     * @time: 2019/11/29 15:29
     * @desc:
     */
    
    public class Person {
        private String name;
        private int age;
    
        public Person() {
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = 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;
        }
    }
    
  • 熟悉sax解析流程

    package com.sxt.Server_study01;
    
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;
    import org.xml.sax.helpers.DefaultHandler;
    
    import javax.xml.parsers.ParserConfigurationException;
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    import java.io.IOException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: XmlTest01.java
     * @time: 2019/11/29 15:10
     * @desc: 熟悉sax解析流程
     */
    
    public class XmlTest01 {
        public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
            // SAX解析
            // 1. 获取解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // 2. 从解析工厂获取解析器
            SAXParser parse = null;
            parse = factory.newSAXParser();
            // 3. 加载文档Document注册处理器
            // 4. 编写处理器
            PHandler handler = new PHandler();
            parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/Server_study01/p.xml"), handler);
        }
    }
    
    class PHandler extends DefaultHandler {
        @Override
        public void startDocument() throws SAXException {
            System.out.println("解析文档开始");
        }
    
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            System.out.println(qName + "-->解析开始");
        }
    
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String contents = new String(ch, start, length).trim();
            if(contents.length()>0) {
                System.out.println("内容为:" + contents);
            }else{
                System.out.println("内容为空!");
            }
        }
    
        @Override
        public void endDocument() throws SAXException {
            System.out.println("解析文档结束");
        }
    
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            System.out.println(qName + "-->解析结束");
        }
    }
    
  • 获取xml中的内容

    package com.sxt.Server_study01;
    
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;
    import org.xml.sax.helpers.DefaultHandler;
    
    import javax.xml.parsers.ParserConfigurationException;
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: XmlTest02.java
     * @time: 2019/11/29 15:31
     * @desc: 获取xml中的内容
     */
    
    public class XmlTest02 {
        public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
            // SAX解析
            // 1. 获取解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // 2. 从解析工厂获取解析器
            SAXParser parse = null;
            parse = factory.newSAXParser();
            // 3. 加载文档Document注册处理器
            // 4. 编写处理器
            PersonHandler handler = new PersonHandler();
            parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/Server_study01/p.xml"), handler);
            // 5. 获取数据
            List<Person> persons = handler.getPersons();
            for (Person p : persons) {
                System.out.println(p.getName() + "-->" + p.getAge());
            }
        }
    }
    
    class PersonHandler extends DefaultHandler {
        private List<Person> persons;
        private Person person;
        private String tag;     // 存储操作的标签
    
        @Override
        public void startDocument() throws SAXException {
            System.out.println("解析文档开始");
            persons = new ArrayList<>();
        }
    
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (null != qName) {
                tag = qName;        // 存储标签名
                if (tag.equals("person")) {
                    person = new Person();
                }
            }
        }
    
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String contents = new String(ch, start, length).trim();
            if (contents.length() > 0) {
                if (tag.equals("name")) {
                    person.setName(contents);
                } else if (tag.equals("age")) {
                    person.setAge(Integer.valueOf(contents));
                }
            }
        }
    
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals("person")) {
                persons.add(person);
            }
        }
    
        @Override
        public void endDocument() throws SAXException {
            System.out.println("解析文档结束");
        }
    
        public List<Person> getPersons() {
            return persons;
        }
    }
    

3. 解析webxml

  • webxml例子

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app>
        <servlet>
            <servlet-name>login</servlet-name>
            <servlet-class>com.sxt.server.basic.servlet.LoginServlet</servlet-class>
        </servlet>
        <servlet>
            <servlet-name>reg</servlet-name>
            <servlet-class>com.sxt.server.basic.servlet.RegisterServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>login</servlet-name>
            <url-pattern>/login</url-pattern>
            <url-pattern>/g</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
            <servlet-name>reg</servlet-name>
            <url-pattern>/reg</url-pattern>
        </servlet-mapping>
    </web-app>
    
  • 解析webxml

    package com.sxt.Server_study01.servlet;
    
    import com.sxt.Server_study01.Person;
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;
    import org.xml.sax.helpers.DefaultHandler;
    
    import javax.xml.parsers.ParserConfigurationException;
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: XmlTest02.java
     * @time: 2019/11/29 15:31
     * @desc: 解析Webxml
     */
    
    public class XmlTest02 {
        public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
            // SAX解析
            // 1. 获取解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // 2. 从解析工厂获取解析器
            SAXParser parse = null;
            parse = factory.newSAXParser();
            // 3. 加载文档Document注册处理器
            // 4. 编写处理器
            WebHandler handler = new WebHandler();
            parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/Server_study01/servlet/web.xml"), handler);
            // 5. 获取数据
            List<Entity> entitys = handler.getEntitys();
            List<Mapping> mappings = handler.getMappings();
            System.out.println(entitys.size());
            System.out.println(mappings.size());
        }
    }
    
    class WebHandler extends DefaultHandler {
        private List<Entity> entitys;
        private List<Mapping> mappings;
        private Entity entity;
        private Mapping mapping;
        private String tag;
        private boolean isMapping = false;
    
        public List<Entity> getEntitys() {
            return entitys;
        }
    
        public void setEntitys(List<Entity> entitys) {
            this.entitys = entitys;
        }
    
        public List<Mapping> getMappings() {
            return mappings;
        }
    
        public void setMappings(List<Mapping> mappings) {
            this.mappings = mappings;
        }
    
        @Override
        public void startDocument() throws SAXException {
            entitys = new ArrayList<>();
            mappings = new ArrayList<>();
        }
    
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (null != qName) {
                tag = qName;        // 存储标签名
                if (tag.equals("servlet")) {
                    entity = new Entity();
                    isMapping = false;
                } else if (tag.equals("servlet-mapping")) {
                    mapping = new Mapping();
                    isMapping = true;
                }
            }
        }
    
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String contents = new String(ch, start, length).trim();
            if (contents.length() > 0) {
                if (isMapping) {// 操作servlet-mapping
                    if (tag.equals("servlet-name")) {
                        mapping.setName(contents);
                    } else if (tag.equals("url-pattern")) {
                        mapping.addPattern(contents);
                    }
                } else {// 操作servlet
                    if (tag.equals("servlet-name")) {
                        entity.setName(contents);
                    } else if (tag.equals("servlet-class")) {
                        entity.setClz(contents);
                    }
                }
            }
        }
    
        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals("servlet")) {
                entitys.add(entity);
            } else if (qName.equals("servlet-mapping")) {
                mappings.add(mapping);
            }
        }
    }
    

4. 反射webxml

  • xml样例

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app>
        <servlet>
            <servlet-name>login</servlet-name>
            <servlet-class>com.sxt.Server_study01.servlet.LoginServlet</servlet-class>
        </servlet>
        <servlet>
            <servlet-name>reg</servlet-name>
            <servlet-class>com.sxt.Server_study01.servlet.RegisterServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>login</servlet-name>
            <url-pattern>/login</url-pattern>
            <url-pattern>/g</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
            <servlet-name>reg</servlet-name>
            <url-pattern>/reg</url-pattern>
        </servlet-mapping>
    </web-app>
    
  • 解析xml

    • Entity类

      package com.sxt.Server_study01.servlet;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Entity.java
       * @time: 2019/12/2 13:20
       * @desc:
       */
      
      public class Entity {
          private String name;
          private String clz;
      
          public Entity() {
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getClz() {
              return clz;
          }
      
          public void setClz(String clz) {
              this.clz = clz;
          }
      }
      
    • Mapping类

      package com.sxt.Server_study01.servlet;
      
      import java.util.HashSet;
      import java.util.Set;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Mapping.java
       * @time: 2019/12/2 13:21
       * @desc:
       */
      
      public class Mapping {
          private String name;
          private Set<String> patterns;
      
          public Mapping() {
              patterns = new HashSet<>();
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Set<String> getPatterns() {
              return patterns;
          }
      
          public void setPatterns(Set<String> patterns) {
              this.patterns = patterns;
          }
      
          public void addPattern(String pattern){
              this.patterns.add(pattern);
          }
      }
      
    • 通过URL的路径找到了对应的class

      package com.sxt.Server_study01.servlet;
      
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: WebContext.java
       * @time: 2019/12/3 10:51
       * @desc:
       */
      
      public class WebContext {
          private List<Entity> entitys = null;
          private List<Mapping> mappings = null;
      
          // key-->servlet-name  value-->servlet-class
          private Map<String, String> mappingMap = new HashMap<>();
          // key-->url-pattern  value-->servlet-name
          private Map<String, String> entityMap = new HashMap<>();
      
          public WebContext(List<Entity> entitys, List<Mapping> mappings) {
              this.entitys = entitys;
              this.mappings = mappings;
      
              // 将entity的List转成了对应的map
              for(Entity entity: entitys){
                  entityMap.put(entity.getName(), entity.getClz());
              }
              // 将map的List转成了对应的map
              for(Mapping mapping: mappings){
                  for(String pattern: mapping.getPatterns()){
                      mappingMap.put(pattern, mapping.getName());
                  }
              }
          }
      
          // 通过URL的路径找到了对应的class
          public String getClz(String pattern) {
              String name = mappingMap.get(pattern);
              return entityMap.get(name);
          }
      }
      
    • 核心代码,取出数据

      package com.sxt.Server_study01.servlet;
      
      import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
      import org.xml.sax.Attributes;
      import org.xml.sax.SAXException;
      import org.xml.sax.helpers.DefaultHandler;
      
      import javax.xml.parsers.ParserConfigurationException;
      import javax.xml.parsers.SAXParser;
      import javax.xml.parsers.SAXParserFactory;
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: XmlTest02.java
       * @time: 2019/11/29 15:31
       * @desc: 解析Webxml
       */
      
      public class XmlTest02 {
          public static void main(String[] args) throws Exception {
              // SAX解析
              // 1. 获取解析工厂
              SAXParserFactory factory = SAXParserFactory.newInstance();
              // 2. 从解析工厂获取解析器
              SAXParser parse = null;
              parse = factory.newSAXParser();
              // 3. 加载文档Document注册处理器
              // 4. 编写处理器
              WebHandler handler = new WebHandler();
              parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/Server_study01/servlet/web.xml"), handler);
              // 5. 获取数据
              WebContext context = new WebContext(handler.getEntitys(), handler.getMappings());
              // 假设你输入了 /login or /g
              String className = context.getClz("/g");
              Class clz = Class.forName(className);
              Servlet servlet = (Servlet)clz.getConstructor().newInstance();
              System.out.println(servlet);
              servlet.service();
          }
      }
      
      class WebHandler extends DefaultHandler {
          private List<Entity> entitys;
          private List<Mapping> mappings;
          private Entity entity;
          private Mapping mapping;
          private String tag;
          private boolean isMapping = false;
      
          public List<Entity> getEntitys() {
              return entitys;
          }
      
          public void setEntitys(List<Entity> entitys) {
              this.entitys = entitys;
          }
      
          public List<Mapping> getMappings() {
              return mappings;
          }
      
          public void setMappings(List<Mapping> mappings) {
              this.mappings = mappings;
          }
      
          @Override
          public void startDocument() throws SAXException {
              entitys = new ArrayList<>();
              mappings = new ArrayList<>();
          }
      
          @Override
          public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
              if (null != qName) {
                  tag = qName;        // 存储标签名
                  if (tag.equals("servlet")) {
                      entity = new Entity();
                      isMapping = false;
                  } else if (tag.equals("servlet-mapping")) {
                      mapping = new Mapping();
                      isMapping = true;
                  }
              }
          }
      
          @Override
          public void characters(char[] ch, int start, int length) throws SAXException {
              String contents = new String(ch, start, length).trim();
              if (contents.length() > 0) {
                  if (isMapping) {// 操作servlet-mapping
                      if (tag.equals("servlet-name")) {
                          mapping.setName(contents);
                      } else if (tag.equals("url-pattern")) {
                          mapping.addPattern(contents);
                      }
                  } else {// 操作servlet
                      if (tag.equals("servlet-name")) {
                          entity.setName(contents);
                      } else if (tag.equals("servlet-class")) {
                          entity.setClz(contents);
                      }
                  }
              }
          }
      
          @Override
          public void endElement(String uri, String localName, String qName) throws SAXException {
              if (qName.equals("servlet")) {
                  entitys.add(entity);
              } else if (qName.equals("servlet-mapping")) {
                  mappings.add(mapping);
              }
          }
      }
      
    • Servlet接口

      package com.sxt.Server_study01.servlet;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Servlet.java
       * @time: 2019/12/3 15:59
       * @desc:
       */
      
      public interface Servlet {
          void service();
      }
      
    • 需要被映射的类:Loginservlet

      package com.sxt.Server_study01.servlet;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginServlet.java
       * @time: 2019/12/3 15:59
       * @desc:
       */
      
      public class LoginServlet implements Servlet {
          @Override
          public void service() {
              System.out.println("LoginServlet");
          }
      }
      
    • 需要被映射的类:RegisterServlet

      package com.sxt.Server_study01.servlet;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: RegisterServlet.java
       * @time: 2019/12/3 15:59
       * @desc:
       */
      
      public class RegisterServlet implements Servlet{
          @Override
          public void service() {
              System.out.println("RegisterServlet");
          }
      }
      

5. 简单易学的html

  • HyperText Markup Language ,超文本标记语言,简单理解为浏览器使用的语言。

  • name是后端用的,id是前端用的

    <html>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <head>
            <title>第一个html登陆</title>
        </head>
        <body>
            <h1>表单的使用</h1>
            <pre>
                post: 提交,基于http协议不同  量大  请求参数url不可见  安全<br/>
                get: 默认,获取,基于http协议不同  量小  请求参数url可见  不安全<br/>
                action: 请求web服务器的资源 URL<br/>
                name: 作为后端使用,区分唯一,请求服务器,必须存在,数据不能提交<br/>
                id: 作为前端使用,区分唯一<br/>
            </pre>
            <form method="post" action="http://localhost:8888/index.html">
                用户名: <input type="text" name="uname" id="uname"/>
                密码: <input type="password" name="pwd" id="pwd"/>
                <input type="submit" value="登陆"/>
            </form>
        </body>
    </html>
    

6. 不得不提的http协议

  • http请求协议

    img

  • http响应协议

    img

7. 获取请求协议

  • 使用ServerSocket建立与浏览器的连接,获取请求协议

    package com.sxt.Server_study02;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server01.java
     * @time: 2019/12/4 9:26
     * @desc: 使用ServerSocket建立与浏览器的连接,获取请求协议
     */
    
    public class Server01 {
        private ServerSocket serverSocket;
        public static void main(String[] args) {
            Server01 server = new Server01();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
            }
        }
    
        // 接受连接处理
        public void receive() {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接...");
                // 获取请求协议
                InputStream is = client.getInputStream();
                byte[] datas = new byte[1024*1024];
                int len = is.read(datas);
                String requstInfo = new String(datas, 0, len);
                System.out.println(requstInfo);
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误...");
            }
        }
    
        // 停止服务
        public void stop() {
    
        }
    }
    
  • 两种请求方法get和post:GET和POST两种基本请求方法的区别GET 和 POST 到底有什么区别?

8. 返回响应协议

package com.sxt.Server_study02;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

/**
 * @author: Li Tian
 * @contact: litian_cup@163.com
 * @software: IntelliJ IDEA
 * @file: Server02.java
 * @time: 2019/12/4 9:26
 * @desc: 返回响应协议
 */

public class Server02 {
    private ServerSocket serverSocket;
    public static void main(String[] args) {
        Server02 server = new Server02();
        server.start();
    }

    // 启动服务
    public void start() {
        try {
            serverSocket = new ServerSocket(8888);
            receive();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败...");
        }
    }

    // 接受连接处理
    public void receive() {
        try {
            Socket client = serverSocket.accept();
            System.out.println("一个客户端建立了连接...");
            // 获取请求协议
            InputStream is = client.getInputStream();
            byte[] datas = new byte[1024*1024];
            int len = is.read(datas);
            String requstInfo = new String(datas, 0, len);
            System.out.println(requstInfo);

            StringBuilder content = new StringBuilder();
            content.append("<html>");
            content.append("<head>");
            content.append("<title>");
            content.append("服务器响应成功");
            content.append("</title>");
            content.append("</head>");
            content.append("<body>");
            content.append("终于回来了...");
            content.append("</body>");
            content.append("</html>");
            int size = content.toString().getBytes().length;
            StringBuilder responseInfo = new StringBuilder();
            String blank = " ";
            String CRLF = "\r\n";
            // 返回
            // 1. 响应行:HTTP/1.1 200 OK
            responseInfo.append("HTTP/1.1").append(blank);
            responseInfo.append(200).append(blank);
            responseInfo.append("OK").append(CRLF);
            // 2. 响应头(最后一行存在空行):
            responseInfo.append("Date:").append(new Date()).append(CRLF);
            responseInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
            responseInfo.append("Content-type:text/html").append(CRLF);
            responseInfo.append("Content-length:").append(size).append(CRLF);
            responseInfo.append(CRLF);
            // 3. 正文
            responseInfo.append(content.toString());
            // 写出到客户端
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
            bw.write(responseInfo.toString());
            bw.flush();
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("客户端错误...");
        }
    }

    // 停止服务
    public void stop() {

    }
}

9. 封装响应信息

  1. 动态添加内容print

  2. 累加字节数的长度

  3. 根据状态码拼接响应头协议

  4. 根据状态码统一推送出去

    package com.sxt.Server_study02;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server03.java
     * @time: 2019/12/4 9:26
     * @desc: 封装响应信息
     */
    
    public class Server03 {
        private ServerSocket serverSocket;
        public static void main(String[] args) {
            Server03 server = new Server03();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
            }
        }
    
        // 接受连接处理
        public void receive() {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接...");
                // 获取请求协议
                InputStream is = client.getInputStream();
                byte[] datas = new byte[1024*1024];
                int len = is.read(datas);
                String requstInfo = new String(datas, 0, len);
                System.out.println(requstInfo);
    
                Response response = new Response(client);
    
                // 关注了内容
                response.print("<html>");
                response.print("<head>");
                response.print("<title>");
                response.print("服务器响应成功");
                response.print("</title>");
                response.print("</head>");
                response.print("<body>");
                response.print("终于回来了...");
                response.print("</body>");
                response.print("</html>");
    
                // 关注了状态码
                response.pushToBrowser(200);
    
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误...");
            }
        }
    
        // 停止服务
        public void stop() {
        }
    }
    
    package com.sxt.Server_study02;
    
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    import java.util.Date;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Response.java
     * @time: 2019/12/5 9:17
     * @desc:
     */
    
    public class Response {
        private BufferedWriter bw;
        // 正文
        private StringBuilder content;
        // 协议头信息:状态行与请求头、回车
        private StringBuilder headInfo;
        // 正文的字节数
        private int len;
    
        private final String BLANK = " ";
        private final String CRLF = "\r\n";
    
        public Response() {
            content = new StringBuilder();
            headInfo = new StringBuilder();
            len = 0;
        }
    
        public Response(Socket client) {
            this();
            try {
                bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
            } catch (IOException e) {
                e.printStackTrace();
                headInfo = null;
            }
        }
    
        public Response(OutputStream os) {
            this();
            bw = new BufferedWriter(new OutputStreamWriter(os));
        }
        // 动态添加内容
        public Response print(String info){
            content.append(info);
            len += info.getBytes().length;
            return this;
        }
        public Response println(String info){
            content.append(info).append(CRLF);
            len += (info + CRLF).getBytes().length;
            return this;
        }
    
        // 推送响应信息
        public void pushToBrowser(int code) throws IOException {
            if (null == headInfo){
                code = 505;
            }
            createHeadInfo(code);
            bw.append(headInfo);
            bw.append(content);
            bw.flush();
        }
    
        // 构建头信息
        private void createHeadInfo(int code) {
            // 1. 响应行:HTTP/1.1 200 OK
            headInfo.append("HTTP/1.1").append(BLANK);
            headInfo.append(code).append(BLANK);
            switch (code) {
                case 200:
                    headInfo.append("OK").append(CRLF);
                    break;
                case 404:
                    headInfo.append("NOT FOUND").append(CRLF);
                    break;
                case 505:
                    headInfo.append("SERVER ERROR").append(CRLF);
                    break;
            }
    
            // 2. 响应头(最后一行存在空行):
            headInfo.append("Date:").append(new Date()).append(CRLF);
            headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
            headInfo.append("Content-type:text/html").append(CRLF);
            headInfo.append("Content-length:").append(len).append(CRLF);
            headInfo.append(CRLF);
        }
    }
    

10. 封装请求信息

  • 通过分解字符串获取method、URL和请求参数

  • POST请求参数可能在请求体中存在

  • 分解协议

    package com.sxt.Server_study02;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Request.java
     * @time: 2019/12/5 10:15
     * @desc:
     */
    
    public class Request {
        private String requestInfo;
        // 请求方式
        private String method;
        // 请求url
        private String url;
        // 请求参数
        private String queryStr;
    
        private final String BLANK = " ";
        private final String CRLF = "\r\n";
    
        public Request(Socket client) throws IOException {
            this(client.getInputStream());
        }
    
        public Request(InputStream is) {
            byte[] datas = new byte[1024 * 1024];
            int len;
            try {
                len = is.read(datas);
                this.requestInfo = new String(datas, 0, len);
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            // 分解字符串
            parseRequestInfo();
        }
    
        private void parseRequestInfo() {
            System.out.println("---分解---");
            System.out.println("1. 获取请求方式:开头到第一个/");
            this.method = this.requestInfo.substring(0, this.requestInfo.indexOf("/")).toLowerCase().trim();
            System.out.println("2. 获取请求url:第一个/ 到HTTP/");
            System.out.println("可能包含请求参数?前面的url");
            // 1. 获取/的位置
            int startIdx = this.requestInfo.indexOf("/") + 1;
            // 2. 获取HTTP/的位置
            int endIdx = this.requestInfo.indexOf("HTTP/");
            // 3. 分割字符串
            this.url = this.requestInfo.substring(startIdx, endIdx);
            // 4. 获取?的位置
            int queryIdx = this.url.indexOf("?");
            if (queryIdx >= 0) {
                // 表示存在请求参数
                String[] urlArray = this.url.split("\\?");
                this.url = urlArray[0].trim();
                queryStr = urlArray[1].trim();
            }
            System.out.println(this.url);
    
            System.out.println("3. 获取请求参数:若果Get已经获取,如果post可能在请求体中");
            if (method.equals("post")) {
                String qStr = this.requestInfo.substring(this.requestInfo.lastIndexOf(CRLF)).trim();
                if (null == queryStr) {
                    queryStr = qStr;
                } else {
                    queryStr += "&" + qStr;
                }
            }
            queryStr = null == queryStr?"": queryStr;
            System.out.println(method + "-->" + url + "-->" + queryStr);
        }
    }
    
    package com.sxt.Server_study02;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server04.java
     * @time: 2019/12/4 9:26
     * @desc: 封装请求协议:获取method uri以及请求参数
     */
    
    public class Server04 {
        private ServerSocket serverSocket;
        public static void main(String[] args) {
            Server04 server = new Server04();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
            }
        }
    
        // 接受连接处理
        public void receive() {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接...");
                // 获取请求协议
                Request request = new Request(client);
    
                Response response = new Response(client);
    
                // 关注了内容
                response.print("<html>");
                response.print("<head>");
                response.print("<title>");
                response.print("服务器响应成功");
                response.print("</title>");
                response.print("</head>");
                response.print("<body>");
                response.print("终于回来了...");
                response.print("</body>");
                response.print("</html>");
    
                // 关注了状态码
                response.pushToBrowser(200);
    
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误...");
            }
        }
    
        // 停止服务
        public void stop() {
    
        }
    }
    
    
  • 分解参数

    package com.sxt.Server_study02;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.Socket;
    import java.util.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Request2.java
     * @time: 2019/12/5 10:15
     * @desc: 封装请求协议:封装请求参数为Map
     */
    
    public class Request2 {
        private String requestInfo;
        // 请求方式
        private String method;
        // 请求url
        private String url;
        // 请求参数
        private String queryStr;
        // 存储参数
        private Map<String, List<String>> parameterMap;
    
        private final String BLANK = " ";
        private final String CRLF = "\r\n";
    
        public Request2(Socket client) throws IOException {
            this(client.getInputStream());
        }
    
        public String getMethod() {
            return method;
        }
    
        public String getUrl() {
            return url;
        }
    
        public String getQueryStr() {
            return queryStr;
        }
    
        public Request2(InputStream is) {
            parameterMap = new HashMap<>();
            byte[] datas = new byte[1024 * 1024];
            int len;
            try {
                len = is.read(datas);
                this.requestInfo = new String(datas, 0, len);
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
            // 分解字符串
            parseRequestInfo();
        }
    
        private void parseRequestInfo() {
            System.out.println("---分解---");
            System.out.println("1. 获取请求方式:开头到第一个/");
            this.method = this.requestInfo.substring(0, this.requestInfo.indexOf("/")).toLowerCase().trim();
            System.out.println("2. 获取请求url:第一个/ 到HTTP/");
            System.out.println("可能包含请求参数?前面的url");
            // 1. 获取/的位置
            int startIdx = this.requestInfo.indexOf("/") + 1;
            // 2. 获取HTTP/的位置
            int endIdx = this.requestInfo.indexOf("HTTP/");
            // 3. 分割字符串
            this.url = this.requestInfo.substring(startIdx, endIdx);
            // 4. 获取?的位置
            int queryIdx = this.url.indexOf("?");
            if (queryIdx >= 0) {
                // 表示存在请求参数
                String[] urlArray = this.url.split("\\?");
                this.url = urlArray[0].trim();
                queryStr = urlArray[1].trim();
            }
            System.out.println(this.url);
    
            System.out.println("3. 获取请求参数:若果Get已经获取,如果post可能在请求体中");
            if (method.equals("post")) {
                String qStr = this.requestInfo.substring(this.requestInfo.lastIndexOf(CRLF)).trim();
                if (null == queryStr) {
                    queryStr = qStr;
                } else {
                    queryStr += "&" + qStr;
                }
            }
            queryStr = null == queryStr?"": queryStr;
            System.out.println(method + "-->" + url + "-->" + queryStr);
            // 转成Map    fav=1&fav=2&uname=shsxt&age=18&other=
            convertMap();
        }
    
        // 处理请求参数为Map
        private void convertMap(){
            // 分割字符串 &
            String[] keyValues = this.queryStr.split("&");
            for(String queryStr: keyValues){
                // 再次分割字符串 =
                String[] kv = queryStr.split("=");
                // 保持两个长度 key 和 value
                kv = Arrays.copyOf(kv, 2);
                // 获取key 和 value
                String key = kv[0];
                String value = kv[1]==null? null: decode(kv[1], "utf-8");
                // 存储在Map中
                if(!parameterMap.containsKey(key)){
                    // 容器里面没有,第一次
                    parameterMap.put(key, new ArrayList<String>());
                }
                parameterMap.get(key).add(value);
            }
        }
    
        // 处理中文
        private String decode(String value, String enc){
            try {
                return java.net.URLDecoder.decode(value, enc);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        // 通过name获取对应的多个值
        public String[] getParameterValues(String key){
            List<String> values = this.parameterMap.get(key);
            if(null == values || values.size()<1){
                return null;
            }
            return values.toArray(new String[0]);
        }
    
        // 通过name获取对应的一个值
        public String getParameter(String key){
            String[] values = getParameterValues(key);
            return values == null?null: values[0];
        }
    }
    
    package com.sxt.Server_study02;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server05.java
     * @time: 2019/12/4 9:26
     * @desc: 封装请求信息中参数转成map
     */
    
    public class Server05 {
        private ServerSocket serverSocket;
        public static void main(String[] args) {
            Server05 server = new Server05();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
            }
        }
    
        // 接受连接处理
        public void receive() {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接...");
                // 获取请求协议
                Request2 request = new Request2(client);
    
                Response response = new Response(client);
    
                // 关注了内容
                response.print("<html>");
                response.print("<head>");
                response.print("<title>");
                response.print("服务器响应成功");
                response.print("</title>");
                response.print("</head>");
                response.print("<body>");
                response.print("终于回来了..." + request.getParameter("uname"));
                response.print("</body>");
                response.print("</html>");
    
                // 关注了状态码
                response.pushToBrowser(200);
    
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误...");
            }
        }
    
        // 停止服务
        public void stop() {
    
        }
    }
    

11. 引入servlet

  • 加入了Servlet解耦了业务代码

    package com.sxt.Server_study03;
    
    import com.sxt.tcp.Server;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server06.java
     * @time: 2019/12/4 9:26
     * @desc: 加入了Servlet解耦了业务代码
     */
    
    public class Server06 {
        private ServerSocket serverSocket;
        public static void main(String[] args) {
            Server06 server = new Server06();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
            }
        }
    
        // 接受连接处理
        public void receive() {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接...");
                // 获取请求协议
                Request request = new Request(client);
                Response response = new Response(client);
    
                // 关注了内容
                Servlet servlet = null;
                if(request.getUrl().equals("login")){
                    servlet = new LoginServlet();
                }else if (request.getUrl().equals("reg")){
                    servlet = new RegisterServlet();
                }else {
                    // 首页
                }
    
                servlet.service(request, response);
    
    
                // 关注了状态码
                response.pushToBrowser(200);
    
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误...");
            }
        }
    
        // 停止服务
        public void stop() {
    
        }
    }
    
  • Request和Response同上

  • Servlet

    package com.sxt.Server_study03;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Servlet.java
     * @time: 2019/12/9 12:03
     * @desc: 服务器小脚本接口
     */
    
    public interface Servlet {
        void service(Request request, Response response);
    }
    
  • RegisterServlet

    package com.sxt.Server_study03;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: RegisterServlet.java
     * @time: 2019/12/3 15:59
     * @desc:
     */
    
    public class RegisterServlet implements Servlet{
        @Override
        public void service(Request request, Response response){
            response.print("注册成功...");
        }
    }
    
  • LoginServlet

    package com.sxt.Server_study03;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LoginServlet.java
     * @time: 2019/12/3 15:59
     * @desc:
     */
    
    public class LoginServlet implements Servlet {
        @Override
        public void service(Request request, Response response) {
            response.print("<html>");
            response.print("<head>");
            response.print("<title>");
            response.print("第一个servlet");
            response.print("</title>");
            response.print("</head>");
            response.print("<body>");
            response.print("欢迎回来..." + request.getParameter("uname"));
            response.print("</body>");
            response.print("</html>");
        }
    }
    

12. 整合webxml

  • 将之前的Mapping,Entity,WebContext,WebHandler拷贝到工程中

  • 解析web.xml代码

    package com.sxt.Server_study03;
    
    import com.sxt.Server_study01.servlet.WebContext;
    
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: WebApp.java
     * @time: 2019/12/9 12:37
     * @desc: 解析代码
     */
    
    public class WebApp {
        private static WebContext context;
    
        static {
            try {
                // SAX解析
                // 1. 获取解析工厂
                SAXParserFactory factory = SAXParserFactory.newInstance();
                // 2. 从解析工厂获取解析器
                SAXParser parse = null;
                parse = factory.newSAXParser();
                // 3. 加载文档Document注册处理器
                // 4. 编写处理器
                WebHandler handler = new WebHandler();
                parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("web.xml"), handler);
                // 5. 获取数据
                context = new WebContext(handler.getEntitys(), handler.getMappings());
    
            } catch (Exception e) {
                System.out.println("解析配置文件错误!");
            }
        }
    
        // 通过url获取配置文件对应的servlet
        public static Servlet getServletFromUrl(String url) {
            // 假设你输入了 /login or /g or /reg
            String className = context.getClz("/" + url);
            Class clz = null;
            try {
                clz = Class.forName(className);
                Servlet servlet = (Servlet) clz.getConstructor().newInstance();
                return servlet;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
  • 新增OthersServlet

    package com.sxt.Server_study03;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: OthersServlet.java
     * @time: 2019/12/9 13:17
     * @desc: 其他测试页面
     */
    
    public class OthersServlet implements Servlet{
        @Override
        public void service(Request request, Response response) {
            response.print("其他测试页面...");
        }
    }
    
  • 修改xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app>
        <servlet>
            <servlet-name>login</servlet-name>
            <servlet-class>com.sxt.Server_study03.LoginServlet</servlet-class>
        </servlet>
        <servlet>
            <servlet-name>reg</servlet-name>
            <servlet-class>com.sxt.Server_study03.RegisterServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>login</servlet-name>
            <url-pattern>/login</url-pattern>
            <url-pattern>/g</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
            <servlet-name>reg</servlet-name>
            <url-pattern>/reg</url-pattern>
        </servlet-mapping>
        <servlet>
            <servlet-name>others</servlet-name>
            <servlet-class>com.sxt.Server_study03.OthersServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>others</servlet-name>
            <url-pattern>/o</url-pattern>
        </servlet-mapping>
    </web-app>
    
  • 整合配置文件

    package com.sxt.Server_study03;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server06.java
     * @time: 2019/12/4 9:26
     * @desc: 整合配置文件
     */
    
    public class Server07 {
        private ServerSocket serverSocket;
        public static void main(String[] args) {
            Server07 server = new Server07();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
            }
        }
    
        // 接受连接处理
        public void receive() {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接...");
                // 获取请求协议
                Request request = new Request(client);
                Response response = new Response(client);
    
                Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
                if(null != servlet){
                    servlet.service(request, response);
                    // 关注了状态码
                    response.pushToBrowser(200);
                }else {
                    // 错误页面...
                    response.pushToBrowser(404);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误...");
            }
        }
    
        // 停止服务
        public void stop() {
    
        }
    }
    

13. 高效分发器

  • 多线程处理,加入分发器

    package com.sxt.Server_study03;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server08.java
     * @time: 2019/12/4 9:26
     * @desc: 多线程处理,加入分发器
     */
    
    public class Server08 {
        private ServerSocket serverSocket;
        private boolean isRunning;
    
        public static void main(String[] args) {
            Server08 server = new Server08();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                isRunning = true;
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
                stop();
            }
        }
    
        // 接受连接处理
        public void receive() {
            while (isRunning) {
                try {
                    Socket client = serverSocket.accept();
                    System.out.println("一个客户端建立了连接...");
                    // 多线程处理
                    new Thread(new Dispatcher(client)).start();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("客户端错误...");
                }
            }
        }
    
        // 停止服务
        public void stop() {
            isRunning = false;
            try {
                this.serverSocket.close();
                System.out.println("服务器已停止...");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 分发器

    package com.sxt.Server_study03;
    
    import java.io.IOException;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Dispatcher.java
     * @time: 2019/12/12 16:36
     * @desc: 分发器
     */
    
    public class Dispatcher implements Runnable {
        private Socket client;
        private Request request;
        private Response response;
    
        public Dispatcher(Socket client) {
            this.client = client;
            try {
                // 获取请求和响应
                request = new Request(client);
                response = new Response(client);
            } catch (IOException e) {
                e.printStackTrace();
                this.release();
            }
        }
    
        @Override
        public void run() {
            try {
                Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
                if (null != servlet) {
                    servlet.service(request, response);
                    // 关注了状态码
                    response.pushToBrowser(200);
                } else {
                    // 错误页面...
                    response.pushToBrowser(404);
                }
            }catch (Exception e){
                try {
                    response.pushToBrowser(500);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            release();
        }
    
        // 释放资源
        private void release() {
            try {
                client.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    

14. 经典404及首页处理

  • 这一部分有问题is.readAllBytes()这里报错,并且也没有找到解决办法

  • 分发器

    package com.sxt.Server_study03;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.Socket;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Dispatcher.java
     * @time: 2019/12/12 16:36
     * @desc: 分发器
     */
    
    public class Dispatcher implements Runnable {
        private Socket client;
        private Request request;
        private Response response;
    
        public Dispatcher(Socket client) {
            this.client = client;
            try {
                // 获取请求和响应
                request = new Request(client);
                response = new Response(client);
            } catch (IOException e) {
                e.printStackTrace();
                this.release();
            }
        }
    
        @Override
        public void run() {
            try {
                if (null == request.getUrl() || request.getUrl().equals("")) {
                    InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("index.html");
                    response.print(new String(is.readAllBytes()));
                    response.println(new String(Files.readAllBytes(Paths.get("index.html"))));
                    response.pushToBrowser(200);
                    is.close();
                    return;
                }
                Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
                if (null != servlet) {
                    servlet.service(request, response);
                    // 关注了状态码
                    response.pushToBrowser(200);
                } else {
                    InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("error.html");
                    response.print(new String(is.readAllBytes()));
                    response.pushToBrowser(404);
                    is.close();
                }
            } catch (Exception e) {
                try {
                    response.print("你好我不好,我会马上好");
                    response.pushToBrowser(500);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            release();
        }
    
        // 释放资源
        private void release() {
            try {
                client.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    
  • 处理404/505和首页

    package com.sxt.Server_study03;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Server08.java
     * @time: 2019/12/4 9:26
     * @desc: 处理404/505和首页
     */
    
    public class Server09 {
        private ServerSocket serverSocket;
        private boolean isRunning;
    
        public static void main(String[] args) {
            Server09 server = new Server09();
            server.start();
        }
    
        // 启动服务
        public void start() {
            try {
                serverSocket = new ServerSocket(8888);
                isRunning = true;
                receive();
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("服务器启动失败...");
                stop();
            }
        }
    
        // 接受连接处理
        public void receive() {
            while (isRunning) {
                try {
                    Socket client = serverSocket.accept();
                    System.out.println("一个客户端建立了连接...");
                    // 多线程处理
                    new Thread(new Dispatcher(client)).start();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("客户端错误...");
                }
            }
        }
    
        // 停止服务
        public void stop() {
            isRunning = false;
            try {
                this.serverSocket.close();
                System.out.println("服务器已停止...");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

第2章 注解+反射+字节码+类加载机制

1. 内置注解

  • Annotation

  • 作用

    • 不是程序本身,可以对程序作出解释。
    • 可以被其他程序(比如:编译器等)读取。
  • 格式

    • @注解名
    package com.sxt.test.annotation;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo01.java
     * @time: 2019/12/16 9:34
     * @desc:
     */
    
    public class Demo01 {
        @Override
        // 重写父类方法
        public String toString() {
            return "";
        }
    
        @Deprecated
        // 该方法不建议使用
        public static void test1() {
            System.out.println("你大爷");
        }
    
        @SuppressWarnings("all")
        // 不显示所有警告信息
        public static void test2() {
            List list = new ArrayList();
        }
    
        @SuppressWarnings(value = {"unchecked", "deprecation"})
        // 不显示某几个警告信息
        public static void main(String[] args) {
            test1();
        }
    }
    

2. 自定义注解

  • 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
  • 要点
    • interface用来声明一个注解
    • 格式为:public @interface 注解名
  • 其中的每一个方法实际上是声明了一个配置参数
    • 方法的名称就是参数的名称
    • 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。
    • 可以通过default来声明参数的默认值
    • 如果只有一个参数成员,一般参数名为value
  • 元注解
    • 负责注解其他注解。
    • @Target:用于描述注解的使用范围
    • @Retention:表示需要在什么级别保存该注解信息,用于描述注解的生命周期
    • @Documented
    • @Inherited

3. 反射机制读取注解

  • ORM:Object Relationship Mapping,对象关系映射

    • 类和表结构对应
    • 属性和字段对应
    • 对象和记录对应
  • 使用注解完成类和表结构的映射关系

    • 类注解

      package com.sxt.test.annotation;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LTTable.java
       * @time: 2019/12/16 12:44
       * @desc:
       */
      
      @Target(value = {ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface LTTable {
          String value();
      }
      
    • 属性注解

      package com.sxt.test.annotation;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LTField.java
       * @time: 2019/12/16 12:46
       * @desc: 说明属性的特征
       */
      
      @Target(value = {ElementType.FIELD})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface LTField {
          String columnName();
          String type();
          int length();
      }
      
    • 学生类

      package com.sxt.test.annotation;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Student.java
       * @time: 2019/12/16 12:43
       * @desc:
       */
      
      @LTTable("tb_student")
      public class Student {
      
          @LTField(columnName = "id", type = "int", length = 10)
          private int id;
          @LTField(columnName = "sname", type = "varchar", length = 10)
          private String studentName;
          @LTField(columnName = "age", type = "int", length = 3)
          private int age;
      
          public int getId() {
              return id;
          }
      
          public void setId(int id) {
              this.id = id;
          }
      
          public String getStudentName() {
              return studentName;
          }
      
          public void setStudentName(String studentName) {
              this.studentName = studentName;
          }
      
          public int getAge() {
              return age;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      }
      
    • 使用反射读取注解的信息,模拟处理注解信息的流程

      package com.sxt.test.annotation;
      
      import java.lang.annotation.Annotation;
      import java.lang.reflect.Field;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo03.java
       * @time: 2019/12/16 12:49
       * @desc: 使用反射读取注解的信息,模拟处理注解信息的流程
       */
      
      public class Demo03 {
          public static void main(String[] args) {
              try {
                  Class clazz = Class.forName("com.sxt.test.annotation.Student");
      
                  // 获得类的所有有效注解
                  Annotation[] annotations = clazz.getAnnotations();
                  for (Annotation a : annotations) {
                      System.out.println(a);
                  }
      
                  // 获得类的指定的注解
                  LTTable st = (LTTable) clazz.getAnnotation(LTTable.class);
                  System.out.println(st.value());
      
                  // 获得类的属性的注解
                  Field f = clazz.getDeclaredField("studentName");
                  LTField ltField = f.getAnnotation(LTField.class);
                  System.out.println(ltField.columnName()+"-->"+ltField.type()+"-->"+ltField.length());
      
                  // 根据获得的表明、字段的信息,拼出DDL语句,然后使用JDBC执行这个SQL,在数据库中生成相关的表
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      

4. 反射机制

  • 介绍+Class对象获取

    • 动态语言:程序运行时,可以改变程序结构或变态类型。(如python、javascript)

    • Java不是动态语言,但可以称为“准动态语言”。Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。

    • 反射机制

      • 指的是可以于运行时加载、探知、使用编译期间完全未知的类。
    • 测试各种类型对应的java.lang.Class对象的获取方式

      package com.sxt.test.reflection;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo01.java
       * @time: 2019/12/17 18:59
       * @desc: 测试各种类型对应的java.lang.Class对象的获取方式
       */
      
      public class Demo01 {
          public static void main(String[] args) {
              String path = "com.sxt.test.bean.User";
              try {
                  Class clazz = Class.forName(path);
                  // 对象是表示或封装一些数据。一个类被加载后,JVM会创建一个对应该类的Class对象,
                  // 类的整个结构信息会放到对应的Class对象中。这个Class对象就像一面镜子一样,
                  // 通过这面镜子我们可以看到对应类的全部信息。
                  System.out.println(clazz);
                  System.out.println(clazz.hashCode());
      
                  // 一个类只对应一个Class对象
                  Class clazz2 = Class.forName(path);
                  System.out.println(clazz2.hashCode());
      
                  Class strClazz = String.class;
                  Class strClazz2 = path.getClass();
                  // 获得的都是String的Class对象
                  System.out.println(strClazz==strClazz2);
      
                  Class intClazz = int.class;
                  System.out.println(intClazz.hashCode());
      
                  // 数组跟维度、类型都有关
                  int[] arr01 = new int[10];
                  int[] arr02 = new int[30];
                  int[][] arr03 = new int[10][10];
                  double[] arr04 = new double[10];
                  System.out.println(arr01.getClass().hashCode() == arr02.getClass().hashCode());
                  System.out.println(arr01.getClass().hashCode() == arr03.getClass().hashCode());
                  System.out.println(arr01.getClass().hashCode() == arr04.getClass().hashCode());
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
  • 动态操作+构造器+方法+属性

    • 应用反射的API获取类的信息(类的名字、属性、方法、构造器等)

      package com.sxt.test.reflection;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.Field;
      import java.lang.reflect.Method;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo02.java
       * @time: 2020/1/7 14:33
       * @desc: 应用反射的API获取类的信息(类的名字、属性、方法、构造器等)
       */
      
      public class Demo02 {
          public static void main(String[] args) {
              String path = "com.sxt.test.bean.User";
              try {
                  Class clazz = Class.forName(path);
      
                  // 获取类的全名
                  System.out.println(clazz.getName());
                  // 获取类的包名+全名
                  System.out.println(clazz.getSimpleName());
      
                  // 获取属性信息
                  // 只能获得public的field
                  // Field[] fields = clazz.getFields();
                  // 返回所有的field
                  Field[] fields = clazz.getDeclaredFields();
                  System.out.println(fields.length);
                  for(Field temp: fields){
                      System.out.println("属性:" + temp);
                  }
      
                  // 通过名称获取属性
                  Field f = clazz.getDeclaredField("uname");
                  System.out.println(f);
      
                  // 获取方法信息
                  Method[] methods = clazz.getDeclaredMethods();
                  Method m1 = clazz.getDeclaredMethod("getUname");
                  Method m2 = clazz.getDeclaredMethod("setUname", String.class);
                  for(Method temp: methods){
                      System.out.println("方法:" + temp);
                  }
      
                  // 获取构造器信息
                  Constructor[] constructors = clazz.getDeclaredConstructors();
                  for(Constructor temp: constructors){
                      System.out.println("构造器:" + temp);
                  }
                  // 获取某个特定的构造器
                  Constructor c1 = clazz.getDeclaredConstructor();
                  System.out.println("无参构造器:" + c1);
                  Constructor c2 = clazz.getDeclaredConstructor(int.class, int.class, String.class);
                  System.out.println("有参构造器:" + c2);
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 通过反射API动态的操作:构造器、方法、属性

      package com.sxt.test.reflection;
      
      import com.sxt.test.bean.User;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.Field;
      import java.lang.reflect.Method;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo3.java
       * @time: 2020/1/8 17:03
       * @desc: 通过反射API动态的操作:构造器、方法、属性
       */
      
      public class Demo3 {
          public static void main(String[] args) {
              String path = "com.sxt.test.bean.User";
              try {
                  Class<User> clazz = (Class<User>) Class.forName(path);
      
                  // 通过反射API调用构造方法,构造对象
                  // 其实是调用了User的无参构造方法
                  User u = clazz.newInstance();
                  System.out.println(u);
      
                  // 指定构造器的调用
                  Constructor<User> c = clazz.getDeclaredConstructor(int.class, int.class, String.class);
                  User u2 = c.newInstance(1001, 18, "李英俊");
                  System.out.println(u2.getUname());
      
                  // 通过反射API调用普通方法
                  User u3 = clazz.newInstance();
                  u3.setUname("李不羁");
                  // 上一句用反射来写如下
                  Method method = clazz.getDeclaredMethod("setUname", String.class);
                  method.invoke(u3, "李不羁");
                  System.out.println(u3.getUname());
      
                  // 通过反射API操作属性
                  User u4 = clazz.newInstance();
                  Field f = clazz.getDeclaredField("uname");
                  // 这个属性不需要做安全检查了,可以直接访问
                  f.setAccessible(true);
                  // 通过反射直接写属性
                  f.set(u4, "李傻瓜");
                  System.out.println(u4.getUname());
                  // 通过反射直接读属性的值
                  System.out.println(f.get(u4));
      
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
  • 提高反射效率+操作泛型+操作注解

    • setAccessible

      • 启用和禁用访问安全检查的开关,值为true则指示反射的对象在使用时应该取消java语言访问检查。值为false则指示反射的对象应该实施java语言访问检查。并不是为true就能访问,为false就不能访问。
      • 禁止安全检查,可以提高反射的运行速度。
    • 反射操作泛型(Generic)

      • java采用泛型擦除的机制来引入泛型。java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦变异完成,所有的和泛型有关的类型全部擦除。
      • 为了通过反射操作这些类型以迎合实际开发的需要,java就新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型
    • 操作泛型

      package com.sxt.test.reflection;
      
      import com.sxt.test.bean.User;
      
      import java.lang.reflect.Method;
      import java.lang.reflect.ParameterizedType;
      import java.lang.reflect.Type;
      import java.util.List;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo4.java
       * @time: 2020/1/9 10:44
       * @desc: 操作泛型
       */
      
      public class Demo4 {
          public void test01(Map<String, User> map, List<User> list){
              System.out.println("Demo04.test01()");
          }
      
          public Map<Integer, User> test02(){
              System.out.println("Demo04.test02()");
              return null;
          }
      
          public static void main(String[] args){
              try{
                  // 获得指定方法参数泛型信息
                  Method m = Demo4.class.getMethod("test01", Map.class, List.class);
                  Type[] t = m.getGenericParameterTypes();
                  for (Type paramType: t){
                      System.out.println("#" + paramType);
                      if(paramType instanceof ParameterizedType){
                          Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
                          for (Type genericType: genericTypes){
                              System.out.println("泛型类型:" + genericType);
                          }
                      }
                  }
      
                  // 获得指定方法返回值泛型信息
                  Method m2 = Demo4.class.getMethod("test02");
                  Type returnType = m2.getGenericReturnType();
                  if(returnType instanceof ParameterizedType){
                      Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
      
                      for (Type genericType: genericTypes){
                              System.out.println("返回值,泛型类型:" + genericType);
                          }
                  }
              } catch (NoSuchMethodException e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 操作注解

      package com.sxt.test.annotation;
      
      import java.lang.annotation.Annotation;
      import java.lang.reflect.Field;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo5.java
       * @time: 2020/1/9 10:57
       * @desc: 操作注解
       */
      
      public class Demo5 {
          public static void main(String[] args) {
              try {
                  Class clazz = Class.forName("com.sxt.test.annotation.Student");
      
                  // 获得类的所有有效注解
                  Annotation[] annotations = clazz.getAnnotations();
                  for (Annotation a : annotations) {
                      System.out.println(a);
                  }
      
                  // 获得类指定的注解
                  LTTable st = (LTTable) clazz.getAnnotation(LTTable.class);
                  System.out.println(st.value());
      
                  // 获得类的属性的注解
                  Field f = clazz.getDeclaredField("studentName");
                  LTField ltField = f.getAnnotation(LTField.class);
                  System.out.println(ltField.columnName() + "--" + ltField.type() + "--" + ltField.length());
      
                  // 根据获得的表名、字段的信息,拼出DDL语句,然后,使用JDBC执行这个SQL,在数据库中生成相关的表
      
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      

5. 动态编译

  • 通过JavaCompiler动态编译

    public static int compileFile(String sourceFile){
    	// 动态编译
    	JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    	int result = compiler.run(null, null, null, sourceFile);
    	System.out.println(result==0?"编译成功": "编译失败");
    	return result;
    }
    
    • 参数1:为java编译器提供参数
    • 参数2:得到Java编译器的输出信息
    • 参数3:接受编译器的错误信息
    • 参数4:可变参数(是一个String数组)能传入一个或多个java源文件
    • 返回值:0表示编译成功,非0表示编译失败
    package com.sxt.test.testDynamicCompile;
    
    import javax.tools.JavaCompiler;
    import javax.tools.ToolProvider;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo1.java
     * @time: 2020/1/9 14:38
     * @desc: 动态编译
     */
    
    public class Demo1 {
        public static void main(String[] args) throws IOException {
    
            // 如果是给的字符串的话,可以
            // 通过IO流操作,将字符串存储成一个临时文件,然后调用动态编译方法!
            // 如果是文件的话,就按下面的方法
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            int result = compiler.run(null, null, null, "D:\\java_test\\HelloWorld.java");
            System.out.println(result == 0 ? "编译成功" : "编译失败");
    
            // 上面只是进行了编译,下面还要进行动态运行编译好的类
            
            // 1. 通过Runtime调用执行类
            Runtime run = Runtime.getRuntime();
            Process process = run.exec("java -cp D:\\java_test HelloWorld");
            InputStream in = process.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String info = "";
            while ((info = reader.readLine()) != null) {
                System.out.println(info);
            }
    
            // 2. 通过反射调用执行类
            try {
                URL[] urls = new URL[]{new URL("file:/" + "D:\\java_test\\")};
                URLClassLoader loader = new URLClassLoader(urls);
                Class c = loader.loadClass("HelloWorld");
                // 调用加载类的main方法
                Method m = c.getMethod("main", String[].class);
                // 如果这里不用Object强制转型的话,invoke后面传入的就不是一个String
                // 的字符串数组,而是两个字符串,认为是两个参数,于是就会发生参数个数不匹配的问题
                m.invoke(null, (Object) new String[]{});
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

6. 脚本引擎执行JavaScript代码__Rhino引擎

  • JAVA脚本引擎是从JDK6.0之后添加的新功能

    • 使得Java应用程序可以通过一套固定的借口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的。
    • Java脚本API是连通Java平台和脚本语言的桥梁。
    • 可以把一些复杂异变的业务逻辑交给脚本语言处理,这又大大提高了开发效率。
  • 在写Demo的时候存在一些报错的情况,这是因为JDK6.0的语法在JDK8.0中已经过时了

    • println报错:改成print
    • importPackage报错:改成不导入包,后面直接使用java.util.Arrays.asList
    function test(){
        var a = 3;
        var b = 4;
        print("invoke js file: " + (a+b));
    }
    
    // 执行test方法
    test();
    
    package com.sxt.test.testRhino;
    
    import javax.script.Invocable;
    import javax.script.ScriptEngine;
    import javax.script.ScriptEngineManager;
    import java.io.FileReader;
    import java.net.URL;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo01.java
     * @time: 2020/1/13 15:58
     * @desc: 测试脚本引擎执行JavaScript代码
     */
    
    public class Demo01 {
        public static void main(String[] args) throws Exception {
            // 获得脚本引擎的对象
            ScriptEngineManager sem = new ScriptEngineManager();
            ScriptEngine engine = sem.getEngineByName("javascript");
    
            // 定义变量,存储到引擎上下文中
            engine.put("msg", "liyingjun is g good man!");
            String str = "var user = {name: 'litian', age: 18, schools: ['清华大学', '北京尚学堂']};";
            str += "print(user.name);";
    
            // 执行脚本
            engine.eval(str);
            engine.eval("msg = 'sxt is a good school!'");
            System.out.println(engine.get("msg"));
            System.out.println("########################");
    
            // 定义函数
            engine.eval("function add(a, b){var sum = a + b; return sum;}");
            // 取得调用接口
            Invocable jsInvoke = (Invocable) engine;
            // 执行脚本中定义的方法
            Object result1 = jsInvoke.invokeFunction("add", new Object[]{13, 20});
            System.out.println(result1);
    
            // 导入其他java包,使用其他包中的java类
            String jsCode = "var list=java.util.Arrays.asList([\"北京尚学堂\", \"清华大学\", \"中国石油大学\"]);";
            engine.eval(jsCode);
    
            List<String> list2 = (List<String>) engine.get("list");
            for (String temp : list2) {
                System.out.println(temp);
            }
    
            // 执行一个js文件
            URL url = Demo01.class.getClassLoader().getResource("com/sxt/test/testRhino/test.js");
            FileReader fr = new FileReader(url.getPath());
            engine.eval(fr);
            fr.close();
        }
    }
    

7. 字节码操作__javassist库

  • Java动态性的两种常见实现方式:

    • 字节码操作
    • 反射
  • 运行时操作字节码可以让我们实现如下功能:

    • 动态生成新的类
    • 动态改变某个类的结构(添加/删除/修改 新的属性/方法)
  • 优势

    • 比反射开销小,性能高
    • JAVAssist性能高于反射,低于ASM
  • 常见的字节码操作类库

    • BCEL
    • ASM
    • CGLIB
    • Javassist
  • 测试使用javassist生成一个新的类

    package com.sxt.test.testJavassist;
    
    import javassist.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo1.java
     * @time: 2020/1/14 9:21
     * @desc: 测试使用javassist生成一个新的类
     */
    
    public class Demo1 {
        public static void main(String[] args) throws Exception {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.makeClass("com.sxt.test.testJavassist.EmpTest");
    
            // 创建属性
            CtField f1 = CtField.make("private int empno;", cc);
            CtField f2 = CtField.make("private String ename;", cc);
            cc.addField(f1);
            cc.addField(f2);
    
            // 创建方法
            CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cc);
            CtMethod m2 = CtMethod.make("public void setEmpno(int empno){this.empno=empno;}", cc);
            cc.addMethod(m1);
            cc.addMethod(m2);
    
            // 添加构造器
            CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType, pool.get("java.lang.String")}, cc);
            constructor.setBody("{this.empno = empno; this.ename = ename;}");
            cc.addConstructor(constructor);
    
            // 将上面构造好的类写入到下面的工作空间下面
            cc.writeFile("D:\\java_test");
            System.out.println("生成类,成功!");
    
        }
    }
    
  • 测试javassist的API

    • Emp.java

      package com.sxt.test.testJavassist;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Emp.java
       * @time: 2020/1/14 9:28
       * @desc:
       */
      
      @Author(name="litian", year=2020)
      public class Emp {
          private int empno;
          private String name;
      
          public int getEmpno() {
              return empno;
          }
      
          public void setEmpno(int empno) {
              this.empno = empno;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public Emp() {
          }
      
          public Emp(int empno, String name) {
              this.empno = empno;
              this.name = name;
          }
      
          public void sayHello(int a){
              System.out.println("Hello!: " + a);
          }
      }
      
    • Author.java

      package com.sxt.test.testJavassist;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Author.java
       * @time: 2020/1/14 14:50
       * @desc:
       */
      
      public @interface Author {
          String name();
          int year();
      }
      
    • 测试javassist的API

      package com.sxt.test.testJavassist;
      
      import javassist.*;
      
      import java.lang.reflect.Method;
      import java.util.Arrays;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo2.java
       * @time: 2020/1/14 10:45
       * @desc: 测试javassist的API
       */
      
      public class Demo2 {
          /*
            处理类的基本用法
           */
          public static void test1() throws Exception {
              ClassPool pool = ClassPool.getDefault();
              CtClass cc = pool.get("com.sxt.test.testJavassist.Emp");
      
              byte[] bytes = cc.toBytecode();
              System.out.println(Arrays.toString(bytes));
      
              // 获取类名
              System.out.println(cc.getName());
              // 获取简要类名
              System.out.println(cc.getSimpleName());
              // 获得父类
              System.out.println(cc.getSuperclass());
              // 获得接口
              System.out.println(cc.getInterfaces());
          }
      
          public static void test2() throws Exception {
              /*
              测试产生新的方法
               */
              ClassPool pool = ClassPool.getDefault();
              CtClass cc = pool.get("com.sxt.test.testJavassist.Emp");
      
              // 新建方法1
              CtMethod m1 = CtNewMethod.make("public int add(int a, int b){return a+b;}", cc);
              // 新建方法2
              CtMethod m2 = new CtMethod(CtClass.intType, "add", new CtClass[]{CtClass.intType, CtClass.intType}, cc);
              // 设置权限
              m2.setModifiers(Modifier.PUBLIC);
              // 设置方法体 $1等等代表的是形参的占位符
              m2.setBody("{System.out.println(\"冲冲冲!\"); return $1+$2;}");
      
              cc.addMethod(m2);
      
              // 通过反射调用新生成的方法
              Class clazz = cc.toClass();
              // 通过调用Emp无参构造器,创建新的Emp对象
              Object obj = clazz.newInstance();
              Method method = clazz.getDeclaredMethod("add", int.class, int.class);
              Object result = method.invoke(obj, 200, 300);
              System.out.println(result);
          }
      
          public static void test3() throws Exception {
              /*
              对已有的方法进行修改
               */
              ClassPool pool = ClassPool.getDefault();
              CtClass cc = pool.get("com.sxt.test.testJavassist.Emp");
      
              CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
              cm.insertBefore("System.out.println($1);System.out.println(\"start!!!\");");
              cm.insertAfter("System.out.println(\"end!!!\");");
              // 在某一行前面加代码,从1开始计数,不存在迭代效应,也就是改行代码的行数不会因加入了新的代码而改变
              cm.insertAt(41, "System.out.println(\"???\");");
              cm.insertAt(42, "System.out.println(\"!!!\");");
      
              // 通过反射调用新生成的方法
              Class clazz = cc.toClass();
              Object obj = clazz.newInstance();
              Method method = clazz.getDeclaredMethod("sayHello", int.class);
              Object result = method.invoke(obj, 200);
              System.out.println(result);
      
          }
      
          public static void test4() throws Exception {
              /*
              对已有的属性进行修改
               */
              ClassPool pool = ClassPool.getDefault();
              CtClass cc = pool.get("com.sxt.test.testJavassist.Emp");
      
              // CtField f1 = CtField.make("private int empno;", cc);
              CtField f1 = new CtField(CtClass.intType, "salary", cc);
              f1.setModifiers(Modifier.PRIVATE);
              // 后面的参数是默认值,如果不写的话,就没有默认值
              cc.addField(f1, "1000");
              // 获取指定的属性
              cc.getDeclaredField("salary");
      
              // 除了直接通过增加方法的方式提供getter和setter方法,还可以通过以下方式
              cc.addMethod(CtNewMethod.getter("getSalary", f1));
              cc.addMethod(CtNewMethod.setter("setSalary", f1));
          }
      
          public static void test5() throws Exception {
              /*
              查看已有构造方法,并进行修改
               */
              ClassPool pool = ClassPool.getDefault();
              CtClass cc = pool.get("com.sxt.test.testJavassist.Emp");
      
              CtConstructor[] cs = cc.getConstructors();
              for (CtConstructor c : cs) {
                  System.out.println(c.getLongName());
                  c.insertBefore("System.out.println(\"what?\");");
              }
              // 通过反射调用新生成的方法
              Class clazz = cc.toClass();
              Object obj = clazz.newInstance();
          }
      
          public static void test6() throws Exception {
              /*
              注解
               */
              ClassPool pool = ClassPool.getDefault();
              CtClass cc = pool.get("com.sxt.test.testJavassist.Emp");
              Object[] all = cc.getAnnotations();
              Author a = (Author) all[0];
              String name = a.name();
              int year = a.year();
              System.out.println("name: " + name + "year: " + year);
          }
      
          public static void main(String[] args) throws Exception {
              test6();
          }
      }
      
  • Javassist库的局限性

    • 不支持泛型、枚举等新语法,不支持注解修改,但可以通过底层的javassist类来解决。
    • 不支持数组的初始化
    • 不支持内部类和匿名类
    • 不支持continue和break表达式
    • 对于集成关系,有些不支持

8. JVM核心机制

  • JVM运行和类加载全过程

    • 类的加载机制(JVM内存分析+反射机制核心原理+常量池理解)

      • JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程。

      • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。

      • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程

        • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
        • 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法去中进行分配
        • 解析:虚拟机常量池内的符号引用替换为直接引用的过程。
      • 初始化

        • 初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
        • 当初始化一个类额时候,如果发现其父类还没有进行过初始化、则需要先进行其父类的初始化
        • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。
        • 当访问一个Java类的静态域时,只有真正声明这个域的类才会被初始化。
      • 过程图解

        过程图解

    • 初始化时机+静态初始化块执行的顺序问题

      • 静态变量、静态域、field本质上是一回事
      • 类构造方法就是把静态域复制的动作以及静态初始化块合并在一起形成一个类构造器
      • 先调类构造方法,然后才调普通方法;先调父类的类构造方法,然后才调子类的
      • 类的加载和初始化只执行一次。
    • 类的主动引用和被动引用

      • 类的主动引用(一定会发生类的初始化)
        • new一个类的对象
        • 调用类的静态成员(除了final常量)和静态方法
        • 使用java.lang.reflect包的方法对类进行反射调用
        • 当虚拟机启动,java Hello,则一定会初始化Hello类,说白了就是先启动main方法所在的类
        • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
      • 类的被动引用(不会发生类的初始化)
        • 当访问一个静态域时,只有真正声明这个域的类才会被初始化(通过子类引用父类的静态变量,不会导致子类初始化)
        • 通过数组定义类引用,不会触发此类的初始化
        • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
  • 深入类加载器

    • 类加载器原理

      • 类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区数据的访问入口。
    • 类加载器树状结构、双亲委托(代理)机制

      • 分类:

        • 引导类加载器(C)
        • 扩展类加载器(JAVA)
        • 应用程序类加载器(JAVA)
        • 自定义类加载器(JAVA)
        • 除了引导类,其他的类都要继承ClassLoader,其基本职责就是根据一个指定的类的名称,找到或者生成相应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例;除此之外,ClassLoader还负责加载java应用所需的资源,如图像文件和配置文件等。
      • 类加载器的代理模式

        • 交给其他加载器来加载指定的类
        • 双亲委托机制代理模式的一种,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
        • 双亲委托机制是为了保证Java核心库的类型安全。这种机制就保证不会出现用户自己能定义java.lang.Obejct类的情况。
        • 类加载器除了用于加载类,也是安全的最基本的保障。
        • 并不是所有的类加载器都采用双亲委托机制。(tomcat的加载顺序是相反的)
      • 类加载器实战

        package com.sxt.test.classLoader;
        
        /**
         * @author: Li Tian
         * @contact: litian_cup@163.com
         * @software: IntelliJ IDEA
         * @file: Demo01.java
         * @time: 2020/1/30 21:40
         * @desc:
         */
        
        public class Demo01 {
            public static void main(String[] args){
                // 应用加载器
                System.out.println(ClassLoader.getSystemClassLoader());
                // 扩展加载器(上一个的父类)
                System.out.println(ClassLoader.getSystemClassLoader().getParent());
                // 引导加载器,但是是C写的,所以为null(上一个的父类)
                System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        
                // 系统中类的路径
                System.out.println(System.getProperty("java.class.path"));
            }
        }
        
    • 自定义类加载器(文件、网路、加密)

      • 自定义加载器的流程:

        • 继承java.lang.ClassLoader
        • 检查请求的类型是否已经被这个类加载器装载到命名空间了,如果已经装载,直接返回;
        • 委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;
        • 调用本类加载器的findClass(...)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(...)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,则返回异常给loadClass(...),loadClass(...)转抛异常,终止加载过程。
        • 注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。
      • 自定义文件系统类加载器

        • FileSystemClassLoader

          package com.sxt.test.classLoader;
          
          import java.io.*;
          
          /**
           * @author: Li Tian
           * @contact: litian_cup@163.com
           * @software: IntelliJ IDEA
           * @file: FileSystemClassLoader.java
           * @time: 2020/1/31 20:58
           * @desc: 自定义文件系统类加载器
           */
          
          public class FileSystemClassLoader extends ClassLoader {
              // com.sxt.test.bean.User --> F:/BookStudy/else/JAVAPro/src/com/sxt/test/bean/User.class
              private String rootDir;
          
              public FileSystemClassLoader(String rootDir) {
                  this.rootDir = rootDir;
              }
          
              @Override
              protected Class<?> findClass(String name) throws ClassNotFoundException {
                  Class<?> c = findLoadedClass(name);
          
                  // 应该要先查询有没有加载过这个类,如果已经加载,则直接返回加载好的类,如果没有,则加载新的类。
                  if (c != null) {
                      return c;
                  } else {
                      ClassLoader parent = this.getParent();
                      // 委派给父类加载
                      try {
                          c = parent.loadClass(name);
                      }catch (Exception e){
                          System.out.println("父类加载器没有加载到这个类哦!");
                      }
                      if (c != null) {
                          return c;
                      } else {
                          byte[] classData = getClassData(name);
                          if (classData == null) {
                              throw new ClassNotFoundException();
                          } else {
                              c = defineClass(name, classData, 0, classData.length);
                          }
                      }
                  }
                  return c;
              }
          
              private byte[] getClassData(String classname) {
                  String path = rootDir + "/" + classname.replace('.', '/') + ".class";
                  // 可以使用IOUtils将流中的数据转成字节数组,这里采用手写了
                  InputStream is = null;
                  ByteArrayOutputStream baos = null;
                  try {
                      is = new FileInputStream(path);
                      baos = new ByteArrayOutputStream();
                      byte[] buffer = new byte[1024];
                      int temp = 0;
                      while ((temp = is.read(buffer)) != -1) {
                          baos.write(buffer, 0, temp);
                      }
                      return baos.toByteArray();
                  } catch (IOException e) {
                      e.printStackTrace();
                      return null;
                  } finally {
                      try {
                          if (is != null) {
                              is.close();
                          }
                      }catch(IOException e){
                          e.printStackTrace();
                      }
                      try {
                          if (baos != null) {
                              baos.close();
                          }
                      }catch(IOException e){
                          e.printStackTrace();
                      }
                  }
              }
          }
          
        • 测试自定义类加载器FileSystemClassLoader

          package com.sxt.test.classLoader;
          
          /**
           * @author: Li Tian
           * @contact: litian_cup@163.com
           * @software: IntelliJ IDEA
           * @file: Demo02.java
           * @time: 2020/1/31 21:17
           * @desc: 测试自定义类加载器FileSystemClassLoader
           */
          
          public class Demo02 {
              public static void main(String[] args) throws ClassNotFoundException {
                  FileSystemClassLoader loader1 = new FileSystemClassLoader("F:\\Java WorkSpace");
                  FileSystemClassLoader loader2 = new FileSystemClassLoader("F:\\Java WorkSpace");
                  System.out.println(loader1 == loader2);
          
          
                  Class<?> c1 = loader1.loadClass("NewClass");
                  Class<?> c2 = loader1.loadClass("NewClass");
                  Class<?> c3 = loader2.loadClass("NewClass");
                  Class<?> c4 = loader1.loadClass("java.lang.String");
                  Class<?> c5 = loader1.loadClass("com.sxt.test.classLoader.Demo01");
          
                  System.out.println(c1);
                  System.out.println(c1.hashCode());
                  System.out.println(c2);
                  System.out.println(c2.hashCode());
          
                  // 注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。
                  System.out.println(c3);
                  System.out.println(c3.hashCode());
          
                  System.out.println(c4);
                  System.out.println(c4.hashCode());
          
                  System.out.println(c1.getClassLoader());
                  System.out.println(c2.getClassLoader());
                  System.out.println(c3.getClassLoader());    // 自定义的类加载器
                  System.out.println(c4.getClassLoader());    // 引导类加载器
                  System.out.println(c5.getClassLoader());    // 系统默认的类加载器
              }
          }
          
      • 自定义网路类加载器

        package com.sxt.test.classLoader;
        
        import java.io.ByteArrayOutputStream;
        import java.io.IOException;
        import java.io.InputStream;
        import java.net.URL;
        
        /**
         * @author: Li Tian
         * @contact: litian_cup@163.com
         * @software: IntelliJ IDEA
         * @file: FileSystemClassLoader.java
         * @time: 2020/1/31 20:58
         * @desc: 自定义网路类加载器
         */
        
        public class NetSystemClassLoader extends ClassLoader {
            // com.sxt.test.bean.User --> www.sxt.cn/JAVAPro/src/com/sxt/test/bean/User.class
            private String rootUrl;
        
            public NetSystemClassLoader(String rootDir) {
                this.rootUrl = rootDir;
            }
        
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                Class<?> c = findLoadedClass(name);
        
                // 应该要先查询有没有加载过这个类,如果已经加载,则直接返回加载好的类,如果没有,则加载新的类。
                if (c != null) {
                    return c;
                } else {
                    ClassLoader parent = this.getParent();
                    // 委派给父类加载
                    try {
                        c = parent.loadClass(name);
                    }catch (Exception e){
                        System.out.println("父类加载器没有加载到这个类哦!");
                    }
                    if (c != null) {
                        return c;
                    } else {
                        byte[] classData = getClassData(name);
                        if (classData == null) {
                            throw new ClassNotFoundException();
                        } else {
                            c = defineClass(name, classData, 0, classData.length);
                        }
                    }
                }
                return c;
            }
        
            private byte[] getClassData(String classname) {
                String path = rootUrl + "/" + classname.replace('.', '/') + ".class";
                // 可以使用IOUtils将流中的数据转成字节数组,这里采用手写了
                InputStream is = null;
                ByteArrayOutputStream baos = null;
                try {
                    URL url = new URL(path);
                    is = url.openStream();
                    baos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int temp = 0;
                    while ((temp = is.read(buffer)) != -1) {
                        baos.write(buffer, 0, temp);
                    }
                    return baos.toByteArray();
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                } finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                    try {
                        if (baos != null) {
                            baos.close();
                        }
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                }
            }
        }
        
      • 自定义加密解密类加载器

        • 加密工具类

          package com.sxt.test.classLoader;
          
          import java.io.*;
          
          /**
           * @author: Li Tian
           * @contact: litian_cup@163.com
           * @software: IntelliJ IDEA
           * @file: EncrpUtil.java
           * @time: 2020/2/1 22:52
           * @desc: 加密工具类
           */
          
          public class EncrpUtil {
              public static void main(String[] args) {
                  encrpt("F:/Java WorkSpace/NewClass.class", "F:/Java WorkSpace/temp/NewClass.class");
              }
          
              public static void encrpt(String src, String dest) {
                  FileInputStream fis = null;
                  FileOutputStream fos = null;
          
                  try {
                      fis = new FileInputStream(src);
                      fos = new FileOutputStream(dest);
          
                      int temp = -1;
                      while ((temp = fis.read()) != -1) {
                          // 取反操作
                          fos.write(temp ^ 0xff);
                      }
          
                  } catch (IOException e) {
                      e.printStackTrace();
                  } finally {
                      try {
                          if (fis != null) {
                              fis.close();
                          }
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  try {
                      if (fos != null) {
                          fos.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
          
        • 解密工具类:加载文件系统中加密后的class字节码的类加载器

          package com.sxt.test.classLoader;
          
          import java.io.ByteArrayOutputStream;
          import java.io.FileInputStream;
          import java.io.IOException;
          import java.io.InputStream;
          
          /**
           * @author: Li Tian
           * @contact: litian_cup@163.com
           * @software: IntelliJ IDEA
           * @file: DecrptClassLoader.java
           * @time: 2020/2/1 23:03
           * @desc: 解密工具类:加载文件系统中加密后的class字节码的类加载器
           */
          
          public class DecrptClassLoader extends ClassLoader {
              // com.sxt.test.bean.User --> F:/BookStudy/else/JAVAPro/src/com/sxt/test/bean/User.class
              private String rootDir;
          
              public DecrptClassLoader(String rootDir) {
                  this.rootDir = rootDir;
              }
          
              @Override
              protected Class<?> findClass(String name) throws ClassNotFoundException {
                  Class<?> c = findLoadedClass(name);
          
                  // 应该要先查询有没有加载过这个类,如果已经加载,则直接返回加载好的类,如果没有,则加载新的类。
                  if (c != null) {
                      return c;
                  } else {
                      ClassLoader parent = this.getParent();
                      // 委派给父类加载
                      try {
                          c = parent.loadClass(name);
                      } catch (Exception e) {
                          System.out.println("父类加载器没有加载到这个类哦!");
                      }
                      if (c != null) {
                          return c;
                      } else {
                          byte[] classData = getClassData(name);
                          if (classData == null) {
                              throw new ClassNotFoundException();
                          } else {
                              c = defineClass(name, classData, 0, classData.length);
                          }
                      }
                  }
                  return c;
              }
          
              private byte[] getClassData(String classname) {
                  String path = rootDir + "/" + classname.replace('.', '/') + ".class";
                  // 可以使用IOUtils将流中的数据转成字节数组,这里采用手写了
                  InputStream is = null;
                  ByteArrayOutputStream baos = null;
                  try {
                      is = new FileInputStream(path);
                      baos = new ByteArrayOutputStream();
                      int temp = -1;
                      while ((temp = is.read()) != -1) {
                          // 取反操作,进行解密
                          baos.write(temp ^ 0xff);
                      }
                      return baos.toByteArray();
                  } catch (IOException e) {
                      e.printStackTrace();
                      return null;
                  } finally {
                      try {
                          if (is != null) {
                              is.close();
                          }
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                      try {
                          if (baos != null) {
                              baos.close();
                          }
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
          
        • 测试简单加密解密(取反)操作

          package com.sxt.test.classLoader;
          
          /**
           * @author: Li Tian
           * @contact: litian_cup@163.com
           * @software: IntelliJ IDEA
           * @file: Demo03.java
           * @time: 2020/2/1 22:49
           * @desc: 测试简单加密解密(取反)操作
           */
          
          public class Demo03 {
              public static void main(String[] args) throws ClassNotFoundException {
                  int a = 3;  // 00000011
                  System.out.println(Integer.toBinaryString(a ^ 0xff));
          
                  // 加载这个加密的类会报类格式错误ClassFormatError
                  FileSystemClassLoader loader1 = new FileSystemClassLoader("F:/Java WorkSpace");
                  Class<?> c1 = loader1.loadClass("NewClass_encrp");
                  System.out.println(c1);
          
                  // 使用解密类加载器加载加密后的类
                  DecrptClassLoader loader = new DecrptClassLoader("F:/Java WorkSpace/temp");
                  Class<?> c = loader.loadClass("NewClass");
                  System.out.println(c);
              }
          }
          
    • 线程上下文类加载器

      • 双亲委托机制以及类加载器的问题
        • 一般情况下,保证同一个类中所关联的其他类都是由当前类的类加载器所加载的。(比如,ClassA本身在Ext下找到,那么它里面new出来的一些类也就只能用Ext去查找了(不会低一个级别),所以有些明明App可以找到的,却找不到了。)
        • JDBC API有时限的driver部分(mysql/sql server),我们的JDBC API都是由Boot或者Ext来载入的,但是JDBC driver却是由Ext或者App来载入,那么就有可能找不到driver了。在Java中,其实只要分成这种api+SPI(Service Provide Interface,特定厂商提供)的,都会遇到此问题。
        • 常见的SPI有JDBC、JCE、JNDL、JAXP和JBI等,这些SPI接口由JAVA核心库来提供,如JAXP的SPI接口定义包含在javax.mxl.parsers包中。SPI的接口是JAVA核心库的一部分,是由引导类加载器来加载的;SPI实现的JAVA类一般是由系统类加载器来加载的;引导类加载器是无法找到SPI的实现类的,因为它只加载了JAVA的核心库。
      • 通常当你需要动态加载资源的时候,你至少有三个ClassLoader可以选择:
        • 系统类加载器或者叫做应用类加载器(system classloader or application classloader)
        • 当前类加载器
        • 当前线程类加载器
      • 线程类加载器是为了抛弃双亲委派机制加载链模式。
        • 每一个线程都有一个关联的上下文类加载器,如果你使用new Thread()方式生成新的线程,新线程将继承其父线程的上下文类加载器,如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程都将使用系统类加载器作为上下文类加载器。
      • Thread.currentThread().getContextClassLoader()
      • Thread.currentThread().setContextClassLoader()
    • 线程上下文类加载器测试

      package com.sxt.test.classLoader;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo05.java
       * @time: 2020/2/2 22:51
       * @desc: 线程上下文类加载器测试
       */
      
      public class Demo05 {
          public static void main(String[] args) throws ClassNotFoundException {
              ClassLoader loader = Demo05.class.getClassLoader();
              System.out.println(loader);
      
              ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
              System.out.println(loader2);
      
              Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("F:/Java WorkSpace"));
              System.out.println(Thread.currentThread().getContextClassLoader());
      
              Class<Demo01> c = (Class<Demo01>) Thread.currentThread().getContextClassLoader().loadClass("com.sxt.test.classLoader.Demo01");
              System.out.println(c);
              System.out.println(c.getClassLoader());
          }
      }
      
    • 服务器类加载原理和OSGI介绍

      • TOMCAT服务器类加载机制
        • TOMCAT不能使用系统默认的类加载器(每一个项目都有自己独立的类加载器,不是双亲委派机制,而是反过来,先从子类再找父类)
        • 为了安全TOMCAT需要实现自己的类加载器
      • OSGI(Open Service Gateway Initative)
        • 是面向Java的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。
        • OSGI已经被实现和部署在很多产品上,在开源社区也得到了广泛的支持。Eclipse就是基于OSGI技术来构建的
        • 原理:OSGI中的每个模块(bundle)都包含java包和类。模块可以声明它所依赖的需要导入(import)的其他模块的java包和类(通过import-package),也可以声明导出(export)自己的包和类,供其它模块使用(通过export-package)。也就是说需要能够隐藏和共享一个模块中的某些java包和类。这是通过OSGI特有的类加载器机制来实现的。OSGI中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的java包和类。当它需要加载java核心库时(以java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的java类时,它会代理给导出java类的模块来完成加载。模块也可以是显式的声明某些java包和类,必须由父类加载器来加载。只需要设置系统属性org.osgi.framework.bootdelegation的值即可。
        • Equinox是OSGI的一个实现。

第3章 设计模式GOF23

  • 创建型模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为型模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
  • 各种模式的区别:
    • 行为型模式关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共有11种模式
    • 创建型模式关注对象的创建过程
    • 结构型模式关注对象和类的组织

1. 单例模式

  • 核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

  • 常见的应用场景:

    • Windows的Task Manager(任务管理器)就是很典型的单例模式
    • Windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
    • 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
    • 网站的计数器,一般也是采用单例模式实现,否则难以同步。
    • 应用程序的日志应用,一般都通过单例模式实现,着一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
    • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
    • 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
    • Application也是单例的典型应用(Servlet编程中会涉及到)
    • 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
    • 在servlet编程中,每个servlet也是单例的
    • 在spring MVC框架/struts1框架中,控制对象也是单例
  • 单例模式的优点:

    • 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
    • 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
  • 常见的五种单例模式实现方式:

    • 主要:
      • 饿汉式(线程安全,调用效率高。但是,不能延时加载。)
      • 懒汉式(线程安全,调用效率不高。但是,可以延时加载。)
    • 其他:
      • 双重检测锁式(由于JVM底层内部模型的原因,偶尔会出问题。不建议使用)
      • 静态内部类式(线程安全,调用效率高。但是,可以延时加载)
      • 枚举单例(线程安全,调用效率高,不能延时加载,但可以天然的防止反射和反序列化漏洞!

1.1 饿汉式

  • (单例对象立即加载)

  • 代码

    package com.sxt.singleton;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SingletonDemo01.java
     * @time: 2020/2/3 18:41
     * @desc: 测试饿汉式单例模式
     */
    
    public class SingletonDemo01 {
        // 上来就把这个对象先new了,不管后面你使不使用它。所以叫做饿汉式。
        // 类初始化时,立即加载这个对象。(所以不能延时加载)
        // 由于加载类时,天然的是线程安全的!
        private static SingletonDemo01 instance = new SingletonDemo01();
        private SingletonDemo01(){
        }
    
        // 方法没有同步,调用效率高!
        public static SingletonDemo01 getInstance(){
            return instance;
        }
    }
    
  • 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。

  • 问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

1.2 懒汉式

  • (单例对象延迟加载)

  • 代码

    package com.sxt.singleton;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SingletonDemo01.java
     * @time: 2020/2/3 18:41
     * @desc: 测试懒汉式单例模式
     */
    
    public class SingletonDemo02 {
        // 类初始化时,不初始化这个对象。(延时加载,真正用的时候再创建)
        private static SingletonDemo02 instance;
    
        private SingletonDemo02() {
        }
    
        // 方法同步,调用效率低!
        public static synchronized SingletonDemo02 getInstance() {
            if (instance == null){
                instance = new SingletonDemo02();
            }
            return instance;
        }
    }
    
  • 要点:lazy load!延迟加载,懒加载!真正用的时候才加载!

  • 问题:资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。

1.3 双重检测锁

  • 代码

    package com.sxt.singleton;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SingletonDemo01.java
     * @time: 2020/2/3 18:41
     * @desc: 测试双重检测锁实现单例模式
     */
    
    public class SingletonDemo03 {
        private static SingletonDemo03 instance = null;
    
        private SingletonDemo03() {
        }
    
        public static SingletonDemo03 getInstance() {
            if (instance == null){
                SingletonDemo03 sc;
                synchronized (SingletonDemo03.class){
                    sc = instance;
                    if(sc == null){
                        synchronized (SingletonDemo03.class){
                            if(sc == null){
                                sc = new SingletonDemo03();
                            }
                        }
                        instance = sc;
                    }
                }
            }
            return instance;
        }
    }
    
  • 这个模式将同步内容下放到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了。

  • 问题:由于编辑器优化原因和JVM底层内部模型原因,偶尔会出问题,不建议使用。

1.4 静态内部类

  • (也是一种懒加载方式)

  • 代码

    package com.sxt.singleton;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SingletonDemo01.java
     * @time: 2020/2/3 18:41
     * @desc: 测试静态内部类实现单例模式,这种方式线程安全,调用效率高,并且实现了延时加载
     */
    
    public class SingletonDemo04 {
        private static class SingletonClassInstance{
            private static final SingletonDemo04 instance = new SingletonDemo04();
        }
        private SingletonDemo04(){
        }
    
        // 方法没有同步,调用效率高!
        public static SingletonDemo04 getInstance(){
            return SingletonClassInstance.instance;
        }
    }
    
  • 要点

    • 外部类没有static属性,则不会像饿汉式那样立即家在对象。
    • 只有真正调用了getInstance()才会加载静态内部类,加载类时时线程安全的。instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
    • 兼备了并发高效调用和延迟加载的优势
  • 问题:

    • 反射可以破解上面几种实现方式!(可以在构造方法中手动抛出异常控制)
    • 反序列化可以破解上面几种实现方式!可以通过定义readResolve()防止获得不同对象。反序列化时,如果对象所在类定义了readResolve()(实际是一种回调),定义返回哪个对象。

1.5 枚举

  • 代码

    package com.sxt.singleton;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SingletonDemo01.java
     * @time: 2020/2/3 18:41
     * @desc: 测试枚举实现单例模式(没有延时加载)
     */
    
    public enum  SingletonDemo05 {
        // 这个枚举元素,本身就是单例对象!
        INSTANCE;
    
        // 添加自己需要的操作!
        public void singletonOperation(){
    
        }
    }
    
  • 优点

    • 实现简单
    • 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
  • 缺点

    • 无延迟加载
  • UML类图

    img

  • 如何选用?

    • 单例对象,占用资源少,不需要延时加载:枚举式好于饿汉式
    • 单例对象,占用资源大,需要延时加载:静态内部类好于懒汉式

1.6 如何防止反射和反序列化

  • 测试懒汉式单例模式

    package com.sxt.singleton;
    
    import java.io.Serializable;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SingletonDemo01.java
     * @time: 2020/2/3 18:41
     * @desc: 测试懒汉式单例模式(如何防止反射和反序列化)
     */
    
    public class SingletonDemo06 implements Serializable {
        // 类初始化时,不初始化这个对象。(延时加载,真正用的时候再创建)
        private static SingletonDemo06 instance;
    
        private SingletonDemo06() {
            // 通过检查是否已经创建对象了,如果有了,则抛出异常
            if (instance != null) {
                throw new RuntimeException();
            }
        }
    
        // 方法同步,调用效率低!
        public static synchronized SingletonDemo06 getInstance() {
            if (instance == null) {
                instance = new SingletonDemo06();
            }
            return instance;
        }
    
        // 反序列化时,如果定了readResolve方法,则直接返回此方法指定的对象,而不需要单独再创建新对象!
        private Object readResolve() throws Exception{
            return instance;
        }
    }
    
  • 测试反射和反序列化被破解单例模式

    package com.sxt.singleton;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Client.java
     * @time: 2020/2/3 20:13
     * @desc: 测试反射和反序列化被破解单例模式
     */
    
    public class Client2 {
        public static void main(String[] args) throws Exception {
            // 测试饿汉实现单例
            SingletonDemo06 s1 = SingletonDemo06.getInstance();
            SingletonDemo06 s2 = SingletonDemo06.getInstance();
            System.out.println(s1);
            System.out.println(s2);
    
            // 通过反射的方式直接调用私有构造器
            Class<SingletonDemo06> clazz = (Class<SingletonDemo06>) Class.forName("com.sxt.singleton.SingletonDemo06");
            Constructor<SingletonDemo06> c = clazz.getDeclaredConstructor(null);
            // 这样设置就可以访问private的构造器了,这种方式则跳过了单例模式的限制
            c.setAccessible(true);
    
            SingletonDemo06 s3 = c.newInstance();
            SingletonDemo06 s4 = c.newInstance();
    
            System.out.println(s3);
            System.out.println(s4);
    
            // 通过反序列化的方式构造多个对象
            FileOutputStream fos = new FileOutputStream("a.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s1);
            oos.close();
            fos.close();
    
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
            SingletonDemo06 s5 = (SingletonDemo06) ois.readObject();
            System.out.println(s5);
        }
    }
    
  • CountDownLatch

    • 同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
    • countDown():当前线程调此方法,则计数减一(减一放在finally里进行)
    • await():调用此方法会一直阻塞当前线程,直到计时器的值为0
  • 测试五种创建单例模式的效率

    package com.sxt.singleton;
    
    import java.util.concurrent.CountDownLatch;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Client.java
     * @time: 2020/2/3 20:13
     * @desc: 测试五种创建单例模式的效率
     */
    
    public class Client03 {
        public static void main(String[] args) throws Exception {
    
            long start = System.currentTimeMillis();
            int threadNum = 10;
            final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
            for (int i = 0; i < threadNum; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 100000; i++) {
    //                        Object o = SingletonDemo04.getInstance();
                            Object o = SingletonDemo05.INSTANCE;
                        }
                        countDownLatch.countDown();
                    }
                }).start();
            }
            // main线程阻塞,直到计数器变为0,才会继续往下执行!
            countDownLatch.await();
            long end = System.currentTimeMillis();
            System.out.println("总耗时:" + (end - start));
        }
    }
    

2. 工厂模式

  • 实现了创建者和调用者的分离
  • 详细分类:
    • 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
    • 简单方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
    • 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加的新产品,无能为力,支持增加产品族)
  • 面向对象设计基本原则:
    • OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭
    • DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程
    • LoD(迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和陌生人通信
  • 核心本质:
    • 实例化对象,用工厂方法代替new操作
    • 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

2.1 简单工厂模式

  • 要点:

    • 简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接受的参数的不同来返回不同的对象实例。
    • 对于增加新产品无能为力!不修改代码的话,是无法扩展的。
  • 代码:

    • Car接口

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Car.java
       * @time: 2020/2/5 15:59
       * @desc:
       */
      
      public interface Car {
          void run();
      }
      
    • 奥迪车

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Audi.java
       * @time: 2020/2/5 16:00
       * @desc:
       */
      
      public class Audi implements Car {
          @Override
          public void run() {
              System.out.println("奥迪在跑!");
          }
      }
      
    • 比亚迪车

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Byd.java
       * @time: 2020/2/5 16:01
       * @desc:
       */
      
      public class Byd implements Car {
          @Override
          public void run() {
              System.out.println("比亚迪在跑!");
          }
      }
      
    • 测试在没有工厂模式的情况下

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client01.java
       * @time: 2020/2/5 16:02
       * @desc: 测试在没有工厂模式的情况下
       */
      
      public class Client01 { // 调用者
          public static void main(String[] args){
              Car c1 = new Audi();
              Car c2 = new Byd();
              c1.run();
              c2.run();
          }
      }
      
    • 简单工厂类1

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: CarFactory.java
       * @time: 2020/2/5 16:04
       * @desc:
       */
      
      public class CarFactory {
          public static Car createCar(String type){
              if("奥迪".equals(type)){
                  return new Audi();
              }else if("比亚迪".equals(type)){
                  return new Byd();
              }else{
                  // 简单工厂还是有点小问题的,这里如果要加新的车辆的话,就需要改代码,违反了开闭原则OCP
                  return null;
              }
          }
      }
      
    • 简单工厂2

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: CarFactory.java
       * @time: 2020/2/5 16:04
       * @desc: 简单工厂类2
       */
      
      public class CarFactory2 {
          public static Car createAudi() {
              return new Audi();
          }
      
          public static Car createByd() {
              return new Byd();
          }
      }
      
    • 测试在简单工厂模式的情况下

      package com.sxt.factory.simplefactor;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client01.java
       * @time: 2020/2/5 16:02
       * @desc: 测试在简单工厂模式的情况下
       */
      
      public class Client02 { // 调用者
          public static void main(String[] args){
              Car c1 = CarFactory.createCar("奥迪");
              Car c2 = CarFactory.createCar("比亚迪");
              c1.run();
              c2.run();
          }
      }
      
  • UML类图

    img

2.2 工厂方法模式

  • 为了避免简单工厂模式的缺点,不完全满足OCP。

  • 工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。

  • 代码:

    • 新增了车的工厂interface

      package com.sxt.factory.factorymethod;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: CarFactory.java
       * @time: 2020/2/5 19:42
       * @desc:
       */
      
      public interface CarFactory {
          Car createCar();
      }
      
    • 实现了车工厂的audi工厂

      package com.sxt.factory.factorymethod;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: AudiFactory.java
       * @time: 2020/2/5 19:42
       * @desc:
       */
      
      public class AudiFactory implements CarFactory {
          @Override
          public Car createCar() {
              return new Audi();
          }
      }
      
    • 实现了车工厂的byd工厂

      package com.sxt.factory.factorymethod;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: BydFactory.java
       * @time: 2020/2/5 19:43
       * @desc:
       */
      
      public class BydFactory extends Byd implements CarFactory {
          @Override
          public Car createCar() {
              return new Byd();
          }
      }
      
    • 客户端

      package com.sxt.factory.factorymethod;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/5 19:44
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              Car c1 = new AudiFactory().createCar();
              Car c2 = new BydFactory().createCar();
              Car c3 = new BenzFactory().createCar();
      
              c1.run();
              c2.run();
              c3.run();
          }
      }
      
    • 如果需要新增车的类型的话,不需要修改原来的代码,只需要增加类就行,即满足了OCP

    • 新增benz类

      package com.sxt.factory.factorymethod;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Benz.java
       * @time: 2020/2/5 19:45
       * @desc:
       */
      
      public class Benz extends BenzFactory implements Car {
          @Override
          public void run() {
              System.out.println("奔驰在跑!");
          }
      }
      
    • 新增benz工厂

      package com.sxt.factory.factorymethod;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Benz.java
       * @time: 2020/2/5 19:45
       * @desc:
       */
      
      public class Benz extends BenzFactory implements Car {
          @Override
          public void run() {
              System.out.println("奔驰在跑!");
          }
      }
      
  • 简单工厂模式和工厂方法模式PK:

    img

  • 根据设计理论建议:工厂方法模式。但实际上,我们一般都用简单工厂模式

2.3 抽象工厂模式(第23个模式)

  • 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

  • 抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。

  • 代码:

    • 发动机

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Engine.java
       * @time: 2020/2/6 12:40
       * @desc:
       */
      
      public interface Engine {
          void run();
          void start();
      }
      
      class LuxuryEngine implements Engine{
      
          @Override
          public void run() {
              System.out.println("转得快!");
          }
      
          @Override
          public void start() {
              System.out.println("启动快!可以自动启停!");
          }
      }
      class LowerEngine implements Engine{
      
          @Override
          public void run() {
              System.out.println("转得慢!");
          }
      
          @Override
          public void start() {
              System.out.println("启动慢!可以自动启停!");
          }
      }
      
    • 座位

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Seat.java
       * @time: 2020/2/6 12:42
       * @desc:
       */
      
      public interface Seat {
          void massage();
      }
      
      class LuxurySeat implements Seat{
      
          @Override
          public void massage() {
              System.out.println("可以按摩!");
          }
      }
      class LowerSeat implements Seat{
      
          @Override
          public void massage() {
              System.out.println("不可以按摩!");
          }
      }
      
    • 轮胎

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Tyre.java
       * @time: 2020/2/6 12:43
       * @desc:
       */
      
      public interface Tyre {
          void revolve();
      }
      
      class LuxuryTyre implements Tyre{
      
          @Override
          public void revolve() {
              System.out.println("磨损慢!");
          }
      }
      class LowerTyre implements Tyre{
      
          @Override
          public void revolve() {
              System.out.println("磨损快!");
          }
      }
      
      
    • 车工厂

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: CarFactory.java
       * @time: 2020/2/6 12:44
       * @desc:
       */
      
      public interface CarFactory {
          Engine createEngine();
          Seat createSeat();;
          Tyre createTyre();
      }
      
    • 高端车工厂

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LuxuryCarFactory.java
       * @time: 2020/2/6 12:45
       * @desc:
       */
      
      public class LuxuryCarFactory implements CarFactory {
      
          @Override
          public Engine createEngine() {
              return new LuxuryEngine();
          }
      
          @Override
          public Seat createSeat() {
              return new LuxurySeat();
          }
      
          @Override
          public Tyre createTyre() {
              return new LuxuryTyre();
          }
      }
      
    • 低端车工厂

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LowerCarFactory.java
       * @time: 2020/2/6 12:45
       * @desc:
       */
      
      public class LowerCarFactory implements CarFactory {
      
          @Override
          public Engine createEngine() {
              return new LowerEngine();
          }
      
          @Override
          public Seat createSeat() {
              return new LowerSeat();
          }
      
          @Override
          public Tyre createTyre() {
              return new LowerTyre();
          }
      }
      
    • 客户端

      package com.sxt.factory.abstractfactory;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/6 12:47
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              CarFactory factory = new LuxuryCarFactory();
              Engine e = factory.createEngine();
              e.run();
              e.start();
          }
      }
      
  • 工厂模式要点

    • 简单工厂模式(静态工厂模式):虽然某种程度不符合实际原则,但实际使用最多
    • 工厂方法模式:不修改已有类的前提下,通过增加新的工厂实现扩展。
    • 抽象工厂模式:不可以增加产品,但可以增加产品族。
  • 应用场景

    • JDK中Calendar的getInstance方法
    • JDBC中Connection对象的获取
    • Hibernate中SessionFactory创建Session
    • spring中IOC容器创建管理bean对象
    • XML解析时的DocumentBuilderFactory创建解析器对象
    • 反射中Class对象的newInstance

3. 建造者模式

  • 场景:

    • 我们要建造一个复杂的产品。比如神舟飞船、Iphone。这个复杂的产品的创建,有这样的一个问题要处理:装配这些子组件是不是有个步骤问题?
    • 实际开发中,我们所需要的对象构建时,也非常复杂,有很多步骤需要处理时。
  • 建造者模式的本质

    • 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构造过程复杂的情况下使用。
    • 由于实现了构造和装配的解耦。不同的构造器,相同的装配,也可以做出不同的对象;相同的装备构造器,不同的装配顺序也可以做出不同的对象。也就是实现了构造算法、装配算法的解耦,实现了更好的复用。
  • 代码:

    • 宇宙飞船

      package com.sxt.builder;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: AirShip.java
       * @time: 2020/2/6 13:48
       * @desc: 宇宙飞船
       */
      
      public class AirShip {
          // 轨道舱
          private OrbitalModule orbitalModule;
          // 发动机
          private Engine engine;
          // 逃逸仓
          private EscapeTower escapeTower;
      
          public void launch(){
              System.out.println("发动机【" + engine.getName() + "】" + "轨道舱【" + orbitalModule.getName() + "】" + "逃离塔【" + escapeTower.getName() + "】" + "-->发射!");
          }
      
          public OrbitalModule getOrbitalModule() {
              return orbitalModule;
          }
      
          public void setOrbitalModule(OrbitalModule orbitalModule) {
              this.orbitalModule = orbitalModule;
          }
      
          public Engine getEngine() {
              return engine;
          }
      
          public void setEngine(Engine engine) {
              this.engine = engine;
          }
      
          public EscapeTower getEscapeTower() {
              return escapeTower;
          }
      
          public void setEscapeTower(EscapeTower escapeTower) {
              this.escapeTower = escapeTower;
          }
      }
      
      class OrbitalModule {
          private String name;
      
          public OrbitalModule(String name) {
              this.name = name;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      
      class Engine {
          private String name;
      
          public Engine(String name) {
              this.name = name;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      
      class EscapeTower{
          private String name;
      
          public EscapeTower(String name) {
              this.name = name;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      
    • 构建者接口

      package com.sxt.builder;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: AirShipBuilder.java
       * @time: 2020/2/6 13:53
       * @desc:
       */
      
      public interface AirShipBuilder {
          Engine buildEngine();
          OrbitalModule builderOrbitalModule();
          EscapeTower buildEscapeTower();
      }
      
    • 装配者接口

      package com.sxt.builder;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: AirShipDirector.java
       * @time: 2020/2/6 13:54
       * @desc: 组装飞船对象
       */
      
      public interface AirShipDirector {
          // 组装飞船对象
          AirShip directAirShip();
      }
      
    • 构建者

      package com.sxt.builder;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MyAirShipBuilder.java
       * @time: 2020/2/6 13:55
       * @desc:
       */
      
      public class MyAirShipBuilder implements AirShipBuilder{
      
          @Override
          public Engine buildEngine() {
              System.out.println("构建发动机!");
              return new Engine("我的发动机");
          }
      
          @Override
          public OrbitalModule builderOrbitalModule() {
              System.out.println("构建轨道舱!");
              return new OrbitalModule("我的轨道舱");
          }
      
          @Override
          public EscapeTower buildEscapeTower() {
              System.out.println("构建逃逸塔!");
              return new EscapeTower("我的逃逸塔");
          }
      }
      
    • 装配者

      package com.sxt.builder;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MyAirShipDirector.java
       * @time: 2020/2/6 13:59
       * @desc:
       */
      
      public class MyAirShipDirector implements AirShipDirector{
      
          private AirShipBuilder builder;
      
          public MyAirShipDirector(AirShipBuilder builder) {
              this.builder = builder;
          }
      
          @Override
          public AirShip directAirShip() {
              // 从构建者获取组件
              Engine e = builder.buildEngine();
              OrbitalModule o = builder.builderOrbitalModule();
              EscapeTower es = builder.buildEscapeTower();
      
              // 进行组装
              AirShip ship = new AirShip();
              ship.setEngine(e);
              ship.setOrbitalModule(o);
              ship.setEscapeTower(es);
      
              return ship;
          }
      }
      
    • 客户端

      package com.sxt.builder;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/6 14:02
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args) {
              AirShipDirector director = new MyAirShipDirector(new MyAirShipBuilder());
      
              AirShip ship = director.directAirShip();
      
              ship.launch();
          }
      }
      
    • UML类图

      img

  • 开发中应用场景:

    • StringBuilder类的append方法
    • SQL中的PreparedStatement
    • JDOM中,DomBuilder、SAXBuilder

4. 原型模式

  • 又叫克隆模式、拷贝模式,prototype
  • 根据拷贝方式不同分为
  • 场景:
    • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
    • 就是java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。
    • 优势有:效率高(直接克隆,避免了重新执行构造过程步骤)。
    • 克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后,再修改克隆对象的值。
  • 实现:
    • Cloneable接口和clone方法
    • Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在java中提供了clone()方法替我们做了绝大部分事情。
  • 注意用词:克隆和拷贝一回事。

4.1 浅克隆

  • 代码:

    • package com.sxt.prototype;
      
      import java.util.Date;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Sheep1.java
       * @time: 2020/2/6 15:53
       * @desc: 测试浅复制
       */
      
      public class Sheep1 implements Cloneable{
          private String sname;
          private Date birthday;
      
          public String getSname() {
              return sname;
          }
      
          public void setSname(String sname) {
              this.sname = sname;
          }
      
          public Date getBirthday() {
              return birthday;
          }
      
          public void setBirthday(Date birthday) {
              this.birthday = birthday;
          }
      
          public Sheep1(String sname, Date birthday) {
              this.sname = sname;
              this.birthday = birthday;
          }
      
          public Sheep1() {
          }
      
          @Override
          protected Object clone() throws CloneNotSupportedException {
              // 直接调用object对象的clone()方法
              Object obj = super.clone();
              return obj;
          }
      }
      
    • 客户端

      package com.sxt.prototype;
      
      import java.util.Date;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client2.java
       * @time: 2020/2/6 15:57
       * @desc: 测试原型模式(浅复制)
       */
      
      public class Client1 {
          public static void main(String[] args) throws CloneNotSupportedException {
              Date d = new Date(3333333333L);
              Sheep1 s1 = new Sheep1("少理", d);
              System.out.println(s1);
              System.out.println(s1.getSname());
              System.out.println(s1.getBirthday());
      
              // 两个是不同的对象,但是值是一样的!
              Sheep1 s2 = (Sheep1) s1.clone();
      
              // 修改s1生日
              d.setTime(22222222222L);
              System.out.println("--------------------------");
              System.out.println(s1.getBirthday());
      
              System.out.println(s2);
              System.out.println(s2.getSname());
              System.out.println(s2.getBirthday());
      
              // 修改s2的值
              System.out.println("--------------------------");
              s2.setSname("多里");
              System.out.println(s2);
              System.out.println(s2.getSname());
              System.out.println(s2.getBirthday());
          }
      }
      

4.2 深克隆

  • 代码

    • package com.sxt.prototype;
      
      import java.util.Date;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Sheep2.java
       * @time: 2020/2/6 15:53
       * @desc: 测试深复制
       */
      
      public class Sheep2 implements Cloneable{
          private String sname;
          private Date birthday;
      
          public String getSname() {
              return sname;
          }
      
          public void setSname(String sname) {
              this.sname = sname;
          }
      
          public Date getBirthday() {
              return birthday;
          }
      
          public void setBirthday(Date birthday) {
              this.birthday = birthday;
          }
      
          public Sheep2(String sname, Date birthday) {
              this.sname = sname;
              this.birthday = birthday;
          }
      
          public Sheep2() {
          }
      
          @Override
          protected Object clone() throws CloneNotSupportedException {
              // 直接调用object对象的clone()方法
              Object obj = super.clone();
      
              // 添加如下代码实现深复制
              Sheep2 s = (Sheep2) obj;
              // 把属性也进行克隆
              s.birthday = (Date) this.birthday.clone();
      
              return obj;
          }
      }
      
    • 客户端同上

  • 区别

    • 浅克隆和深克隆的区别

      img

    • 运行结果区别:浅克隆只是复制了生日变量对应的地址,这样即使值改变了,地址相同,则获得的生日的值也相同;深克隆将生日变量复制了一份,两个地址不同,因此原型的值变了,与复制的值无关,因此生日的值依然是之前复制的值。

  • 利用序列化和反序列化的技术实现深克隆!

    package com.sxt.prototype;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.util.Date;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Client.java
     * @time: 2020/2/6 15:57
     * @desc: 测试原型模式(深复制),使用序列化和反序列化的方式实现深复制
     */
    
    public class Client3 {
        public static void main(String[] args) throws Exception {
            Date d = new Date(3333333333L);
            Sheep1 s1 = new Sheep1("少理", d);
            System.out.println(s1);
            System.out.println(s1.getSname());
            System.out.println(s1.getBirthday());
    
            // 两个是不同的对象,但是值是一样的!
            // Sheep1 s2 = (Sheep1) s1.clone();
            // 这里用序列化和反序列化实现深复制,所以用的是Sheep1,即浅复制的类
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(s1);
            byte[] bytes = bos.toByteArray();
    
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bis);
            Sheep1 s2 = (Sheep1) ois.readObject();
    
            // 修改s1生日
            d.setTime(22222222222L);
            System.out.println("--------------------------");
            System.out.println(s1.getBirthday());
    
            System.out.println(s2);
            System.out.println(s2.getSname());
            System.out.println(s2.getBirthday());
        }
    }
    
  • 短时间大量创建对象时,原型模式和普通new方式效率测试:如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!

  • 开发中的应用场景

    • 原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
    • spring中bean的创建实际就是两种:单例模式和原型模式。(当然原型模式需要和工厂模式搭配起来,即原来是new一个对象,改为clone一个对象)

创建型模式总结

  • 创建型模式:都是来帮助我们创建对象的!

    img

5. 适配器模式

  • 结构型模式:

    • 核心作用:是从程序的结构上实现送耦合,从而可以扩大整体的类结构,用来解决更大的问题
    • 分类:适配器模式、代理模式、桥接模式、装饰模式、组合模式、外观模式、享元模式
  • 什么是适配器模式(adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

  • 模式中的角色:

    • 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
    • 需要适配的类(Adaptee):需要适配的类或适配者类。
    • 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。
  • 代码:

    • 被适配的类(没有usb插口的键盘)

      package com.sxt.adapter;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Adaptee.java
       * @time: 2020/2/7 11:01
       * @desc: 被适配的类,相当于PS/2键盘
       */
      
      public class Adaptee {
          public void request(){
              System.out.println("可以完成客户请求的需要的功能!");
          }
      }
      
    • 客户端(只有usb接口的笔记本电脑)

      package com.sxt.adapter;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/7 11:02
       * @desc: 客户类,相当于笔记本,只有USB接口
       */
      
      public class Client {
          public void test1(Target t){
              t.handleReq();
          }
      
          public static void main(String[] args){
              Client c = new Client();
              Adaptee a = new Adaptee();
      
              // 方式1:类适配器方式
              Target t1 = new Adapter();
              // 方式2:对象适配器方式
              Target t2 = new Adapter2(a);
      
              c.test1(t2);
          }
      }
      
    • 接口:usb插口

      package com.sxt.adapter;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Target.java
       * @time: 2020/2/7 11:02
       * @desc: 相当于USB插口
       */
      
      public interface Target {
          void handleReq();
      }
      

5.1 类适配器方式

package com.sxt.adapter;

/**
 * @author: Li Tian
 * @contact: litian_cup@163.com
 * @software: IntelliJ IDEA
 * @file: Adapter.java
 * @time: 2020/2/7 11:03
 * @desc: 【类适配器方式】适配器,相当于把键盘转换成usb接口的转接器
 */

public class Adapter extends Adaptee implements Target{
    // 我的理解:把适配器变成键盘(子类),并实现USB接口-->打字

    @Override
    public void handleReq() {
        super.request();
    }
}

5.2 对象适配器方式

package com.sxt.adapter;

/**
 * @author: Li Tian
 * @contact: litian_cup@163.com
 * @software: IntelliJ IDEA
 * @file: Adapter2.java
 * @time: 2020/2/7 11:07
 * @desc: 【对象组合的方式,对象适配器方式】适配器
 */

public class Adapter2 implements Target{
    // 我的理解:把原来不兼容的键盘接口变成Target(即USB接口)

    private Adaptee adaptee;

    @Override
    public void handleReq() {
        adaptee.request();
    }

    public Adapter2(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
}
  • 工作中的场景:

    • 经常用来做旧系统改造和升级
    • 如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系统的数倍。
  • 我们学习中见过的场景

    • java.io.InputStreamReader(InputStream)
    • java.io.OutputStreamWriter(OutputStream)
  • 以对象适配器为例,绘制UML类图

    img

6. 代理模式

  • Proxy Pattern
  • 核心作用
    • 通过代理,控制对对象的访问
    • 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即AOP的微观实现);从而实现将统一流程代码放到代理类中处理。
    • AOP(Aspect Oriented Programming):面向切面编程的核心实现机制。
      • 它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。
      • 常用术语:
        • 切面:Aspect,其实就是共有功能的实现。
        • 通知:Advice,是切面的具体实现。
        • 连接点:Jointpoint,就是程序在运行过程中能够插入切面的地点。
        • 切入点:Pointcut,用于定义通知应该切入到哪些连接点上。
        • 目标对象:Target,就是那些即将切入切面的对象,也就是那些被通知的对象。
        • 代理对象:Proxy,将通知应用到目标对象之后被动态创建的对象。
        • 织入:Weaving,将切面应用到目标对象从而创建一个新的代理对象的过程。
      • 开源的AOP框架:AspectJ
  • 核心角色
    • 抽象角色:
      • 定义代理角色和真是角色的公共对外方法
    • 真实角色:
      • 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
      • 关注真正的业务逻辑!
    • 代理角色:
      • 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
      • 将统一的流程控制放到代理角色中处理。
    • 应用场景:
      • 安全代理:屏蔽对真实角色的直接访问。
      • 远程代理:通过代理类处理远程方法调用(RMI)。
      • 延迟代理:先加载轻量级的代理对象,真正需要再加载真实对象。(比如你要开发一个大文档查看系统,大文档中有大的图片,有可能一个图片有100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。)
    • 分类:
      • 静态代理(静态定义代理类)
      • 动态代理(动态生成代理类)
        • JDK自带的动态代理
        • javassist字节码操作库实现
        • CGLIB
        • ASM(底层使用指令,可维护性较差)

6.1 静态代理

  • 代码:

    • 明星接口

      package com.sxt.proxy.staticproxy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Star.java
       * @time: 2020/2/7 13:51
       * @desc:
       */
      
      public interface Star {
          // 面谈
          void confer();
          // 签合同
          void signContract();
          // 订票
          void bookTicket();
          // 唱歌
          void sing();
          // 收钱
          void collectMoney();
      }
      
    • 真实明星

      package com.sxt.proxy.staticproxy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: RealStar.java
       * @time: 2020/2/7 13:53
       * @desc:
       */
      
      public class RealStar implements Star {
      
          private String name = "真实明星";
          @Override
          public void confer() {
              System.out.println(name + "面谈!");
          }
      
          @Override
          public void signContract() {
              System.out.println(name + "签合同");
          }
      
          @Override
          public void bookTicket() {
              System.out.println(name + "订票");
          }
      
          @Override
          public void sing() {
              System.out.println(name + "唱歌");
          }
      
          @Override
          public void collectMoney() {
              System.out.println(name + "收钱");
          }
      }
      
    • 代理经纪人

      package com.sxt.proxy.staticproxy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ProxyStar.java
       * @time: 2020/2/7 13:55
       * @desc:
       */
      
      public class ProxyStar implements Star {
          private String name = "经纪人";
          private Star star;
      
          public ProxyStar(Star star) {
              this.star = star;
          }
      
          @Override
          public void confer() {
              System.out.println(name + "面谈!");
          }
      
          @Override
          public void signContract() {
              System.out.println(name + "签合同");
          }
      
          @Override
          public void bookTicket() {
              System.out.println(name + "订票");
          }
      
          @Override
          public void sing() {
              star.sing();
          }
      
          @Override
          public void collectMoney() {
              System.out.println(name + "收钱");
          }
      }
      
    • 客户端

      package com.sxt.proxy.staticproxy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/7 13:57
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              Star real = new RealStar();
              Star proxy = new ProxyStar(real);
              proxy.confer();
              proxy.signContract();
              proxy.bookTicket();
              proxy.sing();
              proxy.collectMoney();
          }
      }
      

6.2 动态代理

  • 动态生成代理类

  • 动态代理相比于静态代理的优点:抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样我们可以更加灵活和统一的处理众多的方法。

  • JDK自带的动态代理

    • java.lang.reflect.Proxy
      • 作用:动态生成代理类和对象
    • java.lang.reflect.InvocationHandler(处理器接口)
      • 可以通过invoke方法实现对真实角色的代理访问。
      • 每次通过Proxy生成代理类对象时都要指出对应的处理器对象。
  • 代码:

    • 明星接口和真实明星接口同上

    • 动态代理类

      package com.sxt.proxy.dynamicproxy;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: StarHandler.java
       * @time: 2020/2/7 16:19
       * @desc:
       */
      
      public class StarHandler implements InvocationHandler {
      
          Star realStar;
      
          public StarHandler(Star realStar) {
              this.realStar = realStar;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              Object obj = null;
              System.out.println("真正的方法执行前!");
              System.out.println("面谈,签合同等。。。");
              if (method.getName().equals("sing")) {
                  obj = method.invoke(realStar, args);
              }
              System.out.println("真正的方法执行后!");
              System.out.println("收钱!");
      
              return obj;
          }
      }
      
    • 客户端

      package com.sxt.proxy.dynamicproxy;
      
      import java.lang.reflect.Proxy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/7 16:22
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              Star realStar = new RealStar();
              StarHandler handler = new StarHandler(realStar);
      
              Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, handler);
      
              proxy.sing();
          }
      }
      

7. 桥接模式

  • bridge pattern

  • 场景:商场系统中常见的商品分类,以电脑为类,如何良好的处理商品分类销售的问题?

  • 我们可以用多继承结构实现下图关系

    graph TB A[电脑] --> B[台式机] A --> C[笔记本] A --> D[平板电脑] B --> B1[联想台式机] B --> B2[戴尔台式机] B --> B3[神舟台式机] C --> C1[联想笔记本] C --> C2[戴尔笔记本] C --> C3[神舟笔记本] D --> D1[联想平板] D --> D2[戴尔平板] D --> D3[神舟平板]
  • 问题:

    • 扩展性问题(类个数膨胀问题):
      • 如果要增加一个新的电脑类型智能手机,则要增加各个品牌下面的类。
      • 如果要增加一个新的品牌,也要增加各种电脑类型的类。
    • 违反单一职责原则:一个类:联想笔记本,有两个引起这个类变化的原因。
  • 桥接模式核心要点:处理多层集成结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联。

    img

  • 桥接模式总结:

    • 桥接模式可以取代多层继承的方案。多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大地减少子类的个数,从而降低管理和维护的成本。
    • 桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则。
    • 桥接模式可以完成java不能多重继承的功能。
  • 桥接模式实际开发中应用场景

    • JDBC驱动查程序
    • AWT中的Peer架构
    • 银行日志管理:
      • 格式分类:操作日志、交易日志、异常日志
      • 距离分类:本地记录日志、异地记录日志
    • 人力资源系统中的奖金计算模块:
      • 奖金分类:个人奖金、团体奖金、激励奖金
      • 部分分类:人事部门、销售部门、研发部门
    • OA系统中的消息处理:
      • 业务类型:普通消息、加急消息、特急消息
      • 发送消息方式:系统内消息、手机短信、邮件
  • 不用桥接模式的话

    package com.sxt.bridge;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Computer.java
     * @time: 2020/2/9 14:44
     * @desc:
     */
    
    public interface Computer {
        void sale();
    }
    
    class Desktop implements Computer{
    
        @Override
        public void sale() {
            System.out.println("销售台式机!");
        }
    }
    
    class Laptop implements Computer{
    
        @Override
        public void sale() {
            System.out.println("销售笔记本!");
        }
    }
    
    class Pad implements Computer{
    
        @Override
        public void sale() {
            System.out.println("销售平板电脑!");
        }
    }
    
    class LenovoDesktop extends Desktop{
        @Override
        public void sale() {
            System.out.println("销售联想台式机!");;
        }
    }
    
    class LenovoLaptop extends Laptop{
        @Override
        public void sale() {
            System.out.println("销售联想笔记本!");;
        }
    }
    
    class LenovoPad extends Pad{
        @Override
        public void sale() {
            System.out.println("销售联想平板电脑!");;
        }
    }
    
    class ShenzhouDesktop extends Desktop{
        @Override
        public void sale() {
            System.out.println("销售神舟台式机!");;
        }
    }
    
    class ShenzhouLaptop extends Laptop{
        @Override
        public void sale() {
            System.out.println("销售神舟笔记本!");;
        }
    }
    
    class ShenzhouPad extends Pad{
        @Override
        public void sale() {
            System.out.println("销售神舟平板电脑!");;
        }
    }
    
    class DellDesktop extends Desktop{
        @Override
        public void sale() {
            System.out.println("销售戴尔台式机!");;
        }
    }
    
    class DellLaptop extends Laptop{
        @Override
        public void sale() {
            System.out.println("销售戴尔笔记本!");;
        }
    }
    
    class DellPad extends Pad{
        @Override
        public void sale() {
            System.out.println("销售戴尔平板电脑!");;
        }
    }
    
  • 使用桥接模式

    • 品牌

      package com.sxt.bridge;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Brand.java
       * @time: 2020/2/9 14:56
       * @desc: 品牌
       */
      
      public interface Brand {
          void sale();
      }
      
      class Lenovo implements Brand{
      
          @Override
          public void sale() {
              System.out.println("销售联想电脑!");
          }
      }
      
      class Dell implements Brand{
      
          @Override
          public void sale() {
              System.out.println("销售戴尔电脑!");
          }
      }
      
    • 电脑类型的维度

      package com.sxt.bridge;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ComputerBridge.java
       * @time: 2020/2/9 14:58
       * @desc: 电脑类型的维度
       */
      
      public class ComputerBridge {
          protected Brand brand;
      
          public ComputerBridge(Brand brand) {
              this.brand = brand;
          }
      
          public void sale(){
              brand.sale();
          }
      }
      
      class Desktop2 extends ComputerBridge{
      
          public Desktop2(Brand brand) {
              super(brand);
          }
      
          @Override
          public void sale() {
              super.sale();
              System.out.println("销售台式机!");
          }
      }
      
      class Laptop2 extends ComputerBridge{
      
          public Laptop2(Brand brand) {
              super(brand);
          }
      
          @Override
          public void sale() {
              super.sale();
              System.out.println("销售笔记本电脑!");
          }
      }
      
    • 客户顿

      package com.sxt.bridge;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/9 15:01
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              // 销售联想的笔记本电脑
              ComputerBridge c = new Laptop2(new Lenovo());
              c.sale();
              // 销售戴尔的台式机
              ComputerBridge c2 = new Desktop2(new Dell());
              c2.sale();
          }
      }
      

8. 组合模式

  • composite patern

  • 使用组合模式的场景:把部分和整体的关系用树形结构来表示,从而使得客户端可以使用统一的方式处理部分对象和整体对象

  • 组合模式核心:

    • 抽象(Component)构建角色:定义了叶子和容器构建的共同点
    • 叶子(Leaf)构建角色:无子节点
    • 容器(Composite)构建角色:有容器特征,可以包含子节点
  • 抽象组件构成代码

    package com.sxt.composite;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Component.java
     * @time: 2020/2/9 16:39
     * @desc: 抽象组件
     */
    
    public interface Component {
        void operation();
    }
    
    // 叶子组件
    interface Leaf extends Component {
    
    }
    // 容器组件
    interface Composite extends Component{
        void add(Component c);
        void remove(Component c);
        Component getChild(int index);
    }
    
  • 组合模式工作流程分析:

    • 组合模式为处理树形结构提供了完美的解决方法,描述了如何将容器和叶子进行递归组合,使得用户在使用时可以一致性的对待容器和叶子。
    • 当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员,并调用执行。其中,使用了递归调用的机制对整个结构进行处理。
  • 使用组合模式,模拟杀毒软件架构设计

    • 抽象构建

      package com.sxt.composite;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: AbstarctFile.java
       * @time: 2020/2/9 16:46
       * @desc: 抽象构建
       */
      
      public interface AbstarctFile {
          void killVirus();
      }
      
      class ImageFile implements AbstarctFile{
          private String name;
      
          public ImageFile(String name) {
              this.name = name;
          }
      
          @Override
          public void killVirus() {
              System.out.println("---图像文件:" + name + ",进行查杀!");
          }
      }
      
      class TextFile implements AbstarctFile{
          private String name;
      
          public TextFile(String name) {
              this.name = name;
          }
      
          @Override
          public void killVirus() {
              System.out.println("---文本文件:" + name + ",进行查杀!");
          }
      }
      
      class VideoFile implements AbstarctFile{
          private String name;
      
          public VideoFile(String name) {
              this.name = name;
          }
      
          @Override
          public void killVirus() {
              System.out.println("---视频文件:" + name + ",进行查杀!");
          }
      }
      
      class Folder implements AbstarctFile{
          private String name;
          // 定义容器,用来存放本容器构建下的子节点
          private List<AbstarctFile> list = new ArrayList<>();
      
          public Folder(String name) {
              this.name = name;
          }
      
          public void add(AbstarctFile file){
              list.add(file);
          }
      
          public void remove(AbstarctFile file){
              list.remove(file);
          }
      
          public AbstarctFile getChild(int index){
              return list.get(index);
          }
      
          @Override
          public void killVirus() {
              System.out.println("---文件夹:" + name + ",进行查杀!");
              for (AbstarctFile file: list){
                  file.killVirus();
              }
          }
      }
      
    • 客户端

      package com.sxt.composite;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/9 16:54
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              AbstarctFile f2, f3, f5, f6;
              Folder f1 = new Folder("我的收藏");
              f2 = new ImageFile("我的头像.jpg");
              f3 = new TextFile("Hello.txt");
              f1.add(f2);
              f1.add(f3);
      
              Folder f4 = new Folder("电影");
              f5 = new VideoFile("神雕侠侣.avi");
              f6 = new VideoFile("笑傲江湖.avi");
      
              f4.add(f5);
              f4.add(f6);
              f1.add(f4);
      
              f1.killVirus();
          }
      }
      
  • 开发中的应用场景

    • 操作系统的资源管理器
    • GUI中的容器层次图
    • XML文件解析
    • OA系统中,组织结构的处理
    • Junit单元测试框架:底层设计就是典型的组合模式,TestCase(叶子)、TestSuite(容器)、Test接口(抽象)

9. 装饰器模式

  • decorator pattern

  • 职责:

    • 动态的为一个对象增加新的功能。
    • 装饰器模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
  • 实现细节:

    • Component抽象构件角色:真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。
    • ConcreteComponent具体构件角色(真实对象):io流中的FileInputStream、FileOutoutStream
    • Decorator装饰角色:持有一个抽象构件的应用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调动前后增加新的功能。
    • ConcreteDecorator具体装饰角色:负责给构件对象增加新的责任。
  • 代码

    • 抽象组件

      package com.sxt.decorator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ICar.java
       * @time: 2020/2/9 18:07
       * @desc: 抽象组件
       */
      
      public interface ICar {
          void move();
      }
      
      // 真实对象
      class Car implements ICar{
      
          @Override
          public void move() {
              System.out.println("陆地上跑");
          }
      }
      
      //装饰角色
      class SuperCar implements ICar{
          private ICar car;
      
          public SuperCar(ICar car) {
              this.car = car;
          }
      
          @Override
          public void move() {
              car.move();
          }
      }
      
      // 具体装饰对象
      class FlyCar extends SuperCar{
      
          public FlyCar(ICar car) {
              super(car);
          }
      
          public void fly(){
              System.out.println("天上飞!");
          }
      
          @Override
          public void move() {
              super.move();
              fly();
          }
      }
      
      class WaterCar extends SuperCar{
      
          public WaterCar(ICar car) {
              super(car);
          }
      
          public void swim(){
              System.out.println("水上游!");
          }
      
          @Override
          public void move() {
              super.move();
              swim();
          }
      }
      
      class AICar extends SuperCar{
      
          public AICar(ICar car) {
              super(car);
          }
      
          public void autoMove(){
              System.out.println("无人驾驶!");
          }
      
          @Override
          public void move() {
              super.move();
              autoMove();
          }
      }
      
    • 客户端

      package com.sxt.decorator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/9 18:13
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              Car car = new Car();
              car.move();
      
              System.out.println("增加新的功能,飞行!");
              FlyCar flyCar = new FlyCar(car);
              flyCar.move();
      
              System.out.println("增加新的功能,游泳!");
              WaterCar waterCar = new WaterCar(car);
              waterCar.move();
      
              System.out.println("增加新的功能,飞行和游泳!");
              WaterCar car2 = new WaterCar(new FlyCar(car));
              car2.move();
          }
      }
      
  • UML类图

    img

  • 开发中使用的场景

    • IO中输入流和输出流的设计
    • Swing包中图形界面构件功能
    • Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HTTPServletRequestWrapper, HTTPServletRequestWrapper类,增强了request对象的功能
    • Struts2中,request,response,session对象的处理
  • IO流实现细节

    • Component抽象构件角色:io流中的InputStream、OutputStream、Reader、Writer
    • ConcreteComponent具体构件角色:io流中的FileInputStream、FileOutputStream
    • Decorator装饰角色:持有一个抽象构件的引用:io流中的FilterInputStream、FilterOutputStream
    • ConcreteDecorator具体装饰角色:负责给构件对象增加新的责任。io流中的BufferedOutputStream、BufferedInputStream等。
  • 总结:

    • 装饰模式(Decorator)也叫包装器模式(Wrapper)
    • 装饰模式降低系统的耦合度,可以动态的增加或删减对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化,以便增加新的具体构件类和具体装饰类。
  • 优点:

    • 扩展对象功能,比继承灵活,不会导致类的个数急剧增加
    • 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象
    • 具体构件类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类
  • 缺点:

    • 产生很多小对象。大量小对象占据内存,一定程度上影响性能。
    • 装饰模式容易出错,调试排查比较麻烦。
  • 装饰模式和桥接模式的区别

    两个模式都是为了解决过多子类对象的问题。但是他们的诱因不一样。

    • 桥接模式是对象自身现有机制沿多个维度变化,是既有部分不稳定
    • 装饰模式是为了增加新的功能。
  • 我的理解是:

    • 桥接模式把A维度(品牌)接口当做(电脑)的属性,然后B维度(电脑的类别)继承类,这样A维度通过实现不同的品牌来扩展,B维度通过生成不同的类别的子类来扩展。
    • 装饰器模式的主角是(汽车),为了给这个类增加新的功能,新建了一个装饰器类,装饰器类的属性就是主角类。新功能装饰器相当于实现了新功能的主角类,并继承装饰器类,因此实现了给主角类添加新功能的目的。
    • 没有装饰者和被装饰者的主次区别,桥接和被桥接者是平等的,不用继承自同一个父类。(即桥接可以互换)
    • 桥接模式不用使用同一个接口;装饰模式用同一个接口装饰,接口在父类中定义。
    • 都可以处理类扩散的情况。

10. 外观模式

  • 迪米特法则(最少知识原则):一个软件实体应当尽可能少的与其他实体发生相互作用。

  • 外观模式核心:为子系统提供统一的入口。封装子系统的复杂性,便于客户端调用。

  • 公司注册流程:

    • 不使用外观模式

      img

    • 使用外观模式

      img

  • 开发中常见的场景

    • 频率很高。哪里都会遇到。各种技术和框架中,都有外观模式的使用。
    • JDBC封装后的,commons提供的DBUtils类,Hibernate提供的工具类、Spring JDBC工具类等。

11. 享元模式

  • FlyWeight Pattern

  • 场景:内存属于稀缺资源,不要随便浪费。如果有很多个完全相同或相似的对象,我们可以通过享元模式,节省内存。

  • 核心:

    • 享元模式以共享的方式高效地支持大量细粒度对象的重用。
    • 享元对象能够做到共享的关键是区分了内部状态和外部状态。
      • 内部状态:可以共享,不会随环境变化而改变
      • 外部状态:不可以共享,会随环境变化而改变
  • 围棋软件设计:每个围棋棋子都是一个对象,有如下属性:

    graph LR A[颜色] --> station[*内部状态*可以共享] B[形状] --> station C[大小] --> station D[位置] --> station_non[*外部状态*不可以共享]
  • 享元模式实现:

    • FlyweightFactory享元工厂类:

      创建并管理享元对象,享元池一般设计成键值对

    • FlyWeight抽象享元类

      通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,设置外部状态。

    • ConcreteFlyWeight具体享元类

      为内部状态提供成员变量进行存储

    • UnsharedConcreteFlyWeight非共享享元类

      不能被共享的子类可以设计为非共享享元类

  • 享元模式实现的UML图

img

  • 代码:

    • 享元类和实际享元实现类(这里实现了颜色共享)

      package com.sxt.flyweight;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ChessFlyWeight.java
       * @time: 2020/2/10 12:37
       * @desc: 享元类
       */
      
      public interface ChessFlyWeight {
          void setColor(String c);
          String getColor();
          void display(Coordinate c);
      }
      
      class ConcreteChess implements ChessFlyWeight{
          private String color;
      
          public ConcreteChess(String color) {
              this.color = color;
          }
      
          @Override
          public void setColor(String c) {
              this.color = c;
          }
      
          @Override
          public String getColor() {
              return color;
          }
      
          @Override
          public void display(Coordinate c) {
              System.out.println("棋子颜色:" + color);
              System.out.println("棋子位置:" + c.getX() + ", " + c.getY());
          }
      }
      
    • 外部类,这里是坐标,即非享元类

      package com.sxt.flyweight;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Coordinate.java
       * @time: 2020/2/10 12:37
       * @desc: 外部状态,UnsharedConcreteFlyWeight非共享享元类
       */
      
      public class Coordinate {
          private int x, y;
      
          public Coordinate(int x, int y) {
              this.x = x;
              this.y = y;
          }
      
          public int getX() {
              return x;
          }
      
          public void setX(int x) {
              this.x = x;
          }
      
          public int getY() {
              return y;
          }
      
          public void setY(int y) {
              this.y = y;
          }
      }
      
    • 享元工厂类(用来生成享元对象)

      package com.sxt.flyweight;
      
      
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ChessFlyWeightFactory.java
       * @time: 2020/2/10 12:43
       * @desc: 享元工厂类
       */
      
      public class ChessFlyWeightFactory {
          // 享元池
          private static Map<String, ChessFlyWeight> map = new HashMap<>();
          public static ChessFlyWeight getChess(String color){
              if(map.get(color) != null){
                  return map.get(color);
              }else{
                  ChessFlyWeight cfw = new ConcreteChess(color);
                  map.put(color, cfw);
                  return cfw;
              }
          }
      }
      
    • 客户端

      package com.sxt.flyweight;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/10 12:47
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("black");
              ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("black");
              System.out.println(chess1);
              System.out.println(chess2);
      
              // 增加外部状态的处理
              chess1.display(new Coordinate(10, 10));
              chess1.display(new Coordinate(20, 20));
          }
      }
      
  • 享元模式开发中应用的场景:

    • 享元模式由于其共享的特征,可以在任何“池”中操作,比如:线程池、数据库连接池。
    • String类的设计也是享元模式。
  • 享元模式的优点:

    • 极大减少内存中对象的数量
    • 相同或相似对象内存中只存一份,极大的节约资源,提高系统性能
    • 外部状态相对独立,不影响内部状态
  • 享元模式的缺点:

    • 模式较复杂,使程序逻辑复杂化
    • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间边长。用时间换取了空间。

结构型模式总结

img

12. 责任链模式

  • chain of responsibility pattern
  • 定义:将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。
  • 场景:
    • 打牌时,轮流出牌
    • 接力赛跑
    • 大学中,奖学金审批
    • 公司中,公文审批
  • 开发中常见的场景:
    • java中,异常机制就是一种责任链模式。一个try可以对应多个catch,当第一个catch不匹配类型,则自动跳到第二个catch。
    • JavaScript语言中,事件的冒泡和捕获机制。java语言中,事件的处理采用观察者模式。
    • Servlet开发中,过滤器的链式处理
    • Struts2中,拦截器的调用也是经典的责任链模式

12.1 链表方式img

  • 封装请假的基本信息

    package com.sxt.chainofresp;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LeaveRequest.java
     * @time: 2020/2/10 18:01
     * @desc: 封装请假的基本信息
     */
    
    public class LeaveRequest {
        private String empName;
        private int leaveDays;
        private String reason;
    
        public LeaveRequest(String empName, int leaveDays, String reason) {
            this.empName = empName;
            this.leaveDays = leaveDays;
            this.reason = reason;
        }
    
        public String getEmpName() {
            return empName;
        }
    
        public void setEmpName(String empName) {
            this.empName = empName;
        }
    
        public int getLeaveDays() {
            return leaveDays;
        }
    
        public void setLeaveDays(int leaveDays) {
            this.leaveDays = leaveDays;
        }
    
        public String getReason() {
            return reason;
        }
    
        public void setReason(String reason) {
            this.reason = reason;
        }
    }
    
  • 抽象类领导

    package com.sxt.chainofresp;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Leader.java
     * @time: 2020/2/10 18:02
         * @desc: 抽象类 领导
     */
    
    public abstract class Leader {
        protected String name;
            // 领导的下一个责任领导
        protected Leader nextLeader;
    
        public Leader(String name) {
            this.name = name;
        }
    
        // 设定责任链上的后继对象
        public void setNextLeader(Leader nextLeader) {
            this.nextLeader = nextLeader;
        }
    
        // 处理请求的核心业务方法
        public abstract void handleRequest(LeaveRequest request);
    }
    
  • 上级

    package com.sxt.chainofresp;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Director.java
     * @time: 2020/2/10 18:06
     * @desc:
     */
    
    public class Director extends Leader {
    
        public Director(String name) {
            super(name);
        }
    
        @Override
        public void handleRequest(LeaveRequest request) {
            if(request.getLeaveDays() < 3){
                System.out.println("员工:" + request.getEmpName() + "请假:" + request.getLeaveDays() + "天,理由是:" + request.getReason());
                System.out.println("主任:" + this.name + "审批通过!");
            }else{
                if(this.nextLeader != null){
                    this.nextLeader.handleRequest(request);
                }
            }
        }
    }
    
  • 经理

    package com.sxt.chainofresp;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Manager.java
     * @time: 2020/2/10 18:06
     * @desc: 经理
     */
    
    public class Manager extends Leader {
    
        public Manager(String name) {
            super(name);
        }
    
        @Override
        public void handleRequest(LeaveRequest request) {
            if(request.getLeaveDays() < 10){
                System.out.println("员工:" + request.getEmpName() + "请假:" + request.getLeaveDays() + "天,理由是:" + request.getReason());
                System.out.println("经理:" + this.name + "审批通过!");
            }else{
                if(this.nextLeader != null){
                    this.nextLeader.handleRequest(request);
                }
            }
        }
    }
    
  • 总经理

    package com.sxt.chainofresp;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Manager.java
     * @time: 2020/2/10 18:06
     * @desc: 总经理
     */
    
    public class GeneralManager extends Leader {
    
        public GeneralManager(String name) {
            super(name);
        }
    
        @Override
        public void handleRequest(LeaveRequest request) {
            if(request.getLeaveDays() < 30){
                System.out.println("员工:" + request.getEmpName() + "请假:" + request.getLeaveDays() + "天,理由是:" + request.getReason());
                System.out.println("总经理:" + this.name + "审批通过!");
            }else{
                System.out.println("莫非" + request.getEmpName() + "想辞职!竟然请假" + request.getLeaveDays() + "天!");
            }
        }
    }
    
  • 客户端

    package com.sxt.chainofresp;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Client.java
     * @time: 2020/2/10 18:11
     * @desc:
     */
    
    public class Client {
        public static void main(String[] args){
            Leader a = new Director("张三");
            Leader b = new Manager("李四");
            Leader c = new GeneralManager("王五");
    
            // 组织责任链对象关系
            a.setNextLeader(b);
            b.setNextLeader(c);
    
            // 开始请假操作
            LeaveRequest req1 = new LeaveRequest("Tom", 10, "回家睡觉!");
            a.handleRequest(req1);
        }
    }
    

12.2 非链表方式

  • 通过集合、数组生成责任链更加实用!实际上,很多项目中,每个具体的Handler并不是由开发团队定义的,而是项目上线后由外部单位追加的,所以实用链表方式定义COR链就很困难。

13. 迭代器模式

  • iterator pattern

  • 场景:

    • 提供一种可以遍历聚合对象的方式。又称为:游标cursor模式
    • 聚合对象:存储数据
    • 迭代器:遍历数据
  • 基本案例:

    • 实现正向遍历的迭代器

      • 自定义的迭代器接口

        package com.sxt.iterator;
        
        /**
         * @author: Li Tian
         * @contact: litian_cup@163.com
         * @software: IntelliJ IDEA
         * @file: MyIterator.java
         * @time: 2020/2/14 18:42
         * @desc: 自定义的迭代器接口
         */
        
        public interface MyIterator {
            // 将游标指向第一个元素
            void first();
            // 将游标指向下一个元素
            void next();
            // 判断是否存在下一个元素
            boolean hasNext();
        
            boolean ifFirst();
            boolean isLast();
        
            // 获取当前游标指向的对象
            Object getCurrentObj();
        }
        
      • 自定义的聚合类

        package com.sxt.iterator;
        
        import java.util.ArrayList;
        import java.util.List;
        
        /**
         * @author: Li Tian
         * @contact: litian_cup@163.com
         * @software: IntelliJ IDEA
         * @file: ConcreteMyAggregate.java
         * @time: 2020/2/14 18:45
         * @desc: 自定义的聚合类
         */
        
        public class ConcreteMyAggregate {
            private List<Object> list = new ArrayList<Object>();
        
            public void addObject(Object obj) {
                this.list.add(obj);
            }
        
            public void removeObject(Object obj) {
                this.list.remove(obj);
            }
        
            public List<Object> getList() {
                return list;
            }
        
            public void setList(List<Object> list) {
                this.list = list;
            }
        
            // 获得迭代器
            public MyIterator createIterator(){
                return new ConcreteIterator();
            }
        
            // 使用内部类定义迭代器,可以直接使用外部类的属性
            private class ConcreteIterator implements MyIterator {
                // 定义游标用于记录遍历时的位置
                private int cursor;
        
                @Override
                public void first() {
                    cursor = 0;
                }
        
                @Override
                public void next() {
                    if (cursor < list.size()) {
                        cursor++;
                    }
                }
        
                @Override
                public boolean hasNext() {
                    return cursor < list.size();
                }
        
                @Override
                public boolean ifFirst() {
                    return cursor == 0;
                }
        
                @Override
                public boolean isLast() {
                    return cursor == (list.size() - 1);
                }
        
                @Override
                public Object getCurrentObj() {
                    return list.get(cursor);
                }
            }
        }
        
      • 客户端

        package com.sxt.iterator;
        
        /**
         * @author: Li Tian
         * @contact: litian_cup@163.com
         * @software: IntelliJ IDEA
         * @file: Client.java
         * @time: 2020/2/14 18:53
         * @desc:
         */
        
        public class Client {
            public static void main(String[] args){
                ConcreteMyAggregate cma = new ConcreteMyAggregate();
                cma.addObject("aa");
                cma.addObject("bb");
                cma.addObject("cc");
        
                MyIterator iter = cma.createIterator();
                while(iter.hasNext()){
                    System.out.println(iter.getCurrentObj());
                    iter.next();
                }
            }
        }
        
    • 实现逆向遍历的迭代器

  • 开发中常见的场景:

    • JDK内置的迭代器(List/Set)

14. 中介者模式

  • Mediator Pattern

  • 场景:总经理协调各个部门之间的关系,起到一个中介的作用。

  • 核心:

    • 如果一个系统中对象之间的联系呈现网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象称为“同事对象”
    • 我们可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将复杂的网络结构化解为星形结构。
  • 代码类图

    img

  • 代码:

    • 总经理类的接口

      package com.sxt.mediator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Mediator.java
       * @time: 2020/2/14 19:07
       * @desc: 总经理类的接口
       */
      
      public interface Mediator {
          void regitster(String dname, Department d);
          void command(String dname);
      }
      
    • 同事类接口

      package com.sxt.mediator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Department.java
       * @time: 2020/2/14 19:08
       * @desc: 同事类的接口
       */
      
      public interface Department {
          // 做本部门的事情
          void selfAction();
          // 向总经理发出申请
          void outAction();
      }
      
    • 研发部门

      package com.sxt.mediator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Development.java
       * @time: 2020/2/14 19:09
       * @desc:
       */
      
      public class Development implements Department {
          // 持有中介者(总经理)的引用
          private Mediator m;
      
          public Development(Mediator m) {
              this.m = m;
              m.regitster("development", this);
          }
      
          @Override
          public void selfAction() {
              System.out.println("专心搞科研!");
          }
      
          @Override
          public void outAction() {
              System.out.println("向总经理汇报工作!需要资金支持!");
          }
      }
      
    • 财务部门

      package com.sxt.mediator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Finacial.java
       * @time: 2020/2/14 19:11
       * @desc:
       */
      
      public class Finacial implements Department {
          // 持有中介者(总经理)的引用
          private Mediator m;
      
          public Finacial(Mediator m) {
              this.m = m;
              m.regitster("finacial", this);
          }
      
          @Override
          public void selfAction() {
              System.out.println("数钱!");
          }
      
          @Override
          public void outAction() {
              System.out.println("钱太多了啊总经理!怎么花!");
          }
      }
      
    • 市场部门

      package com.sxt.mediator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Market.java
       * @time: 2020/2/14 19:13
       * @desc:
       */
      
      public class Market implements Department {
          // 持有中介者(总经理)的引用
          private Mediator m;
      
          public Market(Mediator m) {
              this.m = m;
              m.regitster("market", this);
          }
      
          @Override
          public void selfAction() {
              System.out.println("跑去接项目!");
          }
      
          @Override
          public void outAction() {
              System.out.println("承接项目的进度!需要资金支持!");
              m.command("finacial");
          }
      }
      
    • 总经理

      package com.sxt.mediator;
      
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: President.java
       * @time: 2020/2/14 19:14
       * @desc:
       */
      
      public class President implements Mediator {
          private Map<String, Department> map = new HashMap<String, Department>();
      
          @Override
          public void regitster(String dname, Department d) {
              map.put(dname, d);
          }
      
          @Override
          public void command(String dname) {
              map.get(dname).selfAction();
          }
      }
      
    • 客户端

      package com.sxt.mediator;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/14 19:17
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              Mediator m = new President();
              Market market = new Market(m);
              Development devp = new Development(m);
              Finacial f = new Finacial(m);
      
              market.selfAction();
              market.outAction();
          }
      }
      
  • 中介者模式的本质:解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系。

  • 开发中常见的场景:

    • MVC模式(其中的C,控制器就是一个中介者对象。M和V都和它打交道)
    • 窗口游戏程序,窗口软件开发中窗口也是一个中介者对象。
    • 图形界面开发GUI中,多个组件之间的交互,可以通过引入一个中介者对象来解决,可以是整体的窗口对象或者DOM对象。
    • java.lang.reflect.Method#invoke()

15. 命令模式

  • command pattern

  • 介绍:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。也称为:动作Action模式、事务transaction模式。

  • 结构:

    • Command抽象命令类

    • ConcreteCommand具体命令类

    • Invoker调用者/请求者:请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接受者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute(),间接调用接受者的相关操作。

    • Receiver接受者

      • 接受者执行与请求相关的操作,具体实现对请求的业务处理。
      • 未抽象前,实际执行操作内容的对象。
    • Client客户类:在客户类中需要创建调用者对象、具体命令类对象,在创建具体命令对象时指定对应的接受者。发送者和接受者之间没有直接联系,都通过命令对象间接调用。

    • 类图

      img

  • 代码:

    • 真正的命令执行者

      package com.sxt.command;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Receiver.java
       * @time: 2020/2/15 14:28
       * @desc: 真正的命令执行者
       */
      
      public class Receiver {
          public void action(){
              System.out.println("Receiver.action()");
          }
      }
      
    • 命令管理

      package com.sxt.command;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Command.java
       * @time: 2020/2/15 14:29
       * @desc: 命令管理
       */
      
      public interface Command {
          // 这个方法是一个返回结果为空的方法
          // 实际项目中,可以根据需求设计多个不同的方法
          void execute();
      }
      
      class ConcreteCommand implements Command{
          // 命令的真正执行者
          private Receiver receiver;
      
          public ConcreteCommand(Receiver receiver) {
              this.receiver = receiver;
          }
      
          @Override
          public void execute() {
              // 命令真正执行前或后,执行相关的处理
              receiver.action();
          }
      }
      
    • 命令的调用者和发起者

      package com.sxt.command;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Invoke.java
       * @time: 2020/2/15 14:34
       * @desc: 命令的调用者和发起者
       */
      
      public class Invoke {
          // 也可以通过容器放很多很多命令,进行批处理。比如数据库底层的事务管理
          private Command command;
      
          public Invoke(Command command) {
              this.command = command;
          }
      
          // 业务方法,用于调用命令类的方法
          public void call(){
              command.execute();
          }
      }
      
    • 客户端

      package com.sxt.command;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/15 14:36
       * @desc:
       */
      
      public class Client {
          public static void main(String[] args){
              Command c = new ConcreteCommand(new Receiver());
              Invoke i = new Invoke(c);
              i.call();
          }
      }
      
  • 开发中常见的场景:

    • Struts2中,action的整个调用过程中就有命令模式
    • 数据库事务机制的底层实现
    • 命令的撤销和恢复,word那种,执行前保存,执行后可以回滚

16. 解释器模式

  • interpreter pattern
  • 介绍:
    • 是一种不常用的设计模式
    • 用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的编译器和计时器设计。
    • 当我们需要开发一种新的语言时,可以考虑使用解释器模式。
    • 尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用Jruby,Groovy,java的js引擎来替代解释器的作用,弥补java语言的不足。
  • 开发中常见的场景:
    • EL表达式的处理
    • 正则表达式解释器
    • SQL语法的解释器
    • 数学表达式解析器:如现成的工具包:Math Expression String Parser、Expression4J等。

17. 访问者模式

  • Visitor Pattern
  • 模式动机:对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访问者其访问方式也有不同。
  • 定义:表示一个作用于某对象结果中的各元素的操作,它使我们可以在不改变每个元素的类的前提下定义作用于这些元素的新操作。
  • 开发中的场景(应用范围非常窄,了解即可):
    • XML文档解析器设计
    • 编译器的设计
    • 复杂集合对象的处理

18. 策略模式

  • strategy pattern

  • 场景:

    • 某个市场人员接到单后的报价策略(CRM系统中常见问题)。报价策略很复杂,可以简单作如下分类:
      • 普通客户小批量报价
      • 普通客户大批量报价
      • 老客户小批量报价
      • 老客户大批量报价
    • 具体选用哪个报价策略,这需要根据实际情况来确定。这时候,我们采用策略模式即可。
    • 一般可以先采用条件语句处理,但类型特别多,算法比较复杂时,整个条件控制代码会变得很长,难于维护。
  • 本质:分离算法,选择实现

  • 开发中常见的场景

    • JAVASE中GUI编程中的布局管理
    • Spring框架中,Resource接口,资源访问策略
    • javax.servlet.http.HttpServlet#service()
  • 策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某以问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。

  • 代码

    • 策略接口

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Strategy.java
       * @time: 2020/2/29 16:37
       * @desc:
       */
      
      public interface Strategy {
          public double getPrice(double standarPrice);
      }
      
    • 普通客户小批量购买

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 16:37
       * @desc: 普通客户小批量购买
       */
      
      public class NewCustomerFewStrategy implements Strategy{
      
          @Override
          public double getPrice(double standarPrice) {
              System.out.println("不打折原价!");
              return standarPrice;
          }
      }
      
    • 普通客户大批量购买

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 16:37
       * @desc: 普通客户大批量购买
       */
      
      public class NewCustomerManyStrategy implements Strategy{
      
          @Override
          public double getPrice(double standarPrice) {
              System.out.println("打九折!");
              return standarPrice*0.9;
          }
      }
      
    • 老客户小批量购买

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 16:37
       * @desc: 老客户小批量购买
       */
      
      public class OldCustomerFewStrategy implements Strategy{
      
          @Override
          public double getPrice(double standarPrice) {
              System.out.println("打八五折!");
              return standarPrice*0.85;
          }
      }
      
    • 老客户大批量购买

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 16:37
       * @desc: 老客户大批量购买
       */
      
      public class OldCustomerManyStrategy implements Strategy{
      
          @Override
          public double getPrice(double standarPrice) {
              System.out.println("打八折!");
              return standarPrice*0.8;
          }
      }
      
    • 负责和具体的策略类交互

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Context.java
       * @time: 2020/2/29 16:40
       * @desc: 负责和具体的策略类交互,这样的话,具体的算法和直接的客户端调用分类了,使得算法可以独立于客户端独立的变化。
       *        如果使用Spring的依赖注入功能,还可以通过配置文件,动态的注入不同策略对象,动态的切换不同的算法。
       */
      
      public class Context {
          // 当前采用的算法对象
          private Strategy strategy;
      
          // 可以通过构造器来注入
          public Context(Strategy strategy) {
              this.strategy = strategy;
          }
      
          // 或者通过加一个set方法来注入
          public void setStrategy(Strategy strategy) {
              this.strategy = strategy;
          }
      
          public void printPrice(double s){
              System.out.println("您的报价:"  + strategy.getPrice(s));
          }
      }
      
    • 客户端

      package com.sxt.strategy;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/29 16:56
       * @desc: |
       */
      
      public class Client {
          public static void main(String[] args){
              Strategy s1 = new OldCustomerManyStrategy();
              Context ctx = new Context(s1);
              ctx.printPrice(998);
          }
      }
      

19. 模板方法模式

  • template method pattern

  • 场景:客户到银行办理业务:

    • 取号排队
    • 办理具体业务
    • 给银行工作人员评分
  • 代码

    • 模拟银行业务流程

      package com.sxt.template;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: BankTemplateMethod.java
       * @time: 2020/2/29 17:10
       * @desc: |
       */
      
      public abstract class BankTemplateMethod {
          // 具体方法
          public void takeNumber(){
              System.out.println("取号排队!");
          }
      
          // 办理具体的业务,钩子方法
          public abstract void transact();
      
          public void evaluate(){
              System.out.println("反馈评分!");
          }
      
          // 模板方法
          public final void process(){
              this.takeNumber();
              this.transact();
              this.evaluate();
          }
      }
      
    • 客户端

      package com.sxt.template;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/29 17:11
       * @desc: |
       */
      
      public class Client {
          public static void main(String[] args){
              BankTemplateMethod btm = new DrawMoney();
              btm.process();
      
              // 通常采用匿名内部类
              BankTemplateMethod btm2 = new BankTemplateMethod() {
                  @Override
                  public void transact() {
                      // 存钱
                      System.out.println("我要存钱");
                  }
              };
              btm2.process();
          }
      }
      
      // 取款
      class DrawMoney extends BankTemplateMethod{
      
          @Override
          public void transact() {
              System.out.println("我要取款!");
          }
      }
      
  • 方法回调(钩子方法)

    • 好莱坞原则“Don't call me, we'll call you back.”
    • 在好莱坞,当艺人把简历递交给好莱坞的娱乐公司时,所能做的就是等待,整个过程由娱乐公司控制,演员只能被动的服务安排,在需要的时候再由公司安排具体环节的演出。
    • 在软件开发中,我们可以将call翻译为调用。子类不能调用父类,而通过父类调用子类。这些调用步骤已经在父类中写好了,完全由父类控制整个过程。
  • 什么时候用到模板方法模式:实现一个算法时,整体步骤很固定,但是,某些部分易变。易变的部分可以抽象出来,供子类实现。

  • 开发中常见的场景:非常频繁。各个框架、类库中都有影子。比如常见的优:

    • 数据库访问的封装
    • Junit单元测试
    • servlet中关于doGet/doPost方法调用
    • Hibernate中模板程序
    • spring中JDBCTemplate、HibernateTemplate等。

20. 状态模式

  • state pattern

  • 场景:

    • 电梯的运行
    • 红绿灯
    • 企业或政府系统、公文的审批状态
    • 网上购物时,订单的状态
    • 酒店系统中,房间的状态变化:已预订、已入住、空闲
  • 核心:用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题

  • 结构:

    • Context环境类:环境类中维护一个State对象,它是定义了当前的状态。
    • State抽象状态类
    • ConcreteState具体状态类:每一个类封装了一个状态对应的行为。
  • 类图:

    img

  • 代码

    • 状态接口

      package com.sxt.state;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: State.java
       * @time: 2020/2/29 18:11
       * @desc: |
       */
      
      public interface State {
          void handle();
      }
      
    • 空闲状态

      package com.sxt.state;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 18:11
       * @desc: |空闲状态
       */
      
      public class FreeState implements State{
      
          @Override
          public void handle() {
              System.out.println("房间空闲!没人住!");
          }
      }
      
    • 预定状态

      package com.sxt.state;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 18:11
       * @desc: |预定状态
       */
      
      public class BookedState implements State{
      
          @Override
          public void handle() {
              System.out.println("房间已预订!");
          }
      }
      
    • 已入住状态

      package com.sxt.state;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @time: 2020/2/29 18:11
       * @desc: |已入住状态
       */
      
      public class CheckedState implements State{
      
          @Override
          public void handle() {
              System.out.println("房间已入住!");
          }
      }
      
    • 上下文环境类:代表当前的状态和状态之间切换的核心方法

      package com.sxt.state;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Context.java
       * @time: 2020/2/29 18:15
       * @desc: |
       */
      
      public class HomeContext {
          // 当前状态
          private State state;
      
          public HomeContext(State state) {
              this.state = state;
              this.state.handle();
          }
      
          // 设置不同状态
          public void setState(State s){
              System.out.println("修改状态!");
              state = s;
              state.handle();
          }
      }
      
    • 客户端

      package com.sxt.state;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/2/29 18:17
       * @desc: |
       */
      
      public class Client {
          public static void main(String[] args) {
              HomeContext ctx = new HomeContext(new FreeState());
              ctx.setState(new BookedState());
              ctx.setState(new CheckedState());
          }
      }
      
  • 开发中常见的场景:

    • 银行系统中账号状态的管理
    • OA系统中公文状态的管理
    • 酒店系统中,房间状态的管理
    • 线程对象各状态之间的切换

21. 观察者模式

  • Observer Pattern

  • 场景

    • 广播机制
    • 聊天室
    • 新闻订阅
    • CS游戏
  • 上面这些场景,我们都可以使用观察者模式处理。我们可以把多个订阅者、客户称之为观察者;需要同步给多个订阅者的数据封装到对象中,称之为目标。

  • 核心:

    • 观察者模式主要用于1:N的通知。当一个对象(目标对象Subject或Objservable,消息发布)的状态发生变化时,他需要及时告知一系列对象(观察者对象,Observer,消息订阅),令他们做出响应。
    • 通知观察者的方式
      • 推:每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接受。
      • 拉:观察者只要知道有情况即可。至于什么时候获取内容,获取什么内容,都可以自主决定。
  • UML类图

    img

  • 代码:

    • 消息发布对象

      package com.sxt.observer;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Subject.java
       * @time: 2020/3/1 13:36
       * @desc: |
       */
      
      public class Subject {
          protected List<Observer> list = new ArrayList<>();
      
          public void registerObserver(Observer obs) {
              list.add(obs);
          }
      
          public void removeObserver(Observer obs) {
              list.remove(obs);
          }
      
          // 通知所有的观察者更新状态
          public void notifyAllObservers() {
              for (Observer obs : list) {
                  obs.update(this);
              }
          }
      }
      
    • 消息发布对象的具体实现

      package com.sxt.observer;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ConcreteSubject.java
       * @time: 2020/3/1 13:41
       * @desc: |
       */
      
      public class ConcreteSubject extends Subject {
          private int state;
      
          public int getState() {
              return state;
          }
      
          public void setState(int state) {
              this.state = state;
              // 主题对象/目标对象的值发生了变化,请通知所有的观察者
              this.notifyAllObservers();
          }
      }
      
    • 观察者接口

      package com.sxt.observer;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Observer.java
       * @time: 2020/3/1 13:36
       * @desc: |
       */
      
      public interface Observer {
          void update(Subject subject);
      }
      
    • 观察者

      package com.sxt.observer;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ObserverA.java
       * @time: 2020/3/1 13:43
       * @desc: |
       */
      
      public class ObserverA implements Observer {
          // myState需要跟目标对象的值保持一致
          private int myState;
      
          public int getMyState() {
              return myState;
          }
      
          public void setMyState(int myState) {
              this.myState = myState;
          }
      
          @Override
          public void update(Subject subject) {
              myState = ((ConcreteSubject)subject).getState();
          }
      }
      
    • 客户端

      package com.sxt.observer;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/3/1 13:45
       * @desc: |
       */
      
      public class Client {
          public static void main(String[] args){
              // 创建目标对象
              ConcreteSubject subject = new ConcreteSubject();
              // 创建多个观察者
              ObserverA obs1 = new ObserverA();
              ObserverA obs3 = new ObserverA();
              ObserverA obs2 = new ObserverA();
      
              // 让这三个观察者添加到subject对象的观察者队伍中
              subject.registerObserver(obs1);
              subject.registerObserver(obs2);
              subject.registerObserver(obs3);
      
              // 改变subject的状态
              subject.setState(3000);
              // 查看观察者的状态
              System.out.println(obs1.getMyState());
              System.out.println(obs2.getMyState());
              System.out.println(obs3.getMyState());
          }
      }
      
  • JAVASE中提供了java.util.Observable和java.util.Observer来实现观察者模式

    • 目标对象

      package com.sxt.observer2;
      
      import java.util.Observable;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ConcreteSubject.java
       * @time: 2020/3/1 14:05
       * @desc: |
       */
      
      public class ConcreteSubject extends Observable {
          private int state;
      
          public int getState() {
              return state;
          }
      
          public void setState(int state) {
              this.state = state;
          }
      
          public void set(int s){
              // 目标对象的状态发生了改变
              state = s;
              // 表示目标对象已经发生了更改
              setChanged();
              // 通知所有的观察者
              notifyObservers(state);
          }
      }
      
    • 观察者对象

      package com.sxt.observer2;
      
      import java.util.Observable;
      import java.util.Observer;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ObserverA.java
       * @time: 2020/3/1 14:07
       * @desc: |
       */
      
      public class ObserverA implements Observer {
          private int myState;
      
          public int getMyState() {
              return myState;
          }
      
          public void setMyState(int myState) {
              this.myState = myState;
          }
      
          @Override
          public void update(Observable o, Object arg) {
              myState = ((ConcreteSubject)o).getState();
          }
      }
      
    • 客户端

      package com.sxt.observer2;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/3/1 14:09
       * @desc: |
       */
      
      public class Client {
          public static void main(String[] args){
              // 创建目标对象Observable
              ConcreteSubject subject = new ConcreteSubject();
      
              // 创建观察者
              ObserverA obs1 = new ObserverA();
              ObserverA obs2 = new ObserverA();
              ObserverA obs3 = new ObserverA();
      
              // 将上面三个观察者对象加到目标对象subject的观察者容器中
              subject.addObserver(obs1);
              subject.addObserver(obs2);
              subject.addObserver(obs3);
      
              // 改变subject对象的状态
              subject.set(300);
      
              // 看看观察者的状态发生变化了没
              System.out.println(obs1.getMyState());
              System.out.println(obs2.getMyState());
              System.out.println(obs3.getMyState());
          }
      }
      
  • 开发中常见的场景

    • 聊天室程序的,服务器转发给所有客户端
    • 网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发
    • 邮件订阅
    • Servlet中,监听器的实现
    • Android中,广播机制
    • JDK的AWT中事件处理模型,基于观察者模式的委派事件模型(Delegation Event Model)
      • 事件源 --> 目标对象
      • 事件监听器 --> 观察者
    • 京东商城中,群发某商品打折信息

22. 备忘录模式

  • memento pattern

  • 场景:

    • 录入大批人员资料。正在录入当前人资料时,发现上一个人录错了,此时需要恢复上一个人的资料,再进行修改。
    • Word文档编辑时,忽然电脑死机或断电,再打开时,可以看到word提示你恢复到以前的文档。
    • 管理系统中,公文撤回功能。公文发送出去后,想撤回来。
  • 核心:就是保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。

  • 结构

    • 源发器类Originator
    • 备忘录类Memento
    • 负责人类CareTaker
  • 代码:

    • 源发器类

      package com.sxt.memento;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Emp.java
       * @time: 2020/3/1 14:38
       * @desc: |源发器类
       */
      
      public class Emp {
          private String name;
          private int age;
          private double salary;
      
          // 进行备忘操作,并返回备忘录对象
          public EmpMemento memento() {
              return new EmpMemento(this);
          }
      
          // 进行数据恢复,恢复成制定备忘录对象的值
          public void recovery(EmpMemento mmt){
              this.name = mmt.getName();
              this.age = mmt.getAge();
              this.salary = mmt.getSalary();
          }
      
          public Emp(String name, int age, double salary) {
              this.name = name;
              this.age = age;
              this.salary = salary;
          }
      
          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;
          }
      
          public double getSalary() {
              return salary;
          }
      
          public void setSalary(double salary) {
              this.salary = salary;
          }
      }
      
    • 备忘录类

      package com.sxt.memento;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: EmpMemento.java
       * @time: 2020/3/1 14:54
       * @desc: |备忘录类
       */
      
      public class EmpMemento {
          private String name;
          private int age;
          private double salary;
      
          public EmpMemento(Emp e){
              this.name = e.getName();
              this.age = e.getAge();
              this.salary = e.getSalary();
          }
      
          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;
          }
      
          public double getSalary() {
              return salary;
          }
      
          public void setSalary(double salary) {
              this.salary = salary;
          }
      }
      
    • 负责人类:管理备忘录对象

      • 负责保存好的备忘录对象
      • 可以通过增加容器,设置多个备忘点
      package com.sxt.memento;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: CareTaker.java
       * @time: 2020/3/1 14:58
       * @desc: |负责人类:管理备忘录对象
       */
      
      public class CareTaker {
          private EmpMemento memento;
      
          public EmpMemento getMemento() {
              return memento;
          }
      
          public void setMemento(EmpMemento memento) {
              this.memento = memento;
          }
      }
      
    • 客户端

      package com.sxt.memento;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2020/3/1 14:58
       * @desc: |
       */
      
      public class Client {
          public static void main(String[] args) {
              CareTaker taker = new CareTaker();
              Emp emp = new Emp("李英俊", 18, 900);
              System.out.println("第一次创建对象:" + emp.getName() + emp.getAge() + emp.getSalary());
      
              // 进行一次备份
              taker.setMemento(emp.memento());
      
              emp.setAge(38);
              emp.setName("哈哈");
              emp.setSalary(10);
              System.out.println("第二次创建对象:" + emp.getName() + emp.getAge() + emp.getSalary());
      
              // 恢复到备忘录对象保存的状态
              emp.recovery(taker.getMemento());
              System.out.println("第三次创建对象:" + emp.getName() + emp.getAge() + emp.getSalary());
          }
      }
      
  • UML类图

    img

  • 备忘点较多时

    • 将备忘录压栈
    • 将多个备忘录对象,序列化和持久化
  • 开发中常见的应用场景:

    • 棋类游戏中的悔棋操作
    • 普通软件中的,撤销操作
    • 数据库管理软件中的,事务管理中的回滚操作
    • PS中的历史记录

第4章 正则表达式

  • 文本的复杂处理
  • regular expression
  • 开发中使用正则表达式的流程:
    • 分析所有匹配的数据,写出测试用的典型数据
    • 在工具软件中进行匹配测试
    • 在程序中调用通过测试的正则表达式
  • regexbuddy工具

1. 正则表达式语法

  • 普通字符:匹配与之相同的一个字符

  • 简单的转义字符:

    img

  • 标准字符集合

    • 能够与“多种字符”匹配的表达式
    • 注意区分带小写,大写是相反的意思(比如\d是匹配一个数字,\D就是匹配非数字)
    • 小数点不能匹配换行符\n
    • [\s\S]用来匹配任意字符,包括换行符

    img

  • 自定义字符集合

    • []方括号匹配方式,能够匹配方括号中任意一个字符

    img

    • 正则表达式的特殊符号,被包含到中括号中,则失去特殊意义,除了^,-之外
    • 标准字符集合,除小数点外,如果被包含于中括号,自定义字符集合将包含该集合。比如:[\d.\-+]将匹配:数字、小数点、+、-
  • 量词 | Quantifier

    • 修饰匹配次数的特殊符号

      img

      \d\d{6} != {\d\d}{6}

    • 匹配次数中的贪婪模式(匹配字符越多越好,默认!)

    • 匹配次数中的非贪婪模式(匹配字符越少越好,修饰匹配次数的特殊符号后再加上一个“?”号)例:\d{2,3}?

  • 字符边界(零宽)

    • 本组标记匹配的不是字符而是位置,符合某 种条件的位置

    • \b匹配这样一个位置:前面的字符和后面的字符不全是\w

      img

    • gaoqi\b测试:

      img

    • \bgaoqi\b测试:

      img

  • IGNORECASE:忽略大小写模式

    • 匹配时忽略大小写
    • 默认情况下,正则表达式是要区分大小写的
  • SINGLELINE:单行模式

    • 整个文本看作一个字符串,只有一个开头,一个结尾。
    • 使小数点“.”可以匹配包含换行符(\n)在内的任意字符。
  • MULTILINE:多行模式

    • 每行都是一个字符串,都有开头和结尾
    • 在制定了MULTILINE之后,如果需要仅匹配字符串开始和结束位置,可以使用\A和\Z
  • 选择符和分组

    img

  • 反向引用(\nnm)

    • 每一对()会分配一个编号,使用()的捕获根据左括号的顺序从1开始自动编号。
    • 通过反向应用,可以对分组已捕获的字符串进行应用。
  • 预搜索(零宽断言)

    • 只进行子表达式的匹配,匹配内容不计入最终的匹配结果,是零宽度

    • 这个位置应该符合某个条件。判断当前位置的前后字符,是否符合指定的条件,但不匹配前后的字符。是对位置的匹配。

    • 正则表达式匹配过程中,如果子表达式匹配到的是字符的内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。占有字符还是零宽度,是针对匹配的内容是否保存到最终的匹配结果中而言的。

      img

2. 正则表达式的练习

  • 练习1

    img

  • 解答1:(0\d{2,3}-\d{7,8})|(1[35789]\d{9})

  • 练习2

    img

  • 解答2:[\w\-]+@[a-z0-9A-Z]+(\.[A-Za-z]{2,4}){1,2}

3. 常用正则表达式列表

img

4. JAVA程序中使用正则表达式

  • 开发环境

    • 开发环境和文本编译器中使用正则
    • 数据库中也可以使用正则
  • JAVA相关类位于java.util.regex包下面

  • 类Pattern:

    • 正则表达式的编译表现形式
    • 建立正则表达式,并启用相应模式
    • Pattern p = Pattern.compile(r, int);
  • 类Matcher:

    • 通过解释Pattern对character sequence执行匹配操作的引擎
    • 匹配str字符串
    • Matcher m = p.matcher(str);
  • 测试

    • 匹配整个正则表达式

      package com.sxt.regex;
      
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Demo01.java
       * @time: 2020/3/3 14:10
       * @desc: |
       */
      
      public class Demo01 {
          public static void main(String[] args){
              // 在这个字符串:asdfsadf2323,是否符合制定的正则表达式:\w+
              Pattern p = Pattern.compile("\\w+");
              // 创建Matcher对象
              Matcher m = p.matcher("asdfsadf@@2323");
              // 尝试将整个字符序列与该模式匹配
              // boolean yo = m.matches();
              // 该方法扫描输入的序列,查找与该模式匹配的下一个子序列
              while(m.find()){
                  // group()和group(0)都是匹配整个表达式的子字符串
                  System.out.println(m.group());
                  System.out.println(m.group(0));
              }
          }
      }
      
    • 测试正则表达式分组的处理

      package com.sxt.regex;
      
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @desc: | 测试正则表达式分组的处理
       */
      
      public class Demo02 {
          public static void main(String[] args) {
              // 在这个字符串:asdfsadf2323,是否符合制定的正则表达式:\w+
              Pattern p = Pattern.compile("([a-z]+)([0-9]+)");
              // 创建Matcher对象
              Matcher m = p.matcher("asdfsa12**asd233**dsd11");
              // 尝试将整个字符序列与该模式匹配
              while (m.find()) {
                  // group()和group(0)都是匹配整个表达式的子字符串
                  System.out.println("start---");
                  System.out.println("满足整个表达式的子字符串:");
                  System.out.println(m.group());
                  System.out.println("满足第1个括号中表达式的字符串:");
                  System.out.println(m.group(1));
                  System.out.println("满足第2个括号中表达式的字符串:");
                  System.out.println(m.group(2));
              }
          }
      }
      
    • 测试正则表达对象替换操作

      package com.sxt.regex;
      
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @desc: | 测试正则表达对象替换操作
       */
      
      public class Demo03 {
          public static void main(String[] args) {
              // 在这个字符串:asdfsadf2323,是否符合制定的正则表达式:\w+
              Pattern p = Pattern.compile("[0-9]");
              // 创建Matcher对象
              Matcher m = p.matcher("asdfsa12**asd233**dsd11");
      
              // 替换
              String newStr = m.replaceAll("#");
              System.out.println(newStr);
          }
      }
      
    • 测试正则表达对象分割字符串的操作

      package com.sxt.regex;
      
      import java.util.Arrays;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @desc: | 测试正则表达对象分割字符串的操作
       */
      
      public class Demo04 {
          public static void main(String[] args) {
              String str = "asdfsa12asd233dsd11";
      
              // 切割
              String[] arrs = str.split("\\d+");
              System.out.println(Arrays.toString(arrs));
          }
      }
      

5. 手写网络爬虫

package com.sxt.regex;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: Li Tian
 * @contact: litian_cup@163.com
 * @software: IntelliJ IDEA
 * @file: WebSpider.java
 * @time: 2020/3/4 17:29
 * @desc: |网络爬虫取数据
 */

public class WebSpider {
    public static void main(String[] args) {
        String url = "http://www.163.com";
        String destStr = getURLContent(url);

        // 取到的超链接的整个内容
        // Pattern p = Pattern.compile("<a[\\s\\S]+?</a>");
        // 取到的超链接的地址
        // Pattern p = Pattern.compile("href=\"(.+?)\"");
        // 注意:上述?是非贪婪模式
        // Matcher m = p.matcher(destStr);
        // while (m.find()) {
        //     System.out.println(m.group());
        //     System.out.println("-----");
        //     System.out.println(m.group(1));
        // }

        List<String> result = getMatherSubstrs(destStr, "href=\"(http://[\\w\\s./]+?)\"");
        for (String temp : result) {
            System.out.println(temp);
        }
    }

    public static String getURLContent(String loc) {
        /*获得url对应的网页源码内容*/
        StringBuilder sb = new StringBuilder();
        try {
            URL url = new URL(loc);
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), Charset.forName("gbk")));
            String temp = "";
            while ((temp = reader.readLine()) != null) {
                sb.append(temp);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    public static List<String> getMatherSubstrs(String destStr, String regexStr) {
        // 取到的超链接地址
        Pattern p = Pattern.compile(regexStr);
        Matcher m = p.matcher(destStr);
        List<String> result = new ArrayList<>();
        while (m.find()) {
            result.add(m.group(1));
        }
        return result;
    }
}

第5章 JDBC

1. Mysql引入

  • 常用命令行操作

    img

2. JDBC简述

  • JDBC(Java Database Connection)为java开发者使用数据库提供了统一的编程接口,它由一组java类和接口组成。是java程序与数据库系统通信的标准api。JDBC API使得开发人员可以使用纯java的方式来连接数据库,并执行操作。

  • 访问数据库流程

    img

  • Driver接口

    • Driver接口由数据库厂家提供,对于java开发者而言,只需要使用Driver接口就可以了。
    • 在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。不同的数据库有不同的装载方法。
    • 驱动:就是各个数据库厂商实现的Sun公司提出的JDBC接口。即对Connection等接口的实现类的jar文件。
    • 装载MySQL驱动
      • Class.forName("com.mysql.jdbc.Driver");
    • 装载Oracle驱动
      • Class.forName("oracle.jdbc.driver.OracleDriver")
  • DriverManager接口

    • DriverManager是JDBC的管理层,作用于用户和驱动程序之间。
    • DriverManager跟踪可用的驱动程序,并在数据库和相应的驱动程序之间建立连接。
  • Connection接口

    • Connection与特定数据库的连接(会话),在连接上下文中执行SQL语句并返回结果。
    • DriverManager的getConnection()方法建立在JDBC URL中定义的数据库Connection连接上。
    • 连接Mysql数据:
      • Connection con = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");
    • 连接Oracle数据库:
      • Connection con = DriverManager.gtConnection("jdbc:oracle:thin:@host:port:database", "user", "password");
  • JDBC详细操作

    • 灵活指定SQL语句中的变量:PreparedStatement
    • 对存储过程进行调用:CallableStatement
    • 运用事务处理:Transaction
    • 批处理:Batch,对于大量的批处理,建议使用Statement,因为PreparedStatement的预编译空间有限,当数据量特别大时,会发生异常。
  • 连接测试

  • 报错参考连接:解决方案

    package com.sxt.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo01.java
     * @time: 2020/3/5 12:48
     * @desc: | 测试跟数据库建立连接
     * 如果报错:参考连接:https://www.cnblogs.com/cn-chy-com/p/10145690.html
     */
    
    public class Demo01 {
        public static void main(String[] args) {
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                long start = System.currentTimeMillis();
                // 建立连接(连接对象内部其实包含了Socket对象,是一个远程的连接。比较耗时!这是Connection对象管理的一个要点!)
                // 真正开发中,为了提高效率,都会使用连接池来管理连接对象!
                Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
                long end = System.currentTimeMillis();
                System.out.println(conn);
                System.out.println("建立连接耗时:" + (end - start) + "ms毫秒");
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
        }
    }
    

3. JDBC常用接口

  • Statement接口

    • 用于执行静态SQL语句并返回它所生成结果的对象。
    • 三种Statement类:
      • Statement:由createStatement创建,用于发送简单的SQL语句。(不带参数的)
      • PreparedStatement:继承自Statement接口,由prepareStatement创建,用于发送含有一个或多个输入参数的sql语句。PreparedStatement对象比Statement对象的效率更高,并且可以防止SQL注入。我们一般都用PreparedStatement
      • CallableStatement:集成自PreparedStatement。由方法prePareCall创建,用于调用存储过程。
    • 常用的Statement方法:
      • execute():运行语句,返回是否有结果集。
      • executeQuery():运行select语句,返回ResultSet结果集。
      • executeUpadate():运行insert/update/delete操作,返回更新的行数。
  • Statement测试执行sql语句以及sql注入问题

    package com.sxt.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo02.java
     * @time: 2020/3/5 12:48
     * @desc: | 测试执行sql语句以及sql注入问题
     */
    
    public class Demo02 {
        public static void main(String[] args) {
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                // 建立连接(连接对象内部其实包含了Socket对象,是一个远程的连接。比较耗时!这是Connection对象管理的一个要点!)
                // 真正开发中,为了提高效率,都会使用连接池来管理连接对象!
                Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
                Statement stmt = conn.createStatement();
                String sql = "insert into t_user (username, pwd, regTime) values ('赵六', 6666, now())";
                stmt.execute(sql);
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
  • sql注入问题:若要根据id删除一行记录,很容易出现数据库危险,比如要删除id=5的记录,传入的时候为id = 5 or 1 = 1,最终导致数据库都被删除。

  • 测试PreparedStatement的用法

    package com.sxt.jdbc;
    
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo01.java
     * @time: 2020/3/5 12:48
     * @desc: | 测试PreparedStatement的基本用法
     */
    
    public class Demo03 {
        public static void main(String[] args) {
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                // ?是占位符
                String sql = "insert into t_user (username, pwd, regTime) values (?, ?, ?)";
                PreparedStatement ps = conn.prepareStatement(sql);
                // 参数索引是从1开始计算,而不是0
                // ps.setString(1, "傻瓜");
                // ps.setString(2, "12345");
    
                // 还可以不管类型直接setObject
                // ps.setObject(1, "傻瓜2");
                // ps.setObject(2, "12344");
    
                // 设置时间:注意该时间的格式应该是java.sql.Date
                ps.setObject(1, "傻瓜3");
                ps.setObject(2, "12343");
                ps.setObject(3, new java.sql.Date(System.currentTimeMillis()));
    
                System.out.println("插入一行记录");
                // 返回是否有结果集
                // ps.execute();
                // 返回更新的行数
                int count = ps.executeUpdate();
                System.out.println(count);
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 关闭顺序:resultset-->statement-->connection

  • 测试ResultSet结果集的用法

    package com.sxt.jdbc;
    
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试ResultSet结果集的用法
     * 记得要关闭打开的接口
     */
    
    public class Demo04 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                // ?是占位符
                String sql = "select id, username, pwd from t_user where id>?";
                ps = conn.prepareStatement(sql);
                // 把id>2的记录都取出来
                ps.setObject(1, 2);
                rs = ps.executeQuery();
    
                while (rs.next()) {
                    // 数字代表哪一列
                    System.out.println(rs.getInt(1) + "-->" + rs.getString(2) + "-->" + rs.getString(3));
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            } finally {
                // 一定要将三个try catch分开写
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (ps != null) {
                    try {
                        ps.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • Batch批处理,尽量使用Statement而不是PreparedStatement

    package com.sxt.jdbc;
    
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 批处理
     */
    
    public class Demo05 {
        public static void main(String[] args) {
            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
                // 设为手动提交
                conn.setAutoCommit(false);
                long start = System.currentTimeMillis();
                stmt = conn.createStatement();
                for (int i = 0; i < 20000; i++) {
                    stmt.addBatch("insert into t_user (username, pwd, regTime) values ('li'" + ", 666666, now())");
                    stmt.executeBatch();
                }
                // 提交事务
                conn.commit();
                long  end = System.currentTimeMillis();
                System.out.println("插入20000条数据,耗时(毫秒):" + (end - start));
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            } finally {
                // 一定要将三个try catch分开写
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (stmt != null) {
                    try {
                        stmt.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

4. 事务

  • 事务的基本概念:一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元。

  • 事务开始于:

    • 连接到数据库上,并执行一条DML语句(INSERT、UPDATE或DELETE)
    • 前一个事务结束后,又输入了另外一条DML语句
  • 事务结束于:

    • 执行COMMIT或ROLLBACK语句
    • 执行一条DDL语句,如CREATE TABLE语句;这种情况下,会自动执行COMMIT语句
    • 执行一条DCL语句,例如GRANT语句;这种情况下,会自动执行COMMIT语句
    • 断开与数据库的连接
    • 执行了一条DML语句,该语句却失败了;在这种情况下,会为这个无效的DML语句执行ROLLBACK语句
  • 事务的四大特性(ACID)

    • atomicity(原子性):表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败
    • consistency(一致性):表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态
    • isolation(隔离性):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
      • 事务隔离级别从低到高:
        • 读取未提交(Read Uncommitted)
        • 读取已提交(Read Committed)
        • 可重复读(Repeatable Read)
        • 序列化(Serializable)
    • durability(持久性):持久性事务完成之后,它对系统的影响是永久的
  • 测试事务的基本用法

    package com.sxt.jdbc;
    
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试事务的基本用法
     */
    
    public class Demo06 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps1 = null;
            PreparedStatement ps2 = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                // JDBC默认是自动提交
                conn.setAutoCommit(false);
    
                ps1 = conn.prepareStatement("insert into t_user (username, pwd) values (?, ?)");
                ps1.setObject(1, "狗子");
                ps1.setObject(2, "111");
                ps1.execute();
                System.out.println("插入一个用户1");
                Thread.sleep(6000);
    
                ps2 = conn.prepareStatement("insert into t_user (username, pwd) values (?, ?, ?)");
                ps2.setObject(1, "狗子2");
                ps2.setObject(2, "111");
                ps2.execute();
                System.out.println("插入一个用户2");
    
                conn.commit();
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 一定要将三个try catch分开写
                if (ps1 != null) {
                    try {
                        ps1.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (ps2 != null) {
                    try {
                        ps2.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

5. 时间处理

  • 时间类型

    • java.util.Date
      • 子类:java.sql.Date:表示年月日
      • 子类:java.sql.Time:表示时分秒
      • 子类:java.sql.Timestamp:表示年月日时分秒
    • 日期比较处理
      • 插入随机日期
      • 取出指定日期范围的记录
  • Date、Timestamp比较和插入随机日期

    package com.sxt.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    import java.util.Random;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试时间处理(java.sql.Date, java.sql.Time, java.sql.Timestamp)
     */
    
    public class Demo07 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps1 = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                for (int i = 0; i < 1000; i++) {
                    ps1 = conn.prepareStatement("insert into t_user (username, pwd, regTime, lastLoginTime) values (?, ?, ?, ?)");
                    ps1.setObject(1, "狗子" + i);
                    ps1.setObject(2, "111");
    
                    // 定义随机数
                    int rand = 10000000 + new Random().nextInt(1000000000);
    
                    java.sql.Date date = new java.sql.Date(System.currentTimeMillis() - rand);
                    ps1.setDate(3, date);
    
                    // 如果需要插入制定日期,可以使用Calendar或DateFormat
                    java.sql.Timestamp stamp = new java.sql.Timestamp(System.currentTimeMillis());
                    ps1.setTimestamp(4, stamp);
    
                    ps1.execute();
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            } finally {
                // 一定要将三个try catch分开写
                if (ps1 != null) {
                    try {
                        ps1.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
  • 取出指定日期范围的记录

    package com.sxt.jdbc;
    
    import java.sql.*;
    import java.text.DateFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试时间处理,取出指定时间段的数据
     */
    
    public class Demo08 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                // 选择满足regTime条件的记录,Timestamp格式同理,把java.sql.Date改成java.sql.Timestamp即可getDate改成getTimestamp
                ps = conn.prepareStatement("select * from t_user where regTime>? and regTime<?");
                java.sql.Date start = new java.sql.Date(str2Date("2020-3-1 10:23:45"));
                java.sql.Date end = new java.sql.Date(str2Date("2020-3-3 10:23:45"));
                ps.setObject(1, start);
                ps.setObject(2, end);
                rs = ps.executeQuery();
                while(rs.next()){
                    System.out.println(rs.getInt("id") + "-->" + rs.getString("username") + "-->" + rs.getDate("regTime"));
                }
    
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            } finally {
                // 一定要将三个try catch分开写
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (ps != null) {
                    try {
                        ps.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static long str2Date(String dateStr){
            /*将字符串代表的日期转为long数字(格式:yyyy-MM-dd hh:mm:ss)*/
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            try {
                return format.parse(dateStr).getTime();
            } catch (ParseException e) {
                e.printStackTrace();
                return 0;
            }
        }
    }
    

6. 大对象操作

6.1 CLOB文本大对象操作

  • Character Large Object

  • 用于存储大量的文本数据

  • 大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的方式来处理的。而非一般的字段,一次即可读出数据。

  • Mysql中相关类型:

    • TINYTEXT最大长度为255(2[1]-1)字符的TEXT列。
    • TEXT[(M)]最大长度为65535(2[2]-1)字符的TEXT列。
    • MEDIUMTEXT最大长度为16777215(2[3]-1)字符的TEXT列。
    • LONGTEXT最大长度为4294967295或4GB(2[4]-1)字符的TEXT列。
  • 测试CLOB文本大对象的使用

  • 包含:将字符串、文件内容插入数据库中的CLOB字段,将CLOB字段值取出来操作

    package com.sxt.jdbc;
    
    import java.io.*;
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试CLOB文本大对象的使用
     * 包含:将字符串、文件内容插入数据库中的CLOB字段,将CLOB字段值取出来操作
     */
    
    public class Demo09 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                ps = conn.prepareStatement("insert into t_user2 (username, myInfo) values (?, ?)");
                ps.setString(1, "狗子");
                // 将文本文件的内容直接输入到数据库中
                // ps.setClob(2, new FileReader(new File("a1.txt")));
                // 通过流的操作写入字符串内容
                // ps.setClob(2, new BufferedReader(new InputStreamReader(new ByteArrayInputStream("aaaabbbb".getBytes()))));
                // ps.executeUpdate();
    
                // 读取Clob字段
                ps = conn.prepareStatement("select * from t_user2 where username=?");
                ps.setObject(1, "狗子");
                rs = ps.executeQuery();
                while (rs.next()) {
                    Clob c = rs.getClob("myInfo");
                    Reader r = c.getCharacterStream();
                    int temp = 0;
                    while((temp = r.read()) != -1){
                        System.out.print((char)temp);
                    }
                    System.out.println();
                }
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 一定要将三个try catch分开写
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (ps != null) {
                    try {
                        ps.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

6.2 BLOB二进制大对象操作

  • Binary Large Object

  • 用于存储大量的二进制数据

  • 大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的方式来处理的。而非一般的字段,一次即可独处数据。

  • Mysql中相关类型与CLOB类似,只是将CLOB改为BLOB

  • 测试BLOB二进制大对象的使用

    package com.sxt.jdbc;
    
    import java.io.*;
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试BLOB二进制大对象的使用
     */
    
    public class Demo10 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                // 加载驱动类
                Class.forName("com.mysql.cj.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC", "root", "123456");
    
                // ps = conn.prepareStatement("insert into t_user2 (username, headImg) values (?, ?)");
                // ps.setString(1, "狗子2");
                // 将图片文件的内容直接输入到数据库中
                // ps.setBlob(2, new FileInputStream("test.png"));
                // ps.executeUpdate();
    
                // 读取Blob字段
                ps = conn.prepareStatement("select * from t_user2 where username=?");
                ps.setObject(1, "狗子2");
                rs = ps.executeQuery();
                while (rs.next()) {
                    Blob b = rs.getBlob("headImg");
                    InputStream is = b.getBinaryStream();
                    OutputStream os = new FileOutputStream("a1_input.png");
                    int temp = 0;
                    while((temp = is.read()) != -1){
                        os.write(temp);
                    }
                }
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 一定要将三个try catch分开写
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (ps != null) {
                    try {
                        ps.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

7. 经典JDBC代码总结

  • JDBC工具类

  • 包括返回数据库驱动连接,关闭各个接口

    package com.sxt.jdbc;
    
    import java.io.IOException;
    import java.sql.*;
    import java.util.Properties;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: JDBCUTIL.java
     * @time: 2020/3/8 19:43
     * @desc: |JDBC工具类
     */
    
    public class JDBCUtil {
        // 可以帮助我们读取和处理资源文件中的信息
        private static Properties pros = null;
        static {
            /*静态代码块:只有在加载JDBCUtil类的时候调用一次*/
            pros = new Properties();
            try {
                pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/jdbc/db.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static Connection getMysqlConn() {
            /*获取数据库(mysql)驱动连接*/
            // 加载驱动类
            try {
                Class.forName(pros.getProperty("mysqlDriver"));
                return DriverManager.getConnection(
                        pros.getProperty("mysqlURL"),
                        pros.getProperty("mysqlUser"),
                        pros.getProperty("mysqlPwd"));
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        public static Connection getOracleConn() {
            /*获取数据库(oracle)驱动连接*/
            // 加载驱动类
            try {
                Class.forName(pros.getProperty("oracleDriver"));
                return DriverManager.getConnection(
                        pros.getProperty("oracleURL"),
                        pros.getProperty("oracleUser"),
                        pros.getProperty("oraclePwd"));
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        public static void close(ResultSet rs, Statement ps, Connection conn){
            /*关闭接口方法*/
            try {
                if (rs != null){
                    rs.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (ps != null){
                    ps.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (conn != null){
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void close(Statement ps, Connection conn){
            /*关闭接口方法,重载*/
            try {
                if (ps != null){
                    ps.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (conn != null){
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void close(Connection conn){
            /*关闭接口方法,重载*/
            try {
                if (conn != null){
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  • 配置文件封装

    mysqlURL=jdbc:mysql://localhost:3306/testjdbc?serverTimezone=UTC
    #mysqlURL=jdbc:mysql://localhost:3306/sorm?serverTimezone=UTC
    mysqlDriver=com.mysql.cj.jdbc.Driver
    mysqlUser=root
    mysqlPwd=123456
    
    oracleDriver=oracle.jdbc.driver.OracleDriver
    oracleURL=jdbc:oracle:thin:@localhost:1521:database
    oracleUser=scott
    oraclePwd=tiger
    
  • 测试使用JDBCUtil工具类来简化JDBC开发

    package com.sxt.jdbc;
    
    import java.sql.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: | 测试使用JDBCUtil工具类来简化JDBC开发
     */
    
    public class Demo11 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
    
            try {
                conn = JDBCUtil.getMysqlConn();
                ps = conn.prepareStatement("insert into t_user (username) values (?)");
                ps.setString(1, "hehe");
                ps.execute();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                JDBCUtil.close(rs, ps, conn);
            }
        }
    }
    

8. ORM原理

  • ORM的基本思想

    • Object Relationship Mapping:对象关系映射
    • 表结构跟类对应;表中字段和类的属性对应;表中记录和对象对应。
    • 让javabean的属性名和类型尽量和数据库保持一致!
    • 一条记录对应一个对象。将这些查询到的对象放到容器中(List,Set,Map)
  • 将表中的一条记录封装到Object数组中

  • 将表中的一条记录封装到map中

  • 将表中的一条记录封装到javabean对象中

  • 测试使用Object数组来封装一条记录,使用List<Object[]>存储多条记录

    package com.sxt.testORM;
    
    import com.sxt.jdbc.JDBCUtil;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Demo01.java
     * @time: 2020/3/10 13:29
     * @desc: |测试使用Object数组来封装一条记录
     * 使用List<Object[]>存储多条记录
     */
    
    public class Demo01 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            List<Object[]> list = new ArrayList<>();
            try {
                conn = JDBCUtil.getMysqlConn();
                ps = conn.prepareStatement("select empname, salary, age from emp where id > ?");
                ps.setObject(1, 0);
                rs = ps.executeQuery();
                while (rs.next()) {
                    // System.out.println(rs.getString(1) + "-->" + rs.getDouble(2) + "-->" + rs.getInt(3));
                    Object[] objs = new Object[3];
                    objs[0] = rs.getObject(1);
                    objs[1] = rs.getObject(2);
                    objs[2] = rs.getObject(3);
    
                    list.add(objs);
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCUtil.close(rs, ps, conn);
            }
    
            for (Object[] objs : list) {
                System.out.println(objs[0] + "-->" + objs[1] + "-->" + objs[2]);
            }
        }
    }
    
  • 测试使用Map来封装一条记录,使用List<Map>存储多条记录(也可用Map<Map>

    package com.sxt.testORM;
    
    import com.sxt.jdbc.JDBCUtil;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: |测试使用Map来封装一条记录
     * 使用List<Map>存储多条记录(也可用Map<Map>)
     */
    
    public class Demo02 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            // 使用一个Map封装一条记录
            List<Map<String, Object>> list = new ArrayList<>();
            try {
                conn = JDBCUtil.getMysqlConn();
                ps = conn.prepareStatement("select empname, salary, age from emp where id > ?");
                ps.setObject(1, 0);
                rs = ps.executeQuery();
                while (rs.next()) {
                    // System.out.println(rs.getString(1) + "-->" + rs.getDouble(2) + "-->" + rs.getInt(3));
                    Map<String, Object> row = new HashMap<>();
                    row.put("empname", rs.getString(1));
                    row.put("salary", rs.getString(2));
                    row.put("age", rs.getString(3));
                    list.add(row);
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCUtil.close(rs, ps, conn);
            }
    
            // 遍历List和Map
            for (Map<String, Object> row : list) {
                for (String key : row.keySet()) {
                    System.out.print(key + "-->" + row.get(key) + "\t\t");
                }
                System.out.println();
            }
        }
    }
    
  • 使用Javabean对象来封装一条记录,使用List<Javabean>存储多条记录

    package com.sxt.testORM;
    
    import com.sxt.jdbc.JDBCUtil;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @desc: |使用Javabean对象来封装一条记录
     * 使用List<Javabean>存储多条记录
     */
    
    public class Demo03 {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            List<Emp> list = new ArrayList<>();
            try {
                conn = JDBCUtil.getMysqlConn();
                ps = conn.prepareStatement("select empname, salary, age from emp where id > ?");
                ps.setObject(1, 0);
                rs = ps.executeQuery();
                while (rs.next()) {
                    // System.out.println(rs.getString(1) + "-->" + rs.getDouble(2) + "-->" + rs.getInt(3));
                    Emp emp = new Emp(rs.getString(1), rs.getInt(2), rs.getDouble(3));
                    list.add(emp);
                }
    
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                JDBCUtil.close(rs, ps, conn);
            }
    
            for (Emp e: list) {
                System.out.println(e);
            }
        }
    }
    
  • 其中需要为每一个表定义相同结构的类

    • Emp

      package com.sxt.testORM;
      
      import java.sql.Date;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Emp.java
       * @time: 2020/3/10 14:35
       * @desc: |表结构和类对应
       */
      
      public class Emp {
          private Integer id;
          private String empname;
          private Integer age;
          private Double salary;
          private Date birthday;
          private Integer deptId;
      
          public Emp(String empname, Integer age, Double salary) {
              this.empname = empname;
              this.age = age;
              this.salary = salary;
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getEmpname() {
              return empname;
          }
      
          public void setEmpname(String empname) {
              this.empname = empname;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          public Double getSalary() {
              return salary;
          }
      
          public void setSalary(Double salary) {
              this.salary = salary;
          }
      
          public Date getBirthday() {
              return birthday;
          }
      
          public void setBirthday(Date birthday) {
              this.birthday = birthday;
          }
      
          public Integer getDeptId() {
              return deptId;
          }
      
          public void setDeptId(Integer deptId) {
              this.deptId = deptId;
          }
      
          public Emp(String empname, Integer age, Double salary, Date birthday, Integer deptId) {
              this.empname = empname;
              this.age = age;
              this.salary = salary;
              this.birthday = birthday;
              this.deptId = deptId;
          }
      
          public Emp(Integer id, String empname, Integer age, Double salary, Date birthday, Integer deptId) {
              this.id = id;
              this.empname = empname;
              this.age = age;
              this.salary = salary;
              this.birthday = birthday;
              this.deptId = deptId;
          }
      
          public Emp() {
          }
      
          @Override
          public String toString() {
              return empname + "-->" + age + "-->" + salary;
          }
      }
      
    • Dept

      package com.sxt.testORM;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Dept.java
       * @time: 2020/3/10 14:38
       * @desc: |
       */
      
      public class Dept {
          private Integer id;
          private String dname;
          private String address;
      
          public Dept() {
          }
      
          public Dept(String dname, String address) {
              this.dname = dname;
              this.address = address;
          }
      
          public Dept(Integer id, String dname, String address) {
              this.id = id;
              this.dname = dname;
              this.address = address;
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getDname() {
              return dname;
          }
      
          public void setDname(String dname) {
              this.dname = dname;
          }
      
          public String getAddress() {
              return address;
          }
      
          public void setAddress(String address) {
              this.address = address;
          }
      }
      

9. 手写SORM框架

  • Simple Object Relationship Mapping

  • 我们希望设计一个可以实现对象和SQL自动映射的框架,但是整体用法和设计比Hibernate简单。砍掉不必要的功能。

  • 会穿插使用设计模式

  • 从对象到sql

    • 增加:将对象对应成sql语句,执行sql,插入数据库中
    • 删除:根据对象主键的值,生成sql,执行,从库中删除
    • 修改:根据对象需要修改属性的值,生成sql,执行
  • 从sql到对象

    • 查询:根据结果分类
      • 多行多列:List<Javabean>
      • 一行多列:Javabean
      • 一行一列:普通对象:Object,数字:Number
  • 核心架构:

    • Query接口:负责查询(对外提供服务的核心类)
    • QueryFactory类:负责根据配置信息创建query对象
    • TypeConvertor类:负责类型转换
    • TableContext类:负责获取管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结结构。
    • DBManager类:根据配置信息,维持连接对象的管理(增加连接池功能)
    • 工具类:
      • JDBCUtils封装常用JDBC操作
      • StringUtils封装常用字符串操作
      • JavaFileUtils封装Java文件操作
      • ReflectUtils封装常用反射操作
  • 架构图

    img

  • 核心bean,封装相关数据

    • ColumnInfo:封装表中一个字段的信息(字段类型、字段名、键类型)
    • Configuration:封装配置文件信息
    • TableInfo:封装一张表的信息
  • 针对SORM框架的说明:

    • 核心思想:使用简单、性能高、极易上手!
    • 配置文件:目前使用资源文件、后期项目复杂后可以增加XML文件配置和注解
    • 类名由表名生成,只有首字母大写有区别,其他无区别
    • Java对象的属性由表中字段生成,完全对应
    • 目前,只支持表中只有一个主键,联合主键不支持

9.1 第一版SORM

  • bean

    • ColumnInfo

      package com.sxt.SORM.bean;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ColumnInfo.java
       * @time: 2020/3/11 13:46
       * @desc: |封装表中一个字段的信息
       */
      
      public class ColumnInfo {
          // 字段名称
          private String name;
          // 字段数据类型
          private String dataType;
          // 字段的键类型(0普通键;1主键;2外键)
          private int keyType;
      
          public ColumnInfo() {
          }
      
          public ColumnInfo(String name, String dataType, int keyType) {
              this.name = name;
              this.dataType = dataType;
              this.keyType = keyType;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getDataType() {
              return dataType;
          }
      
          public void setDataType(String dataType) {
              this.dataType = dataType;
          }
      
          public int getKeyType() {
              return keyType;
          }
      
          public void setKeyType(int keyType) {
              this.keyType = keyType;
          }
      }
      
    • Configuration

      package com.sxt.SORM.bean;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Configuration.java
       * @time: 2020/3/11 13:55
       * @desc: |管理配置信息
       */
      
      public class Configuration {
          // 正在使用哪个数据库
          private String usingDb;
          // jdbc的url
          private String URL;
          // 驱动类
          private String driver;
          // 数据库的用户名
          private String user;
          // 数据库的密码
          private String pwd;
          // 项目的源码路径
          private String srcPath;
          // 扫描生成java类的包(po的意思是Persistence Object持久化对象)
          private String poPackage;
      
          public Configuration() {
          }
      
          public Configuration(String usingDb, String URL, String driver, String user, String pwd, String srcPath, String poPackage) {
              this.usingDb = usingDb;
              this.URL = URL;
              this.driver = driver;
              this.user = user;
              this.pwd = pwd;
              this.srcPath = srcPath;
              this.poPackage = poPackage;
          }
      
          public String getUsingDb() {
              return usingDb;
          }
      
          public void setUsingDb(String usingDb) {
              this.usingDb = usingDb;
          }
      
          public String getURL() {
              return URL;
          }
      
          public void setURL(String URL) {
              this.URL = URL;
          }
      
          public String getDriver() {
              return driver;
          }
      
          public void setDriver(String driver) {
              this.driver = driver;
          }
      
          public String getUser() {
              return user;
          }
      
          public void setUser(String user) {
              this.user = user;
          }
      
          public String getPwd() {
              return pwd;
          }
      
          public void setPwd(String pwd) {
              this.pwd = pwd;
          }
      
          public String getSrcPath() {
              return srcPath;
          }
      
          public void setSrcPath(String srcPath) {
              this.srcPath = srcPath;
          }
      
          public String getPoPackage() {
              return poPackage;
          }
      
          public void setPoPackage(String poPackage) {
              this.poPackage = poPackage;
          }
      }
      
    • JavaFieldGetSet

      package com.sxt.SORM.bean;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: JavaFieldGetSet.java
       * @time: 2020/3/11 18:24
       * @desc: |封装了java属性和get、set方法的源代码
       */
      
      public class JavaFieldGetSet {
          // 属性源码信息。如:private int userId;
          private String fieldInfo;
          // get方法的源码信息。如:public int getUserId;
          private String getInfo;
          // set方法的源码信息。如:public void setUserId(int id){this.id = id;}
          private String setInfo;
      
          public JavaFieldGetSet() {
          }
      
          public JavaFieldGetSet(String fieldInfo, String getInfo, String setInfo) {
              this.fieldInfo = fieldInfo;
              this.getInfo = getInfo;
              this.setInfo = setInfo;
          }
      
          public String getFieldInfo() {
              return fieldInfo;
          }
      
          public void setFieldInfo(String fieldInfo) {
              this.fieldInfo = fieldInfo;
          }
      
          public String getGetInfo() {
              return getInfo;
          }
      
          public void setGetInfo(String getInfo) {
              this.getInfo = getInfo;
          }
      
          public String getSetInfo() {
              return setInfo;
          }
      
          public void setSetInfo(String setInfo) {
              this.setInfo = setInfo;
          }
      
          @Override
          public String toString() {
              // System.out.println(fieldInfo);
              // System.out.println(getInfo);
              // System.out.println(setInfo);
              return super.toString();
          }
      }
      
    • TableInfo

      package com.sxt.SORM.bean;
      
      import java.util.List;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TableInfo.java
       * @time: 2020/3/11 13:56
       * @desc: |存储表结构信息
       */
      
      public class TableInfo {
          // 表名
          private String tname;
          // 所有字段的信息
          private Map<String, ColumnInfo> columns;
          // 唯一主键(目前只能处理表中有且只有一个的情况)
          private ColumnInfo onlyPriKey;
          // 如果联合主键,则在这里存储
          private List<ColumnInfo> priKeys;
      
          public TableInfo() {
          }
      
          public TableInfo(String tname, List<ColumnInfo> priKeys, Map<String, ColumnInfo> columns) {
              this.tname = tname;
              this.columns = columns;
              this.priKeys = priKeys;
          }
      
          public String getTname() {
              return tname;
          }
      
          public void setTname(String tname) {
              this.tname = tname;
          }
      
          public Map<String, ColumnInfo> getColumns() {
              return columns;
          }
      
          public void setColumns(Map<String, ColumnInfo> columns) {
              this.columns = columns;
          }
      
          public ColumnInfo getOnlyPriKey() {
              return onlyPriKey;
          }
      
          public void setOnlyPriKey(ColumnInfo onlyPriKey) {
              this.onlyPriKey = onlyPriKey;
          }
      
          public List<ColumnInfo> getPriKeys() {
              return priKeys;
          }
      
          public void setPriKeys(List<ColumnInfo> priKeys) {
              this.priKeys = priKeys;
          }
      }
      
  • core

    • Query

      package com.sxt.SORM.core;
      
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Query.java
       * @time: 2020/3/10 17:31
       * @desc: |负责查询(对外提供服务的核心类)
       */
      
      public interface Query {
      
          /**
           * 直接执行一个DML语句
           *
           * @param sql    sql语句
           * @param params 参数
           * @return 执行sql语句后影响记录的行数
           */
          public int executeDML(String sql, Object[] params);
      
          /**
           * 将一个对象存储到数据库中
           *
           * @param obj 要存储的对象
           */
          public void insert(Object obj);
      
          /**
           * 删除clazz表示类对应的表中的记录(指定主键id的记录)
           * 把对象中不为null的属性往数据库中存储!如果数字为null则放0
           * @param clazz 跟表对应的类的Class对象
           * @param id    主键的值
           */
          // delete from User where id = 2;
          public void delete(Class clazz, Object id);
      
          /**
           * 删除对象在数据库中对应的记录(对象所在类对应到表,对象的主键对应到的记录)
           *
           * @param obj
           */
          public void delete(Object obj);
      
          /**
           * 更新对象对应的记录,并且只更新指定的字段的值
           *
           * @param obj        索要更新的对象
           * @param fieldNames 更新的属性列表
           * @return 执行sql语句后影响记录的行数
           */
          // update user set uname=?, pwe=?
          public int update(Object obj, String[] fieldNames);
      
          /**
           * 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中
           *
           * @param sql    查询语句
           * @param clazz  封装数据的javabean类的Class对象
           * @param params sql的参数
           * @return 返回查询到的结果
           */
          public List queryRows(String sql, Class clazz, Object[] params);
      
          /**
           * 查询返回一行记录,并将该记录封装到clazz指定的类的对象中
           *
           * @param sql    查询语句
           * @param clazz  封装数据的javabean类的Class对象
           * @param params sql的参数
           * @return 返回查询到的结果
           */
          public Object queryUniqueRows(String sql, Class clazz, Object[] params);
      
          /**
           * 查询返回一个值(一行一列),并将该值返回
           *
           * @param sql    查询语句
           * @param params sql的参数
           * @return 返回查询到的结果
           */
          public Object queryValue(String sql, Object[] params);
      
          /**
           * 查询返回一个数字(一行一列),并将该值返回
           *
           * @param sql    查询语句
           * @param params sql的参数
           * @return 返回查询到的数字
           */
          public Number queryNumber(String sql, Object[] params);
      }
      
    • MysqlQuery

      package com.sxt.SORM.core;
      
      import com.sxt.SORM.bean.ColumnInfo;
      import com.sxt.SORM.bean.TableInfo;
      import com.sxt.SORM.po.Emp;
      import com.sxt.SORM.utils.JDBCUtils;
      import com.sxt.SORM.utils.ReflectUtils;
      import com.sxt.SORM.vo.EmpVO;
      
      import java.lang.reflect.Field;
      import java.sql.*;
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MysqlQuery.java
       * @time: 2020/3/13 16:54
       * @desc: |负责针对mysql数据库的查询
       */
      
      public class MysqlQuery implements Query {
      
          public static void main(String[] args) {
              Object obj = new MysqlQuery().queryValue("select count(*) from emp where salary>?", new Object[]{1000});
              System.out.println(obj);
          }
      
          /**
           * 复杂多行查询测试
           */
          public static void testQueryRows() {
              List<Emp> list = new MysqlQuery().queryRows("select id,empname,age from emp where age>? and salary<?", Emp.class,
                      new Object[]{1, 9000});
              for (Emp e : list) {
                  System.out.println(e.getEmpname());
              }
      
              String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e"
                      + " " + "join dept d on e.deptId=d.id;";
              List<EmpVO> list2 = new MysqlQuery().queryRows(sql2, EmpVO.class, null);
              for (EmpVO e : list2) {
                  System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui());
              }
          }
      
          /**
           * 增删改操作测试
           */
          public static void testDML() {
              Emp e = new Emp();
              e.setEmpname("Tom");
              e.setBirthday(new java.sql.Date(System.currentTimeMillis()));
              e.setAge(30);
              e.setSalary(8888.0);
              e.setId(1);
      
              // new MysqlQuery().delete(e);
              // new MysqlQuery().insert(e);
              new MysqlQuery().update(e, new String[]{"empname", "age", "salary"});
          }
      
          @Override
          public int executeDML(String sql, Object[] params) {
              Connection conn = DBManager.getConn();
              int count = 0;
              PreparedStatement ps = null;
              try {
                  ps = conn.prepareStatement(sql);
                  // 给sql设置参数,就是?位置的参数
                  JDBCUtils.handleParams(ps, params);
                  count = ps.executeUpdate();
              } catch (SQLException e) {
                  e.printStackTrace();
              } finally {
                  DBManager.close(ps, conn);
              }
              return count;
          }
      
          @Override
          public void insert(Object obj) {
              // obj --> 表中。 insert into 表名(id, name, pwd) values (?, ?, ?)
              Class c = obj.getClass();
              // 存储sql的参数对象
              List<Object> params = new ArrayList<>();
              TableInfo tableInfo = TableContext.poClassTableMap.get(c);
              StringBuilder sql = new StringBuilder("insert into " + tableInfo.getTname() + " (");
      
              // 计算不为空的属性值
              int countNotNullField = 0;
      
              // 目前只能处理数据库来维护自增的方式
              Field[] fs = c.getDeclaredFields();
              for (Field f : fs) {
                  String fieldName = f.getName();
                  Object fieldValue = ReflectUtils.invokeGet(fieldName, obj);
                  if (fieldValue != null) {
                      // 如果该属性值不为空
                      countNotNullField++;
                      sql.append(fieldName + ",");
                      params.add(fieldValue);
                  }
              }
      
              // 把最后一个属性后面的,换成)
              sql.setCharAt(sql.length() - 1, ')');
              sql.append(" values (");
      
              for (int i = 0; i < countNotNullField; i++) {
                  sql.append("?,");
              }
              sql.setCharAt(sql.length() - 1, ')');
      
              executeDML(sql.toString(), params.toArray());
          }
      
          @Override
          public void delete(Class clazz, Object id) {
              // Emp.class, 2 --> delete from emp where id=2
              // 通过Class对象找TableInfo
              TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
              // 获得主键
              ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
      
              String sql = "delete from " + tableInfo.getTname() + " where " + onlyPriKey.getName() + "=?;";
              executeDML(sql, new Object[]{id});
          }
      
          @Override
          public void delete(Object obj) {
              Class c = obj.getClass();
              TableInfo tableInfo = TableContext.poClassTableMap.get(c);
              // 获得主键
              ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
              // 通过反射机制,调用属性对应的get方法或set方法
              Object priKeyValue = ReflectUtils.invokeGet(onlyPriKey.getName(), obj);
              delete(obj.getClass(), priKeyValue);
          }
      
          @Override
          public int update(Object obj, String[] fieldNames) {
              // obj{"uname", "pwd} --> update 表名 set uname=?, pwd=? where id=?
              Class c = obj.getClass();
              List<Object> params = new ArrayList<>();
              TableInfo tableInfo = TableContext.poClassTableMap.get(c);
              ColumnInfo priKey = tableInfo.getOnlyPriKey();
              StringBuilder sql = new StringBuilder("update " + tableInfo.getTname() + " set ");
      
              for (String fname : fieldNames) {
                  Object fvalue = ReflectUtils.invokeGet(fname, obj);
                  params.add(fvalue);
                  sql.append(fname + "=?,");
              }
              sql.setCharAt(sql.length() - 1, ' ');
              sql.append(" where ");
              sql.append(priKey.getName() + "=?");
              params.add(ReflectUtils.invokeGet(priKey.getName(), obj));
      
              return executeDML(sql.toString(), params.toArray());
          }
      
          @Override
          public List queryRows(String sql, Class clazz, Object[] params) {
              Connection conn = DBManager.getConn();
              // 存放查询结果的容器
              List list = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
              try {
                  ps = conn.prepareStatement(sql);
                  // 给sql设置参数,就是?位置的参数
                  JDBCUtils.handleParams(ps, params);
                  rs = ps.executeQuery();
                  ResultSetMetaData metaData = rs.getMetaData();
                  // 多行
                  while (rs.next()) {
                      if (list == null) {
                          list = new ArrayList();
                      }
                      // 调用javabean的无参构造器
                      Object rowObj = clazz.newInstance();
                      // 多列 select username, pwd, age from user where id>? and age>?
                      for (int i = 0; i < metaData.getColumnCount(); i++) {
                          // username
                          String columnName = metaData.getColumnLabel(i + 1);
                          Object columnValue = rs.getObject(i + 1);
      
                          // 调用rowObj对象的setUsername(String uname)方法,将columnValue的值设置进去
                          ReflectUtils.invokeSet(rowObj, columnName, columnValue);
                      }
                      list.add(rowObj);
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  DBManager.close(ps, conn);
              }
              return list;
          }
      
          @Override
          public Object queryUniqueRows(String sql, Class clazz, Object[] params) {
              List list = queryRows(sql, clazz, params);
              return (list == null && list.size() > 0) ? null : list.get(0);
          }
      
          @Override
          public Object queryValue(String sql, Object[] params) {
              Connection conn = DBManager.getConn();
              // 存储查询结果的对象
              Object value = null;
              PreparedStatement ps = null;
              ResultSet rs = null;
              try {
                  ps = conn.prepareStatement(sql);
                  // 给sql设置参数,就是?位置的参数
                  JDBCUtils.handleParams(ps, params);
                  rs = ps.executeQuery();
                  // 多行
                  while (rs.next()) {
                      // select count(*) from user
                      value = rs.getObject(1);
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  DBManager.close(ps, conn);
              }
              return value;
          }
      
          @Override
          public Number queryNumber(String sql, Object[] params) {
              return (Number) queryValue(sql, params);
          }
      }
      
    • TypeConvertor

      package com.sxt.SORM.core;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TypeConvertor.java
       * @time: 2020/3/11 13:39
       * @desc: |负责java数据类型和数据库数据类型的互相转换
       */
      
      public interface TypeConvertor {
          /**
           * 将数据库数据类型转化成java的数据类型
           * @param columnType 数据库字段的数据类型
           * @return java的数据类型
           */
          public String databaseType2JavaType(String columnType);
      
          /**
           * 将java数据类型转化为数据库数据类型
           * @param javaDataType java数据类型
           * @return 数据库数据类型
           */
          public String javaType2DatabaseType(String javaDataType);
      }
      
    • MySqlTypeConvertor

      package com.sxt.SORM.core;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MySqlTypeConvertor.java
       * @time: 2020/3/11 18:16
       * @desc: |mysql数据类型和java数据类型的转换
       */
      
      public class MySqlTypeConvertor implements TypeConvertor {
          @Override
          public String databaseType2JavaType(String columnType) {
              // varchar --> String
              if ("varchar".equalsIgnoreCase(columnType) || "char".equalsIgnoreCase(columnType)) {
                  return "String";
              } else if ("int".equalsIgnoreCase(columnType)
                      || "tinyint".equalsIgnoreCase(columnType)
                      || "smallint".equalsIgnoreCase(columnType)
                      || "integer".equalsIgnoreCase(columnType)) {
                  return "Integer";
              } else if ("bigint".equalsIgnoreCase(columnType)) {
                  return "long";
              } else if ("double".equalsIgnoreCase(columnType) || "float".equalsIgnoreCase(columnType)) {
                  return "Double";
              } else if ("clob".equalsIgnoreCase(columnType)) {
                  return "java.sql.Clob";
              } else if ("blob".equalsIgnoreCase(columnType)) {
                  return "java.sql.Blob";
              }else if("date".equalsIgnoreCase(columnType)){
                  return "java.sql.Date";
              }else if("time".equalsIgnoreCase(columnType)){
                  return "java.sql.Time";
              }else if("timestamp".equalsIgnoreCase(columnType)){
                  return "java.sql.Timestamp";
              }
              return null;
          }
      
          @Override
          public String javaType2DatabaseType(String javaDataType) {
              return null;
          }
      }
      
    • DBManager

      package com.sxt.SORM.core;
      
      import com.sxt.SORM.bean.Configuration;
      
      import java.io.IOException;
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.ResultSet;
      import java.sql.Statement;
      import java.util.Properties;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DBManager.java
       * @time: 2020/3/11 13:43
       * @desc: |根据配置信息,维持连接对象的管理(增加连接池功能)
       */
      
      public class DBManager {
          private static Configuration conf;
      
          static {
              // 静态代码块
              Properties pros = new Properties();
              try {
                  pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/SORM/db.properties"));
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              conf = new Configuration();
              conf.setDriver(pros.getProperty("driver"));
              conf.setPoPackage(pros.getProperty("poPackage"));
              conf.setPwd(pros.getProperty("pwd"));
              conf.setSrcPath(pros.getProperty("srcPath"));
              conf.setURL(pros.getProperty("URL"));
              conf.setUser(pros.getProperty("user"));
              conf.setUsingDb(pros.getProperty("usingDB"));
          }
      
          public static Connection getConn() {
              /*获取数据库(mysql)驱动连接*/
              // 加载驱动类
              try {
                  Class.forName(conf.getDriver());
                  // 直接建立连接,后期增加连接池处理,提高效率!
                  return DriverManager.getConnection(conf.getURL(), conf.getUser(), conf.getPwd());
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
          }
      
          public static void close(ResultSet rs, Statement ps, Connection conn) {
              /*关闭接口方法*/
              try {
                  if (rs != null) {
                      rs.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              try {
                  if (ps != null) {
                      ps.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              try {
                  if (conn != null) {
                      conn.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static void close(Statement ps, Connection conn) {
              /*关闭接口方法,重载*/
              try {
                  if (ps != null) {
                      ps.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              try {
                  if (conn != null) {
                      conn.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static void close(Connection conn) {
              /*关闭接口方法,重载*/
              try {
                  if (conn != null) {
                      conn.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static Configuration getConf(){
              return conf;
          }
      }
      
    • TableContext

      package com.sxt.SORM.core;
      
      import com.sxt.SORM.bean.ColumnInfo;
      import com.sxt.SORM.bean.TableInfo;
      import com.sxt.SORM.utils.JavaFileUtils;
      import com.sxt.SORM.utils.StringUtils;
      
      import java.sql.Connection;
      import java.sql.DatabaseMetaData;
      import java.sql.ResultSet;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.Map;
      
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TableContext.java
       * @time: 2020/3/11 13:42
       * @desc: |负责获取管理数据库所有表结构和类结构的关系,并可以根据表结构生成类结结构
       */
      
      public class TableContext {
          // 表名为key,表信息对象为value
          public static Map<String, TableInfo> tables = new HashMap<>();
          // 将po的calss对象和表信息对象关联起来,便于重用。
          public static Map<Class, TableInfo> poClassTableMap = new HashMap<>();
      
          private TableContext() {
          }
      
          static {
              try {
                  // 初始化获得的表信息
                  Connection conn = DBManager.getConn();
                  DatabaseMetaData dbmd = conn.getMetaData();
                  ResultSet tableSet = dbmd.getTables("", "%", "%", new String[]{"TABLE"});
      
                  while (tableSet.next()) {
                      // 循环每个表名
                      String tableName = (String) tableSet.getObject("TABLE_NAME");
                      TableInfo ti = new TableInfo(tableName, new ArrayList<ColumnInfo>(), new HashMap<String, ColumnInfo>());
                      tables.put(tableName, ti);
                      // 查询表中的所有字段
                      ResultSet set = dbmd.getColumns("", "%", tableName, "%");
                      while (set.next()) {
                          // 循环每个列名
                          ColumnInfo ci = new ColumnInfo(set.getString("COLUMN_NAME"), set.getString("TYPE_NAME"), 0);
                          ti.getColumns().put(set.getString("COLUMN_NAME"), ci);
                      }
      
                      // 查询表中的主键
                      // System.out.println(tableName);
                      ResultSet set2 = dbmd.getPrimaryKeys("", "%", tableName);
                      while (set2.next()) {
                          ColumnInfo ci2 = (ColumnInfo) ti.getColumns().get(set2.getObject("COLUMN_NAME"));
                          // 设置为主键类型
                          ci2.setKeyType(1);
                          ti.getPriKeys().add(ci2);
                      }
                      if (ti.getPriKeys().size() > 0) {
                          // 取唯一主键。方便使用。如果是联合主键。则为空!
                          ti.setOnlyPriKey(ti.getPriKeys().get(0));
                      }
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
      
              // 更新类结构
              updateJavaPOFile();
              // 加载po包下面的所有类,便于重用,提高效率!
              loadPOTables();
          }
      
          /**
           * 根据表结构,更新配置的po包下面的java类
           */
          public static void updateJavaPOFile() {
              Map<String, TableInfo> map = TableContext.tables;
              for (TableInfo t : map.values()) {
                  JavaFileUtils.createJavaPOFile(t, new MySqlTypeConvertor());
              }
          }
      
          /**
           * 加载po包下面的类
           */
          public static void loadPOTables() {
              for (TableInfo tableInfo : tables.values()) {
                  try {
                      Class c = Class.forName(DBManager.getConf().getPoPackage() + "." + StringUtils.firstChar2UpperCase(tableInfo.getTname()));
                      poClassTableMap.put(c, tableInfo);
                  } catch (ClassNotFoundException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public static void main(String[] args) {
              Map<String, TableInfo> tables = TableContext.tables;
              System.out.println(tables);
          }
      
      }
      
  • utils

    • JavaFileUtils

      package com.sxt.SORM.utils;
      
      import com.sxt.SORM.bean.ColumnInfo;
      import com.sxt.SORM.bean.JavaFieldGetSet;
      import com.sxt.SORM.bean.TableInfo;
      import com.sxt.SORM.core.DBManager;
      import com.sxt.SORM.core.MySqlTypeConvertor;
      import com.sxt.SORM.core.TableContext;
      import com.sxt.SORM.core.TypeConvertor;
      
      import java.io.*;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: JavaFileUtils.java
       * @time: 2020/3/11 13:44
       * @desc: | 封装了生成java文件(源代码)常用操作
       */
      
      public class JavaFileUtils {
          /**
           * 根据字段信息生成java属性信息。如varchar username --> private String username;以及相应的set和get方法源码
           *
           * @param column    字段信息
           * @param convertor 类型转化器
           * @return java属性和set/get方法源码
           */
          public static JavaFieldGetSet createFieldGetSetSRC(ColumnInfo column, TypeConvertor convertor) {
              JavaFieldGetSet jfgs = new JavaFieldGetSet();
              String javaFieldType = convertor.databaseType2JavaType(column.getDataType());
              jfgs.setFieldInfo("\tprivate " + javaFieldType + " " + column.getName() + ";\n");
      
              // public String getUsername(){return username;}
              StringBuilder getSrc = new StringBuilder();
              getSrc.append("\tpublic " + javaFieldType + " get" + StringUtils.firstChar2UpperCase(column.getName()) + "(){\n");
              getSrc.append("\t\treturn " + column.getName() + ";\n");
              getSrc.append("\t}\n");
              jfgs.setGetInfo(getSrc.toString());
      
              // public void setUsername(String username){this.username = username;}
              StringBuilder setSrc = new StringBuilder();
              setSrc.append("\tpublic void set" + StringUtils.firstChar2UpperCase(column.getName()) + "(");
              setSrc.append(javaFieldType + " " + column.getName() + "){\n");
              setSrc.append("\t\tthis." + column.getName() + " = " + column.getName() + ";\n");
              setSrc.append("\t}\n");
              jfgs.setSetInfo(setSrc.toString());
      
              return jfgs;
          }
      
          /**
           * 根据表信息生成java类的源代码
           *
           * @param tableInfo 表信息
           * @param convertor 数据类型转化器
           * @return java类的源代码
           */
          public static String createJavaSrc(TableInfo tableInfo, TypeConvertor convertor) {
              Map<String, ColumnInfo> columns = tableInfo.getColumns();
              List<JavaFieldGetSet> javaFields = new ArrayList<>();
      
              for (ColumnInfo c : columns.values()) {
                  javaFields.add(createFieldGetSetSRC(c, convertor));
              }
              StringBuilder src = new StringBuilder();
      
              // 生成package语句
              src.append("package " + DBManager.getConf().getPoPackage() + ";\n\n");
              // 生成import语句
              src.append("import java.sql.*;\n");
              src.append("import java.util.*;\n\n");
              // 生成类声明语句
              src.append("public class " + StringUtils.firstChar2UpperCase(tableInfo.getTname()) + " {\n\n");
              // 生成属性列表
              for (JavaFieldGetSet f : javaFields) {
                  src.append(f.getFieldInfo());
              }
              src.append("\n\n");
              // 生成set方法列表
              for (JavaFieldGetSet f : javaFields) {
                  src.append(f.getSetInfo());
              }
              // 生成get方法列表
              for (JavaFieldGetSet f : javaFields) {
                  src.append(f.getGetInfo());
              }
              // 生成类结束
              src.append("}\n");
              // System.out.println(src);
              return src.toString();
          }
      
          public static void createJavaPOFile(TableInfo tableInfo, TypeConvertor convertor) {
              String src = createJavaSrc(tableInfo, convertor);
              String srcPath = DBManager.getConf().getSrcPath() + "\\";
              String packagePath = DBManager.getConf().getPoPackage().replaceAll("\\.", "/");
              // 修正poPackage路径,因为没有重新创建项目
              String[] packagePath_list = packagePath.split("/");
              packagePath = packagePath_list[packagePath_list.length - 1];
      
              File f = new File(srcPath + packagePath);
              // System.out.println(f.getAbsolutePath());
      
              if (!f.exists()) {
                  // 指定目录不存在则帮助用户建立该目录
                  f.mkdirs();
              }
      
              BufferedWriter bw = null;
              try {
                  bw = new BufferedWriter(new FileWriter(f.getAbsolutePath() + "/" + StringUtils.firstChar2UpperCase(tableInfo.getTname()) + ".java"));
                  bw.write(src);
                  System.out.println("建立表" + tableInfo.getTname() + "对应的java类");
                  bw.flush();
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  try {
                      if (bw != null) {
                          bw.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
      
          }
      
          public static void main(String[] args) {
              // 测试每一个表的field,set、get方法源码生成
              // ColumnInfo ci = new ColumnInfo("username", "int", 0);
              // JavaFieldGetSet f = createFieldGetSetSRC(ci, new MySqlTypeConvertor());
              // System.out.println(f);
              // System.out.println("\n--------------------" + "分割线" + "--------------------\n");
      
              // 测试每一个表的从头到尾完全源码生成
              // Map<String, TableInfo> map = TableContext.tables;
              // TableInfo t = map.get("emp");
              // createJavaSrc(t, new MySqlTypeConvertor());
      
              Map<String, TableInfo> map = TableContext.tables;
              // TableInfo t = map.get("emp");
              for(TableInfo t: map.values()) {
                  createJavaPOFile(t, new MySqlTypeConvertor());
              }
          }
      }
      
    • JDBCUtils

      package com.sxt.SORM.utils;
      
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: JDBCUtils.java
       * @time: 2020/3/11 13:43
       * @desc: | 封装了JDBC查询常用的操作
       */
      
      public class JDBCUtils {
          /**
           * 给sql设置参数,就是?位置的参数
           * @param ps 预编译sql语句对象
           * @param params 参数
           */
          public static void handleParams(PreparedStatement ps, Object[] params) {
              if (params != null) {
                  for (int i = 0; i < params.length; i++) {
                      try {
                          ps.setObject(1 + i, params[i]);
                      } catch (SQLException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
      
    • StringUtils

      package com.sxt.SORM.utils;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: StringUtils.java
       * @time: 2020/3/11 13:44
       * @desc: | 封装了字符串常用的操作
       */
      
      public class StringUtils {
          /**
           * 将目标字符串首字母变为大写
           * @param str 目标字符串
           * @return 首字母变为大写的字符串
           */
          public static String firstChar2UpperCase(String str){
              // abcd-->Abcd
              return str.toUpperCase().substring(0, 1) + str.substring(1);
          }
      }
      
    • ReflectUtils

      package com.sxt.SORM.utils;
      
      import java.lang.reflect.Method;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ReflectUtils.java
       * @time: 2020/3/11 13:44
       * @desc: | 封装了反射的常用操作
       */
      
      public class ReflectUtils {
          /**
           * 调用obj对象对应属性fieldName的get方法
           *
           * @param fieldName 属性名
           * @param obj       Object对象
           * @return
           */
          public static Object invokeGet(String fieldName, Object obj) {
              // 通过反射机制,调用属性对应的get方法或set方法
              try {
                  Class c = obj.getClass();
                  Method m = c.getDeclaredMethod("get" + StringUtils.firstChar2UpperCase(fieldName), null);
                  return m.invoke(obj, null);
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
          }
      
          public static void invokeSet(Object obj, String columnName, Object columnValue) {
              try {
                  if (columnValue != null) {
                      Method m = obj.getClass().getDeclaredMethod("set" + StringUtils.firstChar2UpperCase(columnName),
                              columnValue.getClass());
                      m.invoke(obj, columnValue);
                  }
              } catch (Exception e) {
                  e.printStackTrace();
      
              }
          }
      }
      
  • po(自动生成的代码保存位置)

    • Dept

      package com.sxt.SORM.po;
      
      public class Dept {
      
      	private String address;
      	private Integer id;
      	private String dname;
      
      
      	public void setAddress(String address){
      		this.address = address;
      	}
      	public void setId(Integer id){
      		this.id = id;
      	}
      	public void setDname(String dname){
      		this.dname = dname;
      	}
      	public String getAddress(){
      		return address;
      	}
      	public Integer getId(){
      		return id;
      	}
      	public String getDname(){
      		return dname;
      	}
      }
      
    • Emp

      package com.sxt.SORM.po;
      
      public class Emp {
      
          private String empname;
          private java.sql.Date birthday;
          private Double bonus;
          private Integer deptId;
          private Integer id;
          private Double salary;
          private Integer age;
      
          public String getEmpname() {
              return empname;
          }
      
          public void setEmpname(String empname) {
              this.empname = empname;
          }
      
          public java.sql.Date getBirthday() {
              return birthday;
          }
      
          public void setBirthday(java.sql.Date birthday) {
              this.birthday = birthday;
          }
      
          public Double getBonus() {
              return bonus;
          }
      
          public void setBonus(Double bonus) {
              this.bonus = bonus;
          }
      
          public Integer getDeptId() {
              return deptId;
          }
      
          public void setDeptId(Integer deptId) {
              this.deptId = deptId;
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public Double getSalary() {
              return salary;
          }
      
          public void setSalary(Double salary) {
              this.salary = salary;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      }
      
  • vo(复杂查询生成的类所保存的位置)

    • EmpVO

      package com.sxt.SORM.vo;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: EmpVO.java
       * @time: 2020/3/15 12:44
       * @desc: |
       */
      
      public class EmpVO {
          // select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e
          // join dept d on e.deptId=d.id;
          private Integer id;
          private String empname;
          private Double xinshui;
          private Integer age;
          private String deptName;
          private String deptAddr;
      
          public EmpVO() {
          }
      
          public EmpVO(Integer id, String empname, Double xinshui, Integer age, String deptName, String deptAddr) {
              this.id = id;
              this.empname = empname;
              this.xinshui = xinshui;
              this.age = age;
              this.deptName = deptName;
              this.deptAddr = deptAddr;
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getEmpname() {
              return empname;
          }
      
          public void setEmpname(String empname) {
              this.empname = empname;
          }
      
          public Double getXinshui() {
              return xinshui;
          }
      
          public void setXinshui(Double xinshui) {
              this.xinshui = xinshui;
          }
      
          public Integer getAge() {
              return age;
          }
      
          public void setAge(Integer age) {
              this.age = age;
          }
      
          public String getDeptName() {
              return deptName;
          }
      
          public void setDeptName(String deptName) {
              this.deptName = deptName;
          }
      
          public String getDeptAddr() {
              return deptAddr;
          }
      
          public void setDeptAddr(String deptAddr) {
              this.deptAddr = deptAddr;
          }
      }
      
  • 配置文件(db.properties)

    usingDB=mysql
    URL=jdbc:mysql://localhost:3306/sorm?serverTimezone=UTC
    driver=com.mysql.cj.jdbc.Driver
    user=root
    pwd=123456
    srcPath=F:/BookStudy/else/JAVAPro/src/com/sxt/SORM
    poPackage=com.sxt.SORM.po
    
  • README(说明文件)

    1. 在src下建立db.properties
    2. 每张表只有一个主键,不能处理多个主键的情况
    3. po尽量使用包装类,不要使用基本数据类型
    4. 目前只能处理数据库来维护自增的方式
    

9.2 通过模板方法模式对Query进行优化

  • Query

    package com.sxt.SORM.core;
    
    import com.sxt.SORM.bean.ColumnInfo;
    import com.sxt.SORM.bean.TableInfo;
    import com.sxt.SORM.utils.JDBCUtils;
    import com.sxt.SORM.utils.ReflectUtils;
    
    import java.lang.reflect.Field;
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Query.java
     * @time: 2020/3/10 17:31
     * @desc: |负责查询(对外提供服务的核心类)
     */
    
    public abstract class Query implements Cloneable {
    
        /**
         * 采用模板方法模式将JDBC操作封装成模板,变于重用
         *
         * @param sql    sql语句
         * @param params sql的参数
         * @param clazz  记录要封装到的java类
         * @param back   CallBack的实现类,实现回调
         * @return 返回查询结果
         */
        public Object executeQueryTemplate(String sql, Object[] params, Class clazz, CallBack back) {
            Connection conn = DBManager.getConn();
            // 存放查询结果的容器
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                ps = conn.prepareStatement(sql);
                // 给sql设置参数,就是?位置的参数
                JDBCUtils.handleParams(ps, params);
                rs = ps.executeQuery();
                ResultSetMetaData metaData = rs.getMetaData();
    
                return back.doExecute(conn, ps, rs);
    
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            } finally {
                DBManager.close(ps, conn);
            }
        }
    
        /**
         * 直接执行一个DML语句
         *
         * @param sql    sql语句
         * @param params 参数
         * @return 执行sql语句后影响记录的行数
         */
        public int executeDML(String sql, Object[] params) {
            Connection conn = DBManager.getConn();
            int count = 0;
            PreparedStatement ps = null;
            try {
                ps = conn.prepareStatement(sql);
                // 给sql设置参数,就是?位置的参数
                JDBCUtils.handleParams(ps, params);
                count = ps.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                DBManager.close(ps, conn);
            }
            return count;
        }
    
        /**
         * 将一个对象存储到数据库中
         *
         * @param obj 要存储的对象
         */
        public void insert(Object obj) {
            // obj --> 表中。 insert into 表名(id, name, pwd) values (?, ?, ?)
            Class c = obj.getClass();
            // 存储sql的参数对象
            List<Object> params = new ArrayList<>();
            TableInfo tableInfo = TableContext.poClassTableMap.get(c);
            StringBuilder sql = new StringBuilder("insert into " + tableInfo.getTname() + " (");
    
            // 计算不为空的属性值
            int countNotNullField = 0;
    
            // 目前只能处理数据库来维护自增的方式
            Field[] fs = c.getDeclaredFields();
            for (Field f : fs) {
                String fieldName = f.getName();
                Object fieldValue = ReflectUtils.invokeGet(fieldName, obj);
                if (fieldValue != null) {
                    // 如果该属性值不为空
                    countNotNullField++;
                    sql.append(fieldName + ",");
                    params.add(fieldValue);
                }
            }
    
            // 把最后一个属性后面的,换成)
            sql.setCharAt(sql.length() - 1, ')');
            sql.append(" values (");
    
            for (int i = 0; i < countNotNullField; i++) {
                sql.append("?,");
            }
            sql.setCharAt(sql.length() - 1, ')');
    
            executeDML(sql.toString(), params.toArray());
        }
    
        /**
         * 删除clazz表示类对应的表中的记录(指定主键id的记录)
         * 把对象中不为null的属性往数据库中存储!如果数字为null则放0
         *
         * @param clazz 跟表对应的类的Class对象
         * @param id    主键的值
         */
        // delete from User where id = 2;
        public void delete(Class clazz, Object id) {
            // Emp.class, 2 --> delete from emp where id=2
            // 通过Class对象找TableInfo
            TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
            // 获得主键
            ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
    
            String sql = "delete from " + tableInfo.getTname() + " where " + onlyPriKey.getName() + "=?;";
            executeDML(sql, new Object[]{id});
        }
    
        /**
         * 删除对象在数据库中对应的记录(对象所在类对应到表,对象的主键对应到的记录)
         *
         * @param obj
         */
        public void delete(Object obj) {
            Class c = obj.getClass();
            TableInfo tableInfo = TableContext.poClassTableMap.get(c);
            // 获得主键
            ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
            // 通过反射机制,调用属性对应的get方法或set方法
            Object priKeyValue = ReflectUtils.invokeGet(onlyPriKey.getName(), obj);
            delete(obj.getClass(), priKeyValue);
        }
    
        /**
         * 更新对象对应的记录,并且只更新指定的字段的值
         *
         * @param obj        索要更新的对象
         * @param fieldNames 更新的属性列表
         * @return 执行sql语句后影响记录的行数
         */
        // update user set uname=?, pwe=?
        public int update(Object obj, String[] fieldNames) {
            // obj{"uname", "pwd} --> update 表名 set uname=?, pwd=? where id=?
            Class c = obj.getClass();
            List<Object> params = new ArrayList<>();
            TableInfo tableInfo = TableContext.poClassTableMap.get(c);
            ColumnInfo priKey = tableInfo.getOnlyPriKey();
            StringBuilder sql = new StringBuilder("update " + tableInfo.getTname() + " set ");
    
            for (String fname : fieldNames) {
                Object fvalue = ReflectUtils.invokeGet(fname, obj);
                params.add(fvalue);
                sql.append(fname + "=?,");
            }
            sql.setCharAt(sql.length() - 1, ' ');
            sql.append(" where ");
            sql.append(priKey.getName() + "=?");
            params.add(ReflectUtils.invokeGet(priKey.getName(), obj));
    
            return executeDML(sql.toString(), params.toArray());
        }
    
        /**
         * 查询返回多行记录,并将每行记录封装到clazz指定的类的对象中
         *
         * @param sql    查询语句
         * @param clazz  封装数据的javabean类的Class对象
         * @param params sql的参数
         * @return 返回查询到的结果
         */
        public List queryRows(final String sql, final Class clazz, final Object[] params) {
            // 存放查询结果的容器
            return (List) executeQueryTemplate(sql, params, clazz, new CallBack() {
                @Override
                public Object doExecute(Connection conn, PreparedStatement ps, ResultSet rs) {
                    List list = null;
                    try {
                        ResultSetMetaData metaData = rs.getMetaData();
                        // 多行
                        while (rs.next()) {
                            if (list == null) {
                                list = new ArrayList();
                            }
                            // 调用javabean的无参构造器
                            Object rowObj = clazz.newInstance();
                            // 多列 select username, pwd, age from user where id>? and age>?
                            for (int i = 0; i < metaData.getColumnCount(); i++) {
                                // username
                                String columnName = metaData.getColumnLabel(i + 1);
                                Object columnValue = rs.getObject(i + 1);
    
                                // 调用rowObj对象的setUsername(String uname)方法,将columnValue的值设置进去
                                ReflectUtils.invokeSet(rowObj, columnName, columnValue);
                            }
                            list.add(rowObj);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return list;
                }
            });
        }
    
        /**
         * 查询返回一行记录,并将该记录封装到clazz指定的类的对象中
         *
         * @param sql    查询语句
         * @param clazz  封装数据的javabean类的Class对象
         * @param params sql的参数
         * @return 返回查询到的结果
         */
        public Object queryUniqueRows(String sql, Class clazz, Object[] params) {
            List list = queryRows(sql, clazz, params);
            return (list != null || list.size() > 0) ? list.get(0) : null;
        }
    
        /**
         * 根据主键的值直接查找对应的对象
         * @param clazz
         * @param id
         * @return
         */
        public Object queryById(Class clazz, Object id){
            // select * from emp where id=?
            TableInfo tableInfo = TableContext.poClassTableMap.get(clazz);
            ColumnInfo onlyPriKey = tableInfo.getOnlyPriKey();
            String sql = "select * from " + tableInfo.getTname() +  " where " + onlyPriKey.getName() + "=?";
            return queryUniqueRows(sql, clazz, new Object[]{id});
        }
    
        /**
         * 查询返回一个值(一行一列),并将该值返回
         *
         * @param sql    查询语句
         * @param params sql的参数
         * @return 返回查询到的结果
         */
        public Object queryValue(String sql, Object[] params) {
            return executeQueryTemplate(sql, params, null, new CallBack() {
                @Override
                public Object doExecute(Connection conn, PreparedStatement ps, ResultSet rs) {
                    Object value = null;
                    try {
                        // 多行
                        while (rs.next()) {
                            // select count(*) from user
                            value = rs.getObject(1);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return value;
                }
            });
        }
    
        /**
         * 查询返回一个数字(一行一列),并将该值返回
         *
         * @param sql    查询语句
         * @param params sql的参数
         * @return 返回查询到的数字
         */
        public Number queryNumber(String sql, Object[] params) {
            return (Number) queryValue(sql, params);
        }
    
        /**
         * 分页查询
         *
         * @param pageNum 第几页数据
         * @param size    每页显示多少记录
         * @return
         */
        public abstract Object queryPagenate(int pageNum, int size);
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
  • MysqlQuery

    package com.sxt.SORM.core;
    
    import com.sxt.SORM.po.Emp;
    import com.sxt.SORM.vo.EmpVO;
    
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: MysqlQuery.java
     * @time: 2020/3/13 16:54
     * @desc: |负责针对mysql数据库的查询
     */
    
    public class MysqlQuery extends Query {
    
        public static void main(String[] args) {
            Object obj = new MysqlQuery().queryValue("select count(*) from emp where salary>?", new Object[]{1000});
            System.out.println(obj);
        }
    
        /**
         * 复杂多行查询测试
         */
        public static void testQueryRows() {
            List<Emp> list = new MysqlQuery().queryRows("select id,empname,age from emp where age>? and salary<?", Emp.class,
                    new Object[]{1, 9000});
            for (Emp e : list) {
                System.out.println(e.getEmpname());
            }
    
            String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e"
                    + " " + "join dept d on e.deptId=d.id;";
            List<EmpVO> list2 = new MysqlQuery().queryRows(sql2, EmpVO.class, null);
            for (EmpVO e : list2) {
                System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui());
            }
        }
    
        /**
         * 增删改操作测试
         */
        public static void testDML() {
            Emp e = new Emp();
            e.setEmpname("Tom");
            e.setBirthday(new java.sql.Date(System.currentTimeMillis()));
            e.setAge(30);
            e.setSalary(8888.0);
            e.setId(1);
    
            // new MysqlQuery().delete(e);
            // new MysqlQuery().insert(e);
            new MysqlQuery().update(e, new String[]{"empname", "age", "salary"});
        }
    
        @Override
        public Object queryPagenate(int pageNum, int size) {
            return null;
        }
    }
    

9.3 工厂模式+单例模式+克隆模式构建QueryFactory

  • 修改db.properties,增加queryClass的路径

    queryClass=com.sxt.SORM.core.MysqlQuery
    
  • 修改Configuration,增加queryClass的属性,getset方法

    // 项目使用的查询类的路径
    private String queryClass;
    
    public String getQueryClass() {
    	return queryClass;
    }
    
    public void setQueryClass(String queryClass) {
    	this.queryClass = queryClass;
    }
    
  • Query实现implements Cloneable接口

    public abstract class Query implements Cloneable
    
    protected Object clone() throws CloneNotSupportedException {
    	return super.clone();
    }
    
  • QueryFactory

    package com.sxt.SORM.core;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: QueryFactory.java
     * @time: 2020/3/11 13:33
     * @desc: |创建Query对象的工厂类:单例+克隆+工厂
     */
    
    public class QueryFactory {
        private static QueryFactory factory = new QueryFactory();
        // 原型对象
        private static Query prototypeObj;
    
        static{
            try {
                // 加载指定的Query类
                Class c = Class.forName(DBManager.getConf().getQueryClass());
                prototypeObj = (Query) c.newInstance();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 私有构造器
        private QueryFactory(){}
    
        public static Query createQuery(){
            try {
                return (Query) prototypeObj.clone();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
  • 客户端调用测试类

    package com.sxt.SORM.test;
    
    import com.sxt.SORM.core.MysqlQuery;
    import com.sxt.SORM.core.Query;
    import com.sxt.SORM.core.QueryFactory;
    import com.sxt.SORM.vo.EmpVO;
    
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Test.java
     * @time: 2020/3/17 12:55
     * @desc: |客户端调用测试类
     */
    
    public class Test {
        public static void main(String[] args){
            Query q = QueryFactory.createQuery();
            String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e"
                    + " " + "join dept d on e.deptId=d.id;";
            List<EmpVO> list2 = q.queryRows(sql2, EmpVO.class, null);
            for (EmpVO e : list2) {
                System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui());
            }
        }
    }
    

9.4 增加连接池提高效率

  • Connection Pool

  • 就是将Connection对象放入List中,反复重用!

  • 连接池的初始化:事先放入多个连接对象

  • 从连接池中取连接对象

    • 如果池中有可用连接,则将池中最后一个返回。同时,讲该连接从池中remove,表示正在使用。
    • 如果池中无可用连接,则创建一个新的。
  • 关闭连接

  • 不是真正关闭连接,而是将用完的连接放入连接池中。

  • 市面上的连接池产品:

    • DBCP
    • c3p0
    • proxool
  • 代码

    • 配置文件新增连接池参数

      poolMinSize=10
      poolMaxSize=100
      
    • Configuration增加上述参数的属性和getset方法

      // 连接池最小限制
      private int poolMinSize;
      // 连接池最大限制
      private int poolMaxSize;
      
      public int getPoolMinSize() {
      	return poolMinSize;
      }
      
      public void setPoolMinSize(int poolMinSize) {
      	this.poolMinSize = poolMinSize;
      }
      
      public int getPoolMaxSize() {
      	return poolMaxSize;
      }
      
      public void setPoolMaxSize(int poolMaxSize) {
      	this.poolMaxSize = poolMaxSize;
      }
      
    • 新增连接池类:DBConnPool

      package com.sxt.SORM.pool;
      
      import com.sxt.SORM.core.DBManager;
      
      import java.sql.Connection;
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DBConnPool.java
       * @time: 2020/3/17 14:03
       * @desc: |连接池的类
       */
      
      public class DBConnPool {
          // 最大连接数
          private static final int POOL_MAX_SIZE = DBManager.getConf().getPoolMaxSize();
          // 最小连接数
          private static final int POOL_MIN_SIZE = DBManager.getConf().getPoolMinSize();
          // 连接池对象
          private List<Connection> pool;
      
          public DBConnPool() {
              initPool();
          }
      
          /**
           * 初始化连接池,使池中的连接数达到最小值
           */
          public void initPool() {
              if (pool == null) {
                  pool = new ArrayList<Connection>();
              }
      
              while (pool.size() < DBConnPool.POOL_MIN_SIZE) {
                  pool.add(DBManager.createConn());
                  System.out.println("初始化池,池中连接数:" + pool.size());
              }
          }
      
          /**
           * 从连接池中取出一个连接
           */
          public synchronized Connection getConnection() {
              int last_index = pool.size() - 1;
              // 获得一个连接
              Connection conn = pool.get(last_index);
              pool.remove(last_index);
              return conn;
          }
      
          /**
           * 将连接放回池中
           *
           * @param conn
           */
          public synchronized void close(Connection conn) {
              if (pool.size() >= POOL_MAX_SIZE) {
                  try {
                      if (conn != null) {
                          conn.close();
                      }
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              } else {
                  pool.add(conn);
              }
          }
      }
      
    • 修改DBManager方法,把原来获取连接的方式,改为使用连接池,关闭连接的方式也做同样的修改

      package com.sxt.SORM.core;
      
      import com.sxt.SORM.bean.Configuration;
      import com.sxt.SORM.pool.DBConnPool;
      
      import java.io.IOException;
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.ResultSet;
      import java.sql.Statement;
      import java.util.Properties;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DBManager.java
       * @time: 2020/3/11 13:43
       * @desc: |根据配置信息,维持连接对象的管理(增加连接池功能)
       */
      
      public class DBManager {
          // 配置信息
          private static Configuration conf;
          // 连接池对象
          private static DBConnPool pool;
      
          static {
              // 静态代码块
              Properties pros = new Properties();
              try {
                  pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("com/sxt/SORM/db.properties"));
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              conf = new Configuration();
              conf.setDriver(pros.getProperty("driver"));
              conf.setPoPackage(pros.getProperty("poPackage"));
              conf.setPwd(pros.getProperty("pwd"));
              conf.setSrcPath(pros.getProperty("srcPath"));
              conf.setURL(pros.getProperty("URL"));
              conf.setUser(pros.getProperty("user"));
              conf.setUsingDb(pros.getProperty("usingDB"));
              conf.setQueryClass(pros.getProperty("queryClass"));
              conf.setPoolMaxSize(Integer.parseInt(pros.getProperty("poolMaxSize")));
              conf.setPoolMinSize(Integer.parseInt(pros.getProperty("poolMinSize")));
      
              // 加载TableContext
              System.out.println(TableContext.class);
          }
      
          public static Connection getConn() {
              /*获取数据库(mysql)驱动连接*/
              // 加载驱动类
      
              /* 第一种方法:直接取
              try {
                  Class.forName(conf.getDriver());
                  // 直接建立连接,后期增加连接池处理,提高效率!
                  return DriverManager.getConnection(conf.getURL(), conf.getUser(), conf.getPwd());
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
               */
      
              // 第二种方法,通过连接池
              if(pool == null){
                   pool = new DBConnPool();
              }
              return pool.getConnection();
          }
      
          /**
           * 创建新的Connection连接
           *
           * @return
           */
          public static Connection createConn() {
              /*获取数据库(mysql)驱动连接*/
              // 加载驱动类
              try {
                  Class.forName(conf.getDriver());
                  // 直接建立连接,后期增加连接池处理,提高效率!
                  return DriverManager.getConnection(conf.getURL(), conf.getUser(), conf.getPwd());
              } catch (Exception e) {
                  e.printStackTrace();
                  return null;
              }
          }
      
          public static void close(ResultSet rs, Statement ps, Connection conn) {
              /**关闭接口方法*/
              try {
                  if (rs != null) {
                      rs.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              try {
                  if (ps != null) {
                      ps.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              try {
                  //     if (conn != null) {
                  //         conn.close();
                  //     }
                  pool.close(conn);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static void close(Statement ps, Connection conn) {
              /**关闭接口方法,重载*/
              try {
                  if (ps != null) {
                      ps.close();
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              try {
                  //     if (conn != null) {
                  //         conn.close();
                  //     }
                  pool.close(conn);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static void close(Connection conn) {
              /*关闭接口方法,重载*/
              try {
                  //     if (conn != null) {
                  //         conn.close();
                  //     }
                  pool.close(conn);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      
          public static Configuration getConf() {
              return conf;
          }
      }
      
    • 客户端测试

      package com.sxt.SORM.test;
      
      import com.sxt.SORM.core.Query;
      import com.sxt.SORM.core.QueryFactory;
      import com.sxt.SORM.vo.EmpVO;
      
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Test2.java
       * @time: 2020/3/17 16:40
       * @desc: |测试连接池的调用效率
       */
      
      public class Test2 {
          public static void test1(){
              Query q = QueryFactory.createQuery();
              String sql2 = "select e.id,e.empname,salary+bonus 'xinshui',age,d.dname 'deptName',d.address 'deptAddr' from emp e"
                      + " " + "join dept d on e.deptId=d.id;";
              List<EmpVO> list2 = q.queryRows(sql2, EmpVO.class, null);
              for (EmpVO e : list2) {
                  System.out.println(e.getEmpname() + "-" + e.getDeptAddr() + "-" + e.getXinshui());
              }
          }
      
          public static void main(String[] args){
              long a = System.currentTimeMillis();
              for (int i = 0; i < 3000; i++) {
                  test1();
              }
              long b = System.currentTimeMillis();
              // 不加连接池的耗时:13077ms,增加连接池之后,耗时为2478
              System.out.println(b-a);
          }
      }
      

9.5 jar包和API文档的生成

  • idea导出参考连接:https://blog.csdn.net/rico_rico/article/details/84936785

  • javadoc导出参考连接:https://blog.csdn.net/qq_29347295/article/details/78635861

  • 其中编码需要改为:-encoding utf-8 -charset utf-8

  • 测试使用

    • 配置配置文件

      usingDB=mysql
      URL=jdbc:mysql://localhost:3306/sorm?serverTimezone=UTC
      driver=com.mysql.cj.jdbc.Driver
      user=root
      pwd=123456
      srcPath=F:/BookStudy/else/SORMDemo/src
      poPackage=po
      queryClass=com.sxt.SORM.core.MysqlQuery
      poolMinSize=10
      poolMaxSize=100
      
    • 测试po类的生成,增删改查

      package test;
      
      import com.sxt.SORM.core.Query;
      import com.sxt.SORM.core.QueryFactory;
      import po.Emp;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Test.java
       * @time: 2020/3/17 17:38
       * @desc: |
       */
      
      public class Test {
          public static void main(String[] args) {
              // 通过这个方法可以生成po类
              // TableContext.updateJavaPOFile();
      
              // add();
              // select();
              // delete();
              update();
      
          }
      
          public static void add() {
              // 测试插入对象
              Emp e = new Emp();
              e.setAge(18);
              e.setEmpname("我");
              e.setSalary(2000.0);
      
              Query q = QueryFactory.createQuery();
              q.insert(e);
          }
      
          public static void delete(){
              // 测试删除对象
              Emp e = new Emp();
              e.setId(12);
              Query q = QueryFactory.createQuery();
              q.delete(e);
          }
      
          public static void update(){
              // 测试删除对象
              Emp e = new Emp();
              e.setId(1);
              e.setAge(1);
              Query q = QueryFactory.createQuery();
              q.update(e, new String[]{"age"});
          }
      
          public static void select(){
              // 测试查询
              Query q = QueryFactory.createQuery();
              Number n = q.queryNumber("select count(*) from emp where salary>?", new Object[]{100});
              System.out.println(n);
          }
      }
      
  • 关于连表查询,我看操作实在是过于复杂,还要自己建立EmpVO,想想就算了。


我的CSDN:https://blog.csdn.net/qq_21579045

我的博客园:https://www.cnblogs.com/lyjun/

我的Github:https://github.com/TinyHandsome

纸上得来终觉浅,绝知此事要躬行~

欢迎大家过来OB~

by 李英俊小朋友


  1. 8 ↩︎

  2. 16 ↩︎

  3. 24 ↩︎

  4. 32 ↩︎

posted @ 2020-03-19 14:54  李英俊小朋友  阅读(352)  评论(0编辑  收藏  举报