11分钟安装ssl证书在Tomcat上

11分钟安装ssl证书在Tomcat上

正是因为这一点一滴觉得还有希望的自己,才是最无可救药的吧。


在Tomcat上正确的配置ssl证书,可使你的网站的HTTP转为HTTPS协议,能够让你的网站更加安全,同时也使域名在浏览器上显示小绿标更加美观点。

HTTPS有如下特点:

  1. 内容加密:采用混合加密技术,中间者无法直接查看明文内容
  2. 验证身份:通过证书认证客户端访问的是自己的服务器
  3. 保护数据完整性:防止传输的内容被中间人冒充或者篡改

1.在server.xm配置证书

添加如下 并在主端口修改 redirectPort=”443”

<Connector port="443"
    protocol="org.apache.coyote.http11.Http11NioProtocol" 
    SSLEnabled="true"
    scheme="https"
    secure="true"
    keystoreFile="/tomcat8/conf/mySSL/2826717_onfree.cn.pfx"   
    keystoreType="PKCS12"
    keystorePass="TlB73znA"   
    clientAuth="false"
    SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"
    ciphers="TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"/>

SSLCipherSuite:配置符合PFS规范的加密套餐,苹果ATS特性服务器需要(是否配置自行选择)

ciphers:服务器加密套件(是否配置自行选择)

SSLProtocol:在服务端TLS协议中启用TLS1.2,苹果ATS特性服务器和微信小程序都需要TLS版本为1.2以上,这个还需要jdk支持TLS1.2,jdk1.6和jdk1.7都需要第三方插件,jdk1.8默认支持TLS1.2(是否配置自行选择)

keystoreFile:证书文件的绝对路径

keystorePass:证书密码,这个文件里面

2.在web.xml配置强制将http跳转至https(可选配置)

#在</welcome-file-list>后添加以下内容:
<login-config>  
    <!-- Authorization setting for SSL -->  
    <auth-method>CLIENT-CERT</auth-method>  
    <realm-name>Client Cert Users-only Area</realm-name>  
</login-config>  
<security-constraint>  
    <!-- Authorization setting for SSL -->  
    <web-resource-collection >  
        <web-resource-name >SSL</web-resource-name>  
        <url-pattern>/*</url-pattern>  
    </web-resource-collection>  
    <user-data-constraint>  
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>  
    </user-data-constraint>  
</security-constraint>

11分钟使用Java中HttpClient

11分钟使用Java中HttpClient

从现在开始,我将追寻你的名字


一、HttpClient简介

在 Internet ,HTTP 协议上是使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

HttpClient实现了所有 HTTP 的方法(GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等)
支持 HTTPS 协议
支持代理服务器(Nginx等)等
支持自动(跳转)转向

二、HttpClient的主要功能:

GET无参:

/**
 * GET---无参测试
 * @date 2020年2月23日 下午4:18:50
 */
@Test
public void doGetTestOne() {
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    // 创建Get请求
    HttpGet httpGet = new HttpGet("http://localhost:12345/doGetControllerOne");
 
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 由客户端执行(发送)Get请求
        response = httpClient.execute(httpGet);
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

GET有参(方式一:直接拼接URL):

/**
 * GET---有参测试 (方式一:手动在url后面加上参数)
 * @date 2020年2月23日 下午4:19:23
 */
@Test
public void doGetTestWayOne() {
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
    // 参数
    StringBuffer params = new StringBuffer();
    try {
        // 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去)
        params.append("name=" + URLEncoder.encode("&", "utf-8"));
        params.append("&");
        params.append("age=24");
    } catch (UnsupportedEncodingException e1) {
        e1.printStackTrace();
    }
 
    // 创建Get请求
    HttpGet httpGet = new HttpGet("http://localhost:12345/doGetControllerTwo" + "?" + params);
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 配置信息
        RequestConfig requestConfig = RequestConfig.custom()
                // 设置连接超时时间(单位毫秒)
                .setConnectTimeout(5000)
                // 设置请求超时时间(单位毫秒)
                .setConnectionRequestTimeout(5000)
                // socket读写超时时间(单位毫秒)
                .setSocketTimeout(5000)
                // 设置是否允许重定向(默认为true)
                .setRedirectsEnabled(true).build();
 
        // 将上面的配置信息 运用到这个Get请求里
        httpGet.setConfig(requestConfig);
 
        // 由客户端执行(发送)Get请求
        response = httpClient.execute(httpGet);
 
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

GET有参(方式二:使用URI获得HttpGet):

/**
 * GET---有参测试 (方式二:将参数放入键值对类中,再放入URI中,从而通过URI得到HttpGet实例)
 * @date 2020年2月23日 下午4:19:23
 */
@Test
public void doGetTestWayTwo() {
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
    // 参数
    URI uri = null;
    try {
        // 将参数放入键值对类NameValuePair中,再放入集合中
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("name", "&"));
        params.add(new BasicNameValuePair("age", "18"));
        // 设置uri信息,并将参数集合放入uri;
        // 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value)
        uri = new URIBuilder().setScheme("http").setHost("localhost")
                              .setPort(12345).setPath("/doGetControllerTwo")
                              .setParameters(params).build();
    } catch (URISyntaxException e1) {
        e1.printStackTrace();
    }
    // 创建Get请求
    HttpGet httpGet = new HttpGet(uri);
 
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 配置信息
        RequestConfig requestConfig = RequestConfig.custom()
                // 设置连接超时时间(单位毫秒)
                .setConnectTimeout(5000)
                // 设置请求超时时间(单位毫秒)
                .setConnectionRequestTimeout(5000)
                // socket读写超时时间(单位毫秒)
                .setSocketTimeout(5000)
                // 设置是否允许重定向(默认为true)
                .setRedirectsEnabled(true).build();
 
        // 将上面的配置信息 运用到这个Get请求里
        httpGet.setConfig(requestConfig);
 
        // 由客户端执行(发送)Get请求
        response = httpClient.execute(httpGet);
 
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POST无参:

/**
 * POST---无参测试
 * @date 2020年2月23日 下午4:18:50
 */
@Test
public void doPostTestOne() {
 
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
    // 创建Post请求
    HttpPost httpPost = new HttpPost("http://localhost:12345/doPostControllerOne");
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 由客户端执行(发送)Post请求
        response = httpClient.execute(httpPost);
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
 
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POST有参(普通参数):

注:POST传递普通参数时,方式与GET一样即可,这里以直接在url后缀上参数的方式示例。

/**
 * POST---有参测试(普通参数)
 * @date 2020年2月23日 下午4:18:50
 */
@Test
public void doPostTestFour() {
 
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
    // 参数
    StringBuffer params = new StringBuffer();
    try {
        // 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去)
        params.append("name=" + URLEncoder.encode("&", "utf-8"));
        params.append("&");
        params.append("age=24");
    } catch (UnsupportedEncodingException e1) {
        e1.printStackTrace();
    }
 
    // 创建Post请求
    HttpPost httpPost = new HttpPost("http://localhost:12345/doPostControllerFour" + "?" + params);
 
    // 设置ContentType(注:如果只是传普通参数的话,ContentType不一定非要用application/json)
    httpPost.setHeader("Content-Type", "application/json;charset=utf8");
 
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 由客户端执行(发送)Post请求
        response = httpClient.execute(httpPost);
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
 
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POST有参(对象参数):

/**
 * POST---有参测试(对象参数)
 * @date 2020年2月23日 下午4:18:50
 */
@Test
public void doPostTestTwo() {
 
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
    // 创建Post请求
    HttpPost httpPost = new HttpPost("http://localhost:12345/doPostControllerTwo");
    User user = new User();
    user.setName("潘晓婷");
    user.setAge(18);
    user.setGender("女");
    user.setMotto("姿势要优雅~");
    // 我这里利用阿里的fastjson,将Object转换为json字符串;
    // (需要导入com.alibaba.fastjson.JSON包)
    String jsonString = JSON.toJSONString(user);
 
    StringEntity entity = new StringEntity(jsonString, "UTF-8");
 
    // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
    httpPost.setEntity(entity);
 
    httpPost.setHeader("Content-Type", "application/json;charset=utf8");
 
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 由客户端执行(发送)Post请求
        response = httpClient.execute(httpPost);
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
 
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

POST有参(普通参数 + 对象参数):

注:POST传递普通参数时,方式与GET一样即可,这里以通过URI获得HttpPost的方式为例。

/**
 * POST---有参测试(普通参数 + 对象参数)
 * @date 2020年2月23日 下午4:18:50
 */
@Test
public void doPostTestThree() {
 
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
    // 创建Post请求
    // 参数
    URI uri = null;
    try {
        // 将参数放入键值对类NameValuePair中,再放入集合中
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("flag", "4"));
        params.add(new BasicNameValuePair("meaning", "这是什么鬼?"));
        // 设置uri信息,并将参数集合放入uri;
        // 注:这里也支持一个键值对一个键值对地往里面放setParameter(String key, String value)
        uri = new URIBuilder().setScheme("http").setHost("localhost").setPort(12345)
                .setPath("/doPostControllerThree").setParameters(params).build();
    } catch (URISyntaxException e1) {
        e1.printStackTrace();
    }
 
    HttpPost httpPost = new HttpPost(uri);
    // HttpPost httpPost = new
    // HttpPost("http://localhost:12345/doPostControllerThree1");
 
    // 创建user参数
    User user = new User();
    user.setName("潘晓婷");
    user.setAge(18);
    user.setGender("女");
    user.setMotto("姿势要优雅~");
 
    // 将user对象转换为json字符串,并放入entity中
    StringEntity entity = new StringEntity(JSON.toJSONString(user), "UTF-8");
 
    // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
    httpPost.setEntity(entity);
 
    httpPost.setHeader("Content-Type", "application/json;charset=utf8");
 
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 由客户端执行(发送)Post请求
        response = httpClient.execute(httpPost);
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
 
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ssl证书忽略

    private static CloseableHttpClient getCloseableHttpClient() {
            try {
                SSLContextBuilder builder = new SSLContextBuilder();
                builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
                SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);
                Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", new PlainConnectionSocketFactory())
                        .register("https", sslConnectionSocketFactory)
                        .build();
    
                PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
                cm.setMaxTotal(100);
                return HttpClients.custom()
                        .setSSLSocketFactory(sslConnectionSocketFactory)
                        .setConnectionManager(cm)
                        .build();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            }
            return null;
        }

11分钟学习Java的五种json库及操作


11分钟学习Java的五种json库及操作

你的所言所行,全都闪烁着光芒,太过刺目,于是我闭上双眼,但内心还是无法停止对你的憧憬


​ Java 中json格式的字符串写法

String paramess="{\"name\":\"Mahesh\",\"password\":21}";

1. json-lib

json-lib具有通用性,但是比较麻烦,且时间有些久远,jar包只更新到2010年

项目地址:http://json-lib.sourceforge.net/index.html

1.1 maven依赖
<dependency>
    <groupId>net.sf.json-lib</groupId>
    <artifactId>json-lib</artifactId>
    <version>2.4</version>
    <classifier>jdk15</classifier>//jar包区分jdk1.3和jdk1.5版本
</dependency>
1.2 具体操作
  • 1.2.1 JSONObject与String相互转换

      JSONObject jsonobject= JSONObject.fromObject(str);
      String str = JSONObject.toString
    
  • 1.2.2 获取JSONObject数据及遍历JSONArray

    String str = JSONObject.getInt(“keys”)
    String str = JSONObject.getString(“keys”)
    JSONArray = JSONObject.getJSONArray(String);

  • 遍历:

      for(i<JSONArray.size())
      {
         JSONObject = JSONArray.getJSONObject(i); 
         JSONObject.getInt()...
      }
       
      Iterator<String> iterator = JSONObject.keys(); 
        while(iterator.hasNext()){
          String keys = iterator.next();
      }
    
  • 1.2.2 JSONObject转对象

      Grade grade = (Grade)JSONObject.toBean(jsonObject,Grade.class);
    

2.org.json

与json-lib相类似

2.1maven依赖
    <dependency>
          <groupId>org.json</groupId>
          <artifactId>json</artifactId>
          <version>20170516</version>
    </dependency>
2.2 创建json对象
    JSONObject = new JSONObject(str);
2.3 操作
net.sf.json.JSONObject: 没有严格要求获取字段的类型跟getXXX()的类型一样
org.json.JSONObject:获取的字段类型必须跟getXXX()的类型一样
2.4 JSONArray.length()

3. jackSon

springMVC内置解析器就是jackson

项目地址:https://github.com/FasterXML/jackson

3.1 maven依赖
    <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
    </dependency>
3.2 对象和String之间的转换
    ObjectMapper mapper=new ObjectMapper();
    //json转对象
    Grade grade=mapper.readValue(json1, Grade.class);
    //对象转json
    mapper.writeValueAsString(grade);
3.3 对JsonNode的遍历
    JsonNode jsonNode = mapper.readTree(jsonStr);
    Iterator<String> keys = jsonNode.fieldNames();
    while(keys.hasNext()){
        String fieldName = keys.next();
        System.out.println(fieldName + " : " + jsonNode.path(fieldName).toString());
    }

4. fastjson

阿里巴巴开源框架,效率最高

项目地址:https://github.com/alibaba/fastjson

4.1 maven依赖
    <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.37</version>
    </dependency>
4.2 String转对象
    Student stu = JSON.parseObject(json,Student.class);
    List<String> list=JSON.parseArray(json2, String.class);
4.3 对象转String
    JSON.toJSONString(stu);
    //or String json = JSON.toJSON(stu).toString();

5.GSON

谷歌产品 ,前功能最全

项目地址:https://github.com/google/gson

5.1 maven依赖
    <dependency>
          <groupId>com.google.code.gson</groupId>
          <artifactId>gson</artifactId>
          <version>2.8.1</version>
    </dependency>
5.2 String转对象
    Gson gson = new Gson();
    Grade grade = gson.fromJson(json1,Grade.class);
     
    ArrayList<String> list=gson.fromJson(json2,new TypeToken<ArrayList<String>>(){}.getType());
5.3 对象转String
    String json=gson.toJson(grade);

11分钟详解Redis的常用命令类型和配置

11分钟详解Redis的常用命令类型和配置

苦恼着,歇斯底里着,痛苦着,不断挣扎的数月时间,这一切会在未来的某一瞬间得到回报。我们或许就是被那个瞬间迷住的,一种无可救药的生物吧

一、数据类型

key

keys* 查看当前库全部keys

exists key 判断某个key是否存在

move key db 移动当前库的key到别的库

expire key 秒钟  设置给定的key设置过期时间

ttl key 查看剩余多少秒过期,-1表示永不过期,-2表示已过期

type key 查看你的key是什么类型

String

set/get/del/append/strlen 设置/获取/删除/在值后追加/值的长度

Incr/decr/incrby/decrby,加/减/加多少/减多少 必须为数字

getrange/setrange 获取值的范围/设置范围的值为什么

setex(set with expire)键秒值/setnx(set if not exist)[key] [time] [value] 设置带有效期的键值对/不存在同名键时添加

mset/mget/msetnx 同时多个键值对 设置/添加/不存在添加(只要一个存在都不执行)

List

lpush/rpush/lrange 左边入栈/右边入栈/获取范围的值(0 -1 为获取全部)

lpop/rpop 尾部出栈/头部出栈

lindex 按照索引下标获得元素(从上到下)

llen 获取list值长度

lrem key n value 删N个值

Itrim key 开始index 结束index  截取指定范围的值后再赋值给key 

rpoplpush 源key 目的key  从源list的头部出栈一位到目的list左边入栈

lset key index value 设置索引位的值

linsert key before/after 值1 值2  在某个值前或后插入值

set

zset 和 set 基本一样,不同的是zset 都会关联一个double类型的-score(分数)

sadd/smembers/sismember 添加/查询所有/查询是否存在

scard 获取集合里元素个数

srem key value 删除集合中元素

srandmember key [n]  随机出n个数

spop key 随机出栈

smove key1 key2    将key1里的某个值移动到key2

sdiff/sinter/sunion key1 key2    比较两key的差值/交集/并集

hash

hset/hget/hmset/hmget/hgetall/hdel 插入/获取/多插入/多获取/获取全部/删除

hlen 获取长度

hexists key  在key里面的某个值判断key是否存在

hkeys/hvals  获取全部key/获取全部value

hincrby/hincrbyfloat [n] 增加n值

hsetnx 插入(不存在时)

二、常见配置redis.conf介绍

redis.conf 配置项说明如下:
    1.Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 
        daemonize no
    2.当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
        pidfile/var/run/redis.pid
    3.指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
        port 6379
    4.绑定的主机地址
        bind 127.0.0.1
    5.当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
        timeout 300
    6.指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose 
        loglevel verbose
    7.日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null 
        logfile stdout
    8.设置数据库的数量,默认数据库为0,可以使用SELECT<dbid>命令在连接上指定数据库id 
        databases 16
    9.指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
        save <seconds><changes>
    Redis默认配置文件中提供了三个条件:
        save 9001
        save 30010
        save 6010000分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
    10.指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
        rdbcompression yes
    11.指定本地数据库文件名,默认值为dump.rdb 
        dbfilename dump.rdb
    12.指定本地数据库存放目录
        dir./
    13.设置当本机为lav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
        slaveof <masterip><masterport>
    14.当master服务设置了密码保护时,slav服务连接master的密码
        masterauth <master-password>
    15.设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
        requirepass foobared
    16.设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置maxclients0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息maxclients 12817.指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Reds新的vm机制,会把Key存放内存,Value会存放在swap区
        maxmemory<bytes>
    18.指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no 
        appendonly no
    19.指定更新日志文件名,默认为appendonly.aof 
        appendfilename appendonly.aof
    20.指定更新日志条件,共有3个可选值:
        no:表示等操作系统进行数据缓存同步到磁盘(快)
        always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
        everysec:表示每秒同步一次(折衷,默认值)
    21.指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
        vm-enabled no
    22.虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
        vm-swap-file/tmp/redis.swap
    23.将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
        vm-max-memory 0
    24.Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不确定,就使用默认值
        vm-page-size 32
    25.设置swap文件中的page数重,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
        vm-pages 134217728
    26.设置访间swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
        vm-max-threads 4
    27.设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
        glueoutputbuf yes
    28.指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
        hash-max-zipmap-entries64
        hash-max-zipmap-value 512
    29.指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
        activerehashing yes
    30.指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
        include/path/to/local.conf

11分钟了解Redis简介及使用场景

11分钟了解Redis简介及使用场景

星星在你的头顶上闪耀着,与你交互诉说的话语,一句一句地,如同星点般翩然落至眼前

一、Redis简介

​ Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value型数据库。

二、window下的安装

redis 64位下载地址:https://github.com/ServiceStack/redis-windows

1.修改redis.windows.conf文件

    maxmemory 1024*1024*1024

2.启动redis

    redis-server.exe redis.windows.conf

3.将redis加入到windows的服务中

    redis-server.exe --service-install redis.windows.conf --loglevel verbose

4.常用的redis服务命令。

卸载服务:redis-server --service-uninstall

开启服务:redis-server --service-start

停止服务:redis-server --service-stop

重命名服务:redis-server --service-name name

5.连接服务器

redis-cli.exe

三、linix下的安装

  1. 安装
    $ wget http://download.redis.io/releases/redis-2.8.17.tar.gz
    $ tar xzf redis-2.8.17.tar.gz
    $ cd redis-2.8.17
    $ make
    
  2. 启动服务
  • 默认启动

    $ cd src
    $ ./redis-server
    
  • 使用指定配置文件启动

    $ cd src
    $ ./redis-server ../redis.conf
    

四、redis使用场景

参考 redis使用场景及案例

一、缓存

项目场景:用户登录或注册时的验证码存储,用户名

set Code:1:code 1232 EX 100 NX
get Code:1:code     # 1232
set User:1:name bob EX 100 NX
get User:1:name         # bob

缓存是 redis 出镜率最高的一种使用场景,仅仅使用 set/get 就可以实现,不过也有一些需要考虑的点
1、如何更好的设置缓存
2、如何保持缓存与上游数据的一致性
3、如何解决缓存雪崩,缓存击穿问题(这两个问题会单独写一篇)

二:消息队列

lpush UserEmailQueue 1 2 3 4
lpop UserEmailQueue
rpop UserEmailQueue 1
rpop UserEmailQueue 2

可以把redis的队列看为分布式队列,作为消息队列时,生产者在一头塞入数据。消费者另一条取出数据:(lpush/rpop,rpush/lpop),不过也有一些不足,而这些不足有可能是致命的,不过对于一些丢几条消息也没关系的场景还是可以考虑的:
1、没有ack(消息确认机制),有可能丢消息
2、需要做redis的持久化配置

三:过滤器(dupefilter)

sadd UrlSet http://1
sadd UrlSet http://2
sadd UrlSet http://2

smembers UrlSet
“http://1”
“http://2”
scrapy-redis作为分布式的爬虫框架,便是使用了 redis 的 Set 这个数据结构来对将要爬取的 url 进行去重处理。
def request_seen(self, request):  
  """Returns True if request was already seen.   
Parameters   
-------
request : scrapy.http.Request    
Returns    
-------
 bool    
 """    
 fp = self.request_fingerprint(request)    
 added = self.server.sadd(self.key, fp)    
 return added == 0
不过当 url 过多时,会有内存占用过大的问题

四、分布式锁

分布式锁,这个是除了 KV 缓存之外最为常用的另一个特色功能。

set Lock:User:10086 06be97fc-f258-4202-b60b-8d5412dd5605 EX 60 NX
#释放锁,一段 LUA 脚本

if redis.call("get",KEYS[1]) == ARGV[1] then
      return redis.call("del",KEYS[1])
else
     return 0
end

这是一个最简单的单机版的分布式锁,有以下要点
1)EX 表示锁会过期释放
2)NX 保证原子性
解锁时对比资源对应产生的 UUID,避免误解锁
当你使用分布式锁是为了解决一些性能问题,如分布式定时任务防止执行多次 (做好幂等性),而且鉴于单点 redis 挂掉的可能性很小,可以使用这种单机版的分布式锁。

举个例子说明:
比如一个很能干的资深工程师,开发效率很快,代码质量也很高,是团队里的明星。所以呢诸多产品经理都要来烦他,让他给自己做需求。如果同一时间来了一堆产品经理都找他,它的思路呢就会陷入混乱,再优秀的程序员,大脑的并发能力也好不到哪里去。所以呢他就在自己的办公室的门把上挂了一个请勿打扰的牌子,当一个产品经理来的时候先看看门把上有没有这个牌子,如果没有呢就可以进来找工程师谈需求,谈之前要把牌子挂起来,谈完了再把牌子摘了。这样其它产品经理也要来烦他的时候,如果看见这个牌子挂在那里,就可以选择睡觉等待或者是先去忙别的事。如是这位明星工程师从此获得了安宁。

一定要设置这个过期时间,因为遇到特殊情况 —— 比如地震(进程被 kill -9,或者机器宕机),产品经理可能会选择从窗户上跳下去,没机会摘牌,导致了死锁饥饿,让这位优秀的工程师成了一位大闲人,造成严重的资源浪费。同时还需要注意这个 owner_id,它代表锁是谁加的 —— 产品经理的工号。以免你的锁不小心被别人摘掉了。释放锁时要匹配这个 owner_id,匹配成功了才能释放锁。这个 owner_id 通常是一个随机数,存放在 ThreadLocal 变量里(栈变量)。官方其实并不推荐这种方式,因为它在集群模式下会产生锁丢失的问题 —— 在主从发生切换的时候。官方推荐的分布式锁叫 RedLock,作者认为这个算法较为安全,推荐我们使用。不过我们一直还使用上面最简单的分布式锁。为什么我们不去使用 RedLock 呢,因为它的运维成本会高一些,需要 3 台以上独立的 Redis 实例,用起来要繁琐一些。另外,Redis 集群发生主从切换的概率也并不高,即使发生了主从切换出现锁丢失的概率也很低,因为主从切换往往都有一个过程,这个过程的时间通常会超过锁的过期时间,也就不会发生锁的异常丢失。还有呢就是分布式锁遇到锁冲突的机会也不多,这正如一个公司里明星程序员也比较有限一样,总是遇到锁排队那说明结构上需要优化。

五:定时任务

分布式定时任务有多种实现方式,最常见的一种是 master-workers 模型。
master 负责管理时间,到点了就将任务消息仍到消息中间件里,然后worker们负责监听这些消息队列来消费消息。
著名的 Python 定时任务框架 Celery 就是这么干的。但是 Celery 有一个问题,那就是 master 是单点的,如果这个 master 挂了,整个定时任务系统就停止工作了。

3AVW1U.png

另一种实现方式是 multi-master 模型。这个模型什么意思呢,就类似于 Java 里面的 Quartz 框架,采用数据库锁来控制任务并发。会有多个进程,每个进程都会管理时间,时间到了就使用数据库锁来争抢任务执行权,抢到的进程就获得了任务执行的机会,然后就开始执行任务,这样就解决了 master 的单点问题。

这种模型有一个缺点,那就是会造成竞争浪费问题,不过通常大多数业务系统的定时任务并没有那么多,所以这种竞争浪费并不严重。还有一个问题它依赖于分布式机器时间的一致性,如果多个机器上时间不一致就会造成任务被多次执行,这可以通过增加数据库锁的时间来缓解。

3AVnfK.png

现在有了 Redis 分布式锁,那么我们就可以在 Redis 之上实现一个简单的定时任务框架。

#注册定时任务
hset tasks name trigger_rule
#获取定时任务列表
hgetall tasks
#争抢任务
set lock:$(name) true nx ex=5
#任务列表变空
#轮询版本号,有变化就重新加载任务列表,重新调度时间有变化的任务
set tasks_version $new_version
get tasks_version

六、频率控制

项目的社区功能里,不可避免的总是会遇到垃圾内容,一觉醒来你会发现首页突然会被某些恶意的帖子和广告刷屏了,如果不采取适当的机制来控制就会导致用户体验受到严重的影响。

控制广告垃圾贴的策略很多,高级一点的可以通过AI,最简单的方式是通过关键词扫描,还有比较常用的一种方式是频率控制,限制单个用户内容的生产速度,不通等级的用户会有不同的频率控制参数。

频率控制就可以使用redis来实现,我们将用户的行为理解为一个时间序列,我们要保证在一定的时间内限制单个用户的时间序列的长度,超过这个长度就禁止用户的行为,它可以是用redis的zset(有序集合)来实现

3AEsJK.png

图中绿色的部门就是我们要保留的一个时间段的时间序列信息,灰色的段会被砍掉。统计绿色段中时间序列记录的个数就知道是否超过了频率的阈值。

下面的代码控制用户的ugc行为为每小时最对N次
hist_key:"ugc:${user_id}"
with redis.pipeline() as pipe:
#记录当前的行为
pipe.zadd(hist_key,ts,uuid)
#保留1小时内的行为序列
pipe.zremrangebyscore(hist_key, 0, now_ts -3600)
# 获取这1小时的行为数量
pipe.zcard(hist_key)
# 设置过期时间,节约内存
pipe.expire(hist_key, 3600)
# 批量执行
_ , _ , count, _ =pipe.exec()
return count > N

七、服务发现

如果想要技术成熟度再高一些,有的企业会有服务发现的基础设施。通常我们都会选用zookeeper、etcd,consul等分布式配置数据库来作为服务列表的存储。
它们有非常及时的通知机制来通知服务消费者服务列表发生了变更。那我们该如何使用 Redis 来做服务发现呢?

3AEurj.png

这里我们要再次使用 zset 数据结构,我们使用 zset 来保存单个服务列表。多个服务列表就使用多个 zset 来存储。
zset 的 value 和 score 分别存储服务的地址和心跳的时间。服务提供者需要使用心跳来汇报自己的存活,每隔几秒调用一次 zadd。服务提供者停止服务时,使用 zrem 来移除自己。

zadd service_key heartbeat_ts addr
zrem service_key addr

这样还不够,因为服务有可能是异常终止,根本没机会执行钩子,所以需要使用一个额外的线程来清理服务列表中的过期项

zremrangebyscore service_key 0 now_ts -30 # 30s都没来心跳

接下来还有一个重要的问题是如何通知消费者服务列表发生了变更,这里我们同样使用版本号轮询机制,当服务列表变更时,递增版本号。消费者通过轮询版本号的变化来重加载服务列表

if zadd() >0 || zrem() >0 ||zremrangebuscore() >0:
        incr service_version_key

但是还有一个问题,如果消费者依赖了很多的服务列表,那么它就需要轮询很多的版本号,这样的IO效率会比较低下。

这是我们可以再增加一个全局版本号,在任意的服务类表版本号发生变化时,递增全局版本号;

这样在正常情况下消费者只需要轮询全局版本号就可以了。当全局版本号发生变更时再挨个对依赖的服务类表的子版本号,然后加载有变更的服务列表

3AA48U.png

八、位图

项目里需要做一个球队成员的签到系统,当用户量比较少的时候,设计上比较简单,就是将用户的签到状态用redis的hash结构来存储,签到一次就再hash结构里记录一条,签到有三种状态:未签到,已签到和部签到,分别是0,1,2三个整数值。

hset sign:$(user_id) 2019-08-11 1
hset sign:$(user_id) 2019-08-12 0
hset sign:$(user_id) 2019-08-14 2

这个其实非常浪费用户空间,后来想做全部用户的签到,技术leader指出,这时候的再用hash就有问题了,他讲到当用户过千万的时候,内存可能会飚到 30G+,我们线上实例通常过了 20G 就开始报警,30G 已经属于严重超标了。

这时候我们就开始着手解决这个问题,去优化存储。我们选择使用位图来记录签到信息,一个签到状态需要两个位来记录,一个月的存储空间只需要 8 个字节。这样就可以使用一个很短的字符串来存储用户一个月的签到记录。

3AAeBR.png

但是位图也有一个缺点,它的底层是字符串,字符串是连续存储空间,位图会自动扩展,比如一个很大的位图 8m 个位,只有最后一个位是 1,其它位都是零,这也会占用1m 的存储空间,这样的浪费非常严重。

所以呢就有了咆哮位图这个数据结构,它对大位图进行了分段存储,全位零的段可以不用存。

另外还对每个段设计了稀疏存储结构,如果这个段上置 1 的位不多,可以只存储它们的偏移量整数。这样位图的存储空间就得到了非常显著的压缩。

九、 模糊计数

上面提到的签到系统,如果产品经理需要知道这个签到的日活月活怎么办呢?
通常我们会直接甩锅——请找数据部门。

但是数据部门的数据往往不是很实时,经常前一天的数据需要第二天才能跑出来,离线计算是通常是定时的一天一次。那如何实现一个实时的活跃计数?

最简单的方案就是在 Redis 里面维护一个 set 集合,来一个用户,就 sadd 一下,最终集合的大小就是我们需要的 UV 数字。

但是这个空间浪费严重怎么办?这时候就需要使用redis提供的HyperLogLog模糊计数功能,它是一种概率计数,有一定的误差,大约是0.81%。

但是空间占用很小,其底层是一个位图,它最多只会占用12k的存储空间,而且在计数值比较小的时候,位图使用稀疏存储,空间占用就更小了。

#记录用户
pfadd sign_uv_${day} user_id
#获取记录数量
p count sign_uv_${day}

微信公众号文章的阅读数可以使用它,网页的 UV 统计它都可以完成。但是如果产品经理非常在乎数字的准确性,比如某个统计需求和金钱直接挂钩,那么你可以考虑一下前面提到的咆哮位图。

它使用起来会复杂一些,需要提前将用户 ID 进行整数序列化。Redis 没有原生提供咆哮位图的功能,但是有一个开源的 Redis Module 可以拿来即用。

十、布隆过滤器

如果系统即将会有大量的新用户涌入时,布隆过滤器就会非常有价值,可以显著降低缓存的穿透率,降低数据库的压力
这个新用户的涌入不一定是业务系统的大规模铺开,也可能是因为来自外部的缓存穿透攻击;

def get_user_state(user_id):
        state = cache.get(user_id)
        if not state:
            state = db.get(user_id) or{}
            cache.set(user_id,state)
        return state
def save_user_state(user_id,state):
        cache.set(user_id,state)
        db.set_async(user_id,state)

比如就上面这个业务系统的用户状态查询接口代码,现在一个新用户过来,会先去缓存里查询有没有这个用户的状态数据
因为是新用户,所以肯定缓存里没有,然后它就要去数据库里查,结过数据库也没有,如果这样的新用户大批量瞬间涌入,那么可以遇见数据库的压力会比较大,会存在大量的空查询;

我们非常希望redis里面有这样一个set,它存放了所有的用户id,这样通过查询这个set集合就知道是不是新用户来了
当用户量非常庞大的时候,维护这样的一个集合需要的存储空间是很大的;

这时候就可以使用布隆过滤器,它相当于一个set,但是又不同于set,它需要的存储空间要小的多;

比如存储一个用户id需要64个字节,而布隆过滤器存储一个用户ID只需要1个字节多点,其实它存的不是用户id,而是用户id的指纹,所以会存在一定的小概率误判,它是一个具备模糊过滤能力的容器;

当它说用户 id 不在容器中时,那么就肯定不在。当它说用户 id 在容器里时,99% 的概率下它是正确的,还有 1% 的概率它产生了误判。

3AkEwt.png

不过在这个案例中,这个误判并不会产生问题,误判的代价只是缓存穿透而已。
相当于有 1% 的新用户没有得到布隆过滤器的保护直接穿透到数据库查询,而剩下的 99% 的新用户都可以被布隆过滤器有效的挡住,避免了缓存穿透

3AFurR.png

布隆过滤器的原理有一个很好的比喻,那就是在冬天一片白雪覆盖的地面上,如果你从上面走过,就会留下你的脚印。如果地面上有你的脚印,那么就可以大概率断定你来过这个地方,但是也不一定,也许别人的鞋正好和你穿的一模一样。可是如果地面上没有你的脚印,那么就可以 100% 断定你没来过这个地方。

11分钟理解Java反射

11分钟理解Java反射

星星在你的头顶上闪耀着,与你交互诉说的话语,一句一句地,如同星点般翩然落至眼前


一、简介

JAVA反射机制是在运行状态中.
对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性.

我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象,或者类类型。

二、方法:

1.如何获取.Class文件对象

a.  通过Object类 getClass()方法获取 Class对象

b. 通过类名.class 方式 获取 Class对象

c.  通过反射的方式, Class.forName(String classname) 获取Class对象
public static Class<?> forName(String className)throws ClassNotFoundException
返回与带有给定字符串名的类或接口相关联的 Class 对象

2.通过反射,获取类中的构造方法,并完成对象的创建

  • 获取指定的构造方法

    public Constructor getConstructor(Class<?>… parameterTypes)

  • 获取指定的public修饰的构造方法

    public Constructor getDeclaredConstructor(Class<?>… parameterTypes)

  • 获取指定的构造方法,包含私有的

    public Constructor getConstructor(Class<?>… parameterTypes)

  • 获取所有的构造方法

    public Constructor[] getConstructors() 获取所有的public 修饰的构造方法 public Constructor[] getDeclaredConstructors() 获取所有的构造方法,包含私有的

3.通过反射, 获取类中的构造方法,并完成对象的创建

步骤:
a.获取字节码文件对象
b.通过字节码文件对象 ,获取到指定的构造方法
    getConstructor(参数);
c.通过构造方法,创建对象
    public T newInstance(Object... initargs)

4.私有构造方法,创建对象

a.获取字节码文件对象
b.通过字节码文件对象 ,获取到指定的构造方法
    getDeclaredConstructor (参数);
c.暴力访问
     con.setAccessible(true);
d.通过构造方法,创建对象
     public T newInstance(Object... initargs)

5.通过反射,获取Class文件中的方法

  • 获取指定的方法

    public Method getMethod(String name, Class<?>… parameterTypes)

  • 获取指定的public方法

    public Method getDeclaredMethod(String name, Class<?>… parameterTypes)

  • 获取指定的任意方法,包含私有的

    获取所有的方法:
    public Method[] getMethods()

    获取本类与父类中所有public 修饰的方法:
    public Method[] getDeclaredMethods()获取本类中包含私有的所有的方法

// 数组表示:new Class[]{String.class, String.class}

6.通过反射,调用方法

步骤:
a.获取Class对象
b.构造方法,创建对象
c.取指定的public方法
d.行方法
public Object invoke(Object obj, Object... args)

7.私有方法的调用:

a,获取Class对象
b,获取构造方法,创建对象
c,获取指定的private方法
d,开启暴力访问
m5.setAccessible(true);
e,执行方法
public Object invoke(Object obj, Object... args)

8.通过反射,获取成员变量

  • 获取指定的成员变量

    public Field getField(String name)

  • 获取public修饰的成员变量

    public Field getDeclaredField(String name)

  • 获取任意的成员变量,包含私有

    public Field[] getFields()

  • 获取所有public修饰的成员变量

    public Field[] getDeclaredFields()

9.通过反射,获取成员 变量,并赋值使用

步骤:
a. 获取字节码文件对象
b. 获取构造方法,创建对象
c. 获取指定的成员变量
d. 对成员变量赋值获取值操作
public void set(Object obj, Object value) // 赋值
public Object get(Object obj) // 获取值

10.私有成员变量的使用

步骤:
a. 获取字节码文件对象
b. 获取构造方法,创建对象
c. 获取指定的成员变量
d. 开启暴力访问
e. 对成员变量赋值获取值操作
public void set(Object obj, Object value) //赋值
public Object get(Object obj) //获取值