0%

长沙小记

对于此次的长沙之行,其实我是期待了许久的,作为一个湖南人,我心中对长沙素来是有好感的,不止因为它的美食,也因它的风味。故而虽一路上比较颠簸,吐槽抱怨之余却也有几丝欣然的期待。

甫一下车,一行人便前往参观了深信服公司,对于我们所受到的招待,心中总有种受宠若惊的感觉,也体会到了他们公司对我们团队的尊重与重视。

参观完毕,而后便是一天半的自由活动了。对于长沙的美食小吃,我早已垂涎已久,所以晚上便火急火燎地溜出去觅食,第二天中午也在岳麓山脚下吃了一顿火锅。作为一个无辣不欢的湖南人,武汉待得久了,却也会开始觉得长沙的食物是辣的,从满是红油的火锅里夹出一块熟肉放入口中,不禁大呼过瘾,却也被辣得满头大汗,还被同行的伙伴轻嘲了我的窘态。长沙的辣,在我看来是一种很用心的辣,而不是单纯的辣椒与食物搅拌在一起的轻浮的辣,它能很快地敲击你的味蕾,让你马上便陷入其中,挣脱不得。

我也喜欢长沙的景,岳麓山脚的瀑布,有种长沙的小家碧玉之感。山腰的茶颜悦色,也尽是怡人的平静。而最让我深刻的,还是山顶上的红枫与山脚下的银杏了,红枫的枝叶小巧,不似华科里的法桐叶般硕大,它眉眼低垂,随寒风的方向来来去去,却又不脱离枝头,世故而有自己的底线。若说红枫让我感觉到了岳麓山的坚强,那路两旁的银杏,则是有几分冷冽寒风里的浪漫了,约一个阔别已久的朋友,在满是银杏叶的路上走走停停,抬头即是漫天的金黄色,风一吹,便又带下了几片,不觉凄凉,倒是有几分诗人般的浪漫。

回校的车上,肚子有些饿了,却又是开始想念起东小门外阿姨的烧烤了,虽是一身疲惫,却没了睡意,便约了咸鱼和矢量,踏着启明路上的法桐落叶,出东小门右拐,视眼所及,尽是熟悉的灯火璀璨。

2018个人小结

白驹过隙,蓦然回首,却又是一年。大学的时光虽然漫长却来去匆匆,回顾我的大学前两年,大部分时光都在浑浑噩噩中度过,或流失眼前,或离于脚底。所幸我能加入Dian这个团队,让我能够在茫然的混沌中,看到新的方向。

在过去的一年里,我在启亦电子组和深信服组中共做了两个项目,通过宵,赖过床,起过早,离过晚。在这些项目上的经历,都是我未来技术路上的积累。项目之外,也结识了一群有趣的人,而他们,也让我开始渐渐变得有趣了起来。

启亦的项目是我进入团队之后最先开始接触的项目,从去年被优神带着做Android,到今年扫频仪的项目里,我开始渐渐一个人负责Android端的开发,虽然一开始代码写得还是很矬,但随着项目的发展,我在这方面的能力也得到了比较大的提升。那段时间,我们项目组也会经常跑去启亦公司去做联调,有时是与矢量,咸鱼去,有时也会有晓纤。那段时间,我总有种感觉,觉得只要我们几个人在一起,什么问题都可以慢慢解决。在启亦公司调Bug的时光,也许是我在团队的这些日子里,最开心的一段时间了吧。

后来我离开了启亦,于是整个暑假便掉入了深信服组的坑,失足少年,整个暑假都没有回家。可能是我性子的原因吧,当我在做一件事的时候,便喜欢整个身心都投进去,偶尔会觉得累,但又停不下自己的手。在做深信服项目的时候,常常是待在实验室最久的人之一,有时也会与邹君和小红在实验室通宵。不过虽然累,却让我的成长比较迅速,同时,让我觉得更为珍贵的是,我又认识了一些新的小朋友,如时常会让我不得不给她收拾桌子的邹琪珺,勤劳又有点迷的小红,骚气的曾老师,还有之前就认识的一些团队里的朋友,也更为熟悉了起来。

后来就开始找起了工作,由于在深信服组待了一个暑假,所以对找工作的一些东西没有做好准备,一开始找工作的时候,屡屡碰壁,同时也错过许多比较好的公司的校招机会。所幸有团队做的项目上的经历,最后还是找到了一份还不错的工作。

一年已过,愿我能不负所望,也愿团队的未来越来越好。

流水梦

苍耳

街边拐角的奶茶铺子
传来风的香味
它藏着,不知来处的寂寞
扫下满地梧桐如雨

街头街尾的春秋
年复一年,路过着一个校服男孩
穿过多少光影的昨日
风与树叶打着卷儿
带走了夏天的味道

遗落路上的诗与文字
我尝试将它拾起
伴着岁月的流水
嚼碎并吞咽
闭上眼
却成梦里,花开花落

管理学

总论

第一章 管理活动与管理理论

管理活动

  • 管理的定义:管理是指组织为了达到个人无法实现的目标,通过各项职能活动,合理分配、协调相关资源的过程。
  • 管理的职能:决策与计划,组织,领导,控制,创新。
  • 管理者的角色:人际角色,信息角色,决策角色。
  • 管理者的技能:概念技能,技术技能,人际技能。

管理理论的形成发展

  1. 古典管理理论
    • 科学管理理论
    • 组织管理理论
  2. 行为管理理论
  3. 数量管理理论
  4. 系统管理理论
  5. 权变管理理论
  6. 质量管理理论
  7. 20世纪90年代管理理论新发展
    • 学习型组织
    • 精益思想
    • 业务流程再造
    • 核心能力理论

第二章 管理道德和企业社会责任

管理和伦理道德

伦理道德是现代社会的核心价值构件,具有特殊的管理意义和文明意义。在中国文化中,“伦”指人所处于其中的共同体及人在这个共同体中的地位。“天伦”指家庭血缘关系的共同体,“人伦”指社会关系的共同体。

伦理道德的管理学意义:

  1. 经济与经营活动的意义,尤其是对终极意义的追求
  2. 企业组织
  3. 人文力与企业精神
  4. 企业及其产品的价值观

几种相关的道德观

  • 功利主义
  • 权力至上
  • 公平公正
  • 社会契约
  • 推己及人

道德管理的特征和影响管理道德的因素

道德管理的特征:

  • 把遵守道德规范视作组织获取利益的一种手段,更把其视作组织的一项责任;
  • 不仅从组织自身角度更应从社会角度看问题;
  • 尊重所有者以外的利益相关者的利益,善于处理组织与利益相关者的关系;
  • 不仅把人看做手段,更把人看做目的,组织行为的目的是为了人;
  • 超越法律的要求,能让组织取得卓越的成就;
  • 具有自律的特征;
  • 以组织的价值观为行为导向;

影响管理道德的因素:

  • 道德发展阶段
  • 个人特征
  • 组织结构
  • 组织文化
  • 问题强度

改善企业道德行为的途径

  1. 挑选高道德素质的员工
  2. 建立道德守则和决策规则
  3. 管理者在道德方面领导员工
  4. 设定工作目标
  5. 对员工进行道德教育
  6. 对绩效进行全面评价
  7. 进行独立的社会审计
  8. 提供正式的保护机制

企业的社会责任

第三章 全球化与管理

全球化内涵

全球化与管理者

  • 全球化管理的环境因素
    • 全球化的一般环境
      • 政治与法律环境
      • 经济与技术环境
      • 文化环境
    • 全球化的任务环节
      • 供应商
      • 销售商
      • 顾客
      • 竞争对手
      • 劳动力市场及工会
  • 全球化管理者的关键能力
    • 国际商务知识
    • 文化适应能力
    • 视角转换能力
    • 创新能力

全球化与管理职能

  • 全球化经营的进入方式决策
    • 出口
    • 非股权安排
    • 国际直接投资
  • 全球化经营的组织模式
    • 全球化的压力
    • 当地化的压力
    • 全球化组织模式的选择
  • 全球化经营的领导风格
  • 全球化经营的管理控制
    • 管理控制系统的指定逻辑
    • 管理控制系统的设计

第四章 信息与信息化管理

信息及其特征

  • 信息的定义:信息由数据生成,是数据经过加工处理后得到的,如报表、账册和图纸等;信息被用来反应映客观事物的规律,从而为管理工作提供依据;
  • 数据:是记录客观事物的性质、形态和数量特征的抽象符号;
  • 对信息的评估:信息评估的关键在于对信息的收益和获取成本进行预先评估,即成本—收益分析;
  • 有用信息的特征:
    • 高质量:要求信息是精确的、清楚、排列有序的,信息的传递媒介对质量有重要影响
    • 及时:管理者有需要就能获得信息;要反映当前情况;信息要频繁地提供给管理者;
    • 完全:信息的范围必须足够广泛、简介、详细;

信息管理工作

  • 信息的采集:指管理者根据一定的目的,通过各种不同的方式搜寻并占有各类信息的过程
    • 明确采集的目的
    • 界定采集的范围(对象、时间、空间)
    • 选择信息源(文献、口头、电子、事物)
  • 信息的加工:指对采集来的通常显得杂乱无章的大量信息进行鉴别和筛选,使信息条理化、规范化、准确化的过程
    • 鉴别:指确认信息可靠性的活动
      • 鉴别标准:信息本身是否真实
      • 鉴别方法:查证法、比较法、佐证法、逻辑法:
    • 筛选:指在鉴别的基础上,对采集来的信息进行取舍的活动
      • 筛选分为:依次分为信息的真实性、适用性、精约性、先进性筛选
    • 排序:是指对筛选后的信息进行归类整理,按照管理者所偏好的某一特征对信息进行等级、层次的划分活动
    • 初步激活:指对排序后的信息进行开发、分析和转换.实现信息的活化以便使用的活动
    • 编写:是信息加工的产出环节,指对加工后的信息进行编写,便于人们认识的活动
  • 信息的存储:指对加工后的信息进行记录、存放、保管以便使用的过程
    • 信息的存储由归档、登录、编目、编码、排架等环节构成,在这些环节中要注意准确性、安全性、费用、方便性的问题
  • 信息的传播:指信息在不同主体之间的传递
  • 信息的利用:指有意识地运用存储的信息去解决管理中的具体问题的过程
  • 信息反馈:指对信息利用的实际效果与预期效果进行比较,找出发生偏差的原因,采取相应的控制措施以保证信息的利用符合预期效果

信息化管理

信息系统是企业信息化管理的基础.

信息系统的要素:

  • 输入
  • 处理
  • 输出
  • 反馈
  • 控制

企业信息化管理的发展:

  • 20世纪60年代开环的物料需求计划(MRP)
  • 2O世纪70年代闭环的物料需求计划(MRP)
  • 20世纪80年代制造资源计划(MRP2)
  • 20世纪90年代企业资源计划(ERP)

概述

Class

class文件是Java编译后的目标文件,不像J2se,java编译成class就可以直接运行,android平台上class文件不能直接在android上运行。 由于Google使用了自己的Dalvik(后来又变成了ART)来运行应用, 所以这里的class也肯定不能在AndroidDalvik的java环境中运行, android的class文件实际上只是编译过程中的中间目标文件,需要链接成dex文件后才能在dalvik上运行。

Dex

dex文件是Android平台上的可执行文件。在编译Java代码之后,通过Android平台上的工具可以将Java字节码转换成Dex字节码。

Apk

apk文件是Android上的安装文件。一个Android安装包包含了与某个Android应用程序相关的所有文件。apk文件将AndroidManifest.xml文件、应用程序代码(.dex文件)、资源文件和其他文件打成一个压缩包。

Hook

Hook 又叫“钩子”,它可以在事件传送的过程中截获并监控事件的传输,将自身的代码与系统方法进行融入。这样当这些方法被调用时,也就可以执行我们自己的代码,这也是面向切面编程的思想(AOP)。

Hook 分类

  1. 根据Android开发模式,Native模式(C/C++)和Java模式(Java)区分,在Android平台上
    • Java层级的Hook;
    • Native层级的Hook;
  2. 根据Hook 对象与 Hook 后处理事件方式不同,Hook还分为:
    • 消息Hook;
    • API Hook;
  3. 针对Hook的不同进程上来说,还可以分为:
    • 全局Hook;
    • 单个进程Hook;

Hook选择的关键点

  • Hook 的选择点:尽量静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
  • Hook 过程:
    1. 寻找 Hook 点,原则是尽量静态变量或者单例对象,尽量 Hook public 的对象和方法。
    2. 选择合适的代理方式,如果是接口可以用动态代理。
    3. 偷梁换柱——用代理对象替换原始对象。
  • Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。

工具

ApkTool

Apktool可以用来查看apk中的资源文件及布局文件,下载地址:https://bitbucket.org/iBotPeaches/apktool/downloads/ 。下载了apktool.jar后,可使用如下apktool脚本反编译apk:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/bin/bash
#
# Copyright (C) 2007 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This script is a wrapper for smali.jar, so you can simply call "smali",
# instead of java -jar smali.jar. It is heavily based on the "dx" script
# from the Android SDK

# Set up prog to be the path of this script, including following symlinks,
# and set up progdir to be the fully-qualified pathname of its directory.
prog="$0"
while [ -h "${prog}" ]; do
newProg=`/bin/ls -ld "${prog}"`

newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
if expr "x${newProg}" : 'x/' >/dev/null; then
prog="${newProg}"
else
progdir=`dirname "${prog}"`
prog="${progdir}/${newProg}"
fi
done
oldwd=`pwd`
progdir=`dirname "${prog}"`
cd "${progdir}"
progdir=`pwd`
prog="${progdir}"/`basename "${prog}"`
cd "${oldwd}"

jarfile=apktool.jar
libdir="$progdir"
if [ ! -r "$libdir/$jarfile" ]
then
echo `basename "$prog"`": can't find $jarfile"
exit 1
fi

javaOpts=""

# If you want DX to have more memory when executing, uncomment the following
# line and adjust the value accordingly. Use "java -X" for a list of options
# you can pass here.
#
javaOpts="-Xmx512M -Dfile.encoding=utf-8"

# Alternatively, this will extract any parameter "-Jxxx" from the command line
# and pass them to Java (instead of to dx). This makes it possible for you to
# add a command-line parameter such as "-JXmx256M" in your ant scripts, for
# example.
while expr "x$1" : 'x-J' >/dev/null; do
opt=`expr "$1" : '-J\(.*\)'`
javaOpts="${javaOpts} -${opt}"
shift
done

if [ "$OSTYPE" = "cygwin" ] ; then
jarpath=`cygpath -w "$libdir/$jarfile"`
else
jarpath="$libdir/$jarfile"
fi

# add current location to path for aapt
PATH=$PATH:`pwd`;
export PATH;
exec java $javaOpts -jar "$jarpath" "$@"

使用方法:

1
2
3
4
5
6
# decode
$ ./apktool d sweeper.apk

# build,builds bar folder into new_bar.apk
# 此时生成的apk是未签名的
$ apktool b bar -o new_bar.apk

dex2jar和jar2dex

这是classes.dex转为jar包的工具,下载地址:https://github.com/pxb1988/dex2jar 。使用方法:

1
$ ./d2j-dex2jar.sh -f xxx.apk

JD-GUI

可使用JD-GUI工具查看apk的源代码,下载地址:http://java-decompiler.github.io/ 。使用方法:直接打开dex2jar生成的jar文件即可查看。

baksmali

可使用baksmali工具把dex转换成smali,下载地址:https://bitbucket.org/JesusFreke/smali/downloads/?tab=downloads 。使用方法:

1
$ java -jar baksmali.jar -o [输出文件夹] dex文件

smali

可使用smali工具将smali转换成dex,下载地址:https://bitbucket.org/JesusFreke/smali/downloads/?tab=downloads 。使用方法:

1
$ java -jar smali.jar -o 目标dex文件 [smali文件夹]

signer

关于签名的内容在Android签名笔记中有了新的更新。

工具介绍

  • jarsigner是JDK提供的针对jar包签名的通用工具,位于JDK/bin/;
  • apksigner是Google官方提供的针对Android apk签名及验证的专用工具,位于Android SDK/build-tools/SDK版本/。

不管是apk包,还是jar包,本质都是zip格式的压缩包,所以它们的签名过程都差不多(仅限V1签名),以上两个工具都可以对Android apk包进行签名。

V1和V2签名

从Android 7.0开始, 谷歌增加新签名方案 V2 Scheme (APK Signature);但Android 7.0以下版本, 只能用旧签名方案 V1 scheme (JAR signing)。

  • V1签名:来自JDK(jarsigner), 对zip压缩包的每个文件进行验证, 签名后还能对压缩包修改(移动/重新压缩文件),对V1签名的apk/jar解压,在META-INF存放签名文件(MANIFEST.MF, CERT.SF, CERT.RSA), 其中MANIFEST.MF文件保存所有文件的SHA1指纹(除了META-INF文件), 由此可知: V1签名是对压缩包中单个文件签名验证。
  • V2签名:来自Google(apksigner), 对zip压缩包的整个文件验证, 签名后不能修改压缩包(包括zipalign),对V2签名的apk解压,没有发现签名文件,重新压缩后V2签名就失效, 由此可知: V2签名是对整个APK签名验证。因此,V2签名更安全(不能修改压缩包),且签名验证时间更短(不需要解压验证),因而安装速度加快

注意: apksigner工具默认同时使用V1和V2签名,以兼容Android 7.0以下版本

zipalign和V2签名

位于Android SDK/build-tools/SDK版本/下,zipalign是对zip包对齐的工具,使APK包内未压缩的数据有序排列对齐,从而减少APP运行时内存消耗。

1
2
zipalign -v 4 in.apk out.apk   # 4字节对齐优化
zipalign -c -v 4 in.apk # 检查APK是否对齐

zipalign可以在V1签名后执行,但zipalign不能在V2签名后执行,只能在V2签名之前执行。

签名步骤

1. 生成密钥对

Eclipse或Android Studio在Debug时,对App签名都会使用一个默认的密钥库:~/.android/debug.keystore

密钥库名:   debug.keystore
密钥别名:   androiddebugkey
密钥库密码: android
  1. 生成密钥对

    1
    2
    3
    4
    5
    6
    7
    8
    9
    keytool -genkeypair -keystore 密钥库名 -alias 密钥别名 -validity 天数 -keyalg RSA

    参数:
    -genkeypair 生成一条密钥对(由私钥和公钥组成)
    -keystore 密钥库名字以及存储位置(默认当前目录)
    -alias 密钥对的别名(密钥库可以存在多个密钥对,用于区分不同密钥对)
    -validity 密钥对的有效期(单位: 天)
    -keyalg 生成密钥对的算法(常用RSA/DSA,DSA只用于签名,默认采用DSA)
    -delete 删除一条密钥

    提示: 可重复使用此条命令,在同一密钥库中创建多条密钥对,例如,在debug.keystore中新增一对密钥,别名是release:

    1
    keytool -genkeypair -keystore debug.keystore -alias release -validity 30000
  2. 查看密钥库

    1
    2
    3
    4
    5
    keytool -list -v -keystore 密钥库名

    参数:
    -list 查看密钥列表
    -v 查看密钥详情

2. 签名

jarsigner
1
jarsigner -keystore 密钥库名 xxx.apk 密钥别名

从JDK7开始, jarsigner默认算法是SHA256, 但Android 4.2以下不支持该算法,所以需要修改算法, 添加参数-digestalg SHA1 -sigalg SHA1withRSA

1
2
3
4
5
jarsigner -keystore 密钥库名 -digestalg SHA1 -sigalg SHA1withRSA xxx.apk 密钥别名

参数:
-digestalg 摘要算法
-sigalg 签名算法
apksigner
1
2
3
4
5
6
7
8
9
10
11
12
apksigner sign --ks 密钥库名 --ks-key-alias 密钥别名 xxx.apk

# 若密钥库中有多个密钥对,则必须指定密钥别名
apksigner sign --ks 密钥库名 --ks-key-alias 密钥别名 xxx.apk

# 禁用V2签名
apksigner sign --v2-signing-enabled false --ks 密钥库名 xxx.apk

参数:
--ks-key-alias 密钥别名,若密钥库有一个密钥对,则可省略,反之必选
--v1-signing-enabled 是否开启V1签名,默认开启
--v2-signing-enabled 是否开启V2签名,默认开启

3. 签名验证

keytool,只支持V1签名校验
1
2
3
4
5
keytool -printcert -jarfile MyApp.apk (显示签名证书信息)

参数:
-printcert 打印证书内容
-jarfile <filename> 已签名的jar文件 或apk文件
apksigner,支持V1和V2签名校验
1
2
3
4
5
apksigner verify -v --print-certs xxx.apk

参数:
-v, --verbose 显示详情(显示是否使用V1和V2签名)
--print-certs 显示签名证书信息

命令行打包签名apk

gradlew+keytool

  1. 通过keytool生成签名文件

  2. 配置build.gradle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    android {
    signingConfigs {
    release {
    storeFile file("path/release.keystore")
    storePassword "123456"
    keyAlias "release.keystore"
    keyPassword "123456"
    }
    debug {
    storeFile file("path/test.keystore")
    storePassword "123456"
    keyAlias "test.keystore"
    keyPassword "123456"
    }
    }
    buildTypes {
    release {
    minifyEnabled enableProguardInReleaseBuilds
    proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
    signingConfig signingConfigs.release

    android.applicationVariants.all { variant ->
    variant.outputs.all {
    if (variant.buildType.name.equals('release')) {
    outputFileName = "SecureMail${defaultConfig.versionName}-${releaseTime()}.apk"
    variant.getPackageApplication().outputDirectory = new File(
    project.rootDir.absolutePath + "/app/release")
    }
    }
    }
    }
    }
    }

    static def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
    }
  3. 执行打包命令

    1
    ./gradlew assemblerelease

aapt+java+keytool+apksigner

个人觉得实用性不大,故没有做笔记。

源码混淆

Android中代码混淆可以分为两部分:

  1. Java代码的优化与混淆,依靠 proguard混淆器来实现;
  2. 资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般都会放一起用)。

混淆配置

1
2
3
4
5
6
7
8
9
10
11
android{
buildTypes {
release {
buildConfigField "boolean", "LOG_DEBUG", "false" //不显示log
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
}

开启混淆会使编译时间变长,所以debug模式下不开启。在打包时应修改配置如下:

  1. 将buildConfigField设置为false,不显示log日志;
  2. 将minifyEnabled值设为true,打开混淆;
  3. 将shrinkResources值设为true,打开资源压缩;
  4. signingConfig signingConfigs.config配置签名文件文件
  5. proguardFiles定义了混淆规则由两部分构成:位于SDK的tools/proguard/文件夹中的 proguard-android.txt(SDK提供的默认混淆文件)的内容以及默认放置于模块根目录的 proguard-rules.pro(自定义混淆规则)。

自定义混淆规则

dex反编译修改Java层代码

反编译后得到的dex文件以及转为jar包后都无法进行修改,只能把dex文件转化为smali文件进行修改,然后再编译打包为dex文件,替换掉原有apk中的dex文件,然后对apk进行签名,这样就完成了对apk源码的修改,即需要学习smali相关的语法。具体步骤如下:

  1. 使用apktool工具反编译apk文件,可以得到相关的smali文件,通常来说一个class对应一个smali(或者直接解压apk文件,然后使用baksmali工具将里面的dex转换为smali);
  2. 使用dex2jar工具把dex转为jar文件,然后可以通过JD-GUI进行查看;
  3. 修改对应的smali文件;
  4. 使用smali.jar工具把smali文件转为dex文件;
  5. 把新生成的classes.dex文件替换到原来的apk文件里
  6. 使用签名工具对apk进行签名。

so反编译修改Native层代码

so文件是Android NDK动态链接库,是二进制文件,作用相当于windows下的.dll文件,反编译Native代码需要学习ARM汇编。步骤如下:

  1. 使用apktool工具反编译apk文件,可以得到lib目录下各个架构的so文件;
  2. 使用编辑器打开so文件,并修改;
  3. 重新打包并签名apk文件。

Java运行shell命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void runShell(String command) {
Process process = null;
BufferedReader bufferedReader = null;
StringBuilder mShellCommandSB = new StringBuilder();
Log.d("LLL", "runShell: " + command);
mShellCommandSB.delete(0, mShellCommandSB.length());
String[] cmd = new String[]{"/system/bin/sh", "-c", command};
try {
process = Runtime.getRuntime().exec(cmd);
bufferedReader = new BufferedReader(new InputStreamReader(
process.getInputStream()));
String line;

while ((line = bufferedReader.readLine()) != null) {
mShellCommandSB.append(line);
}
Log.d("LLL", "runShell result: " + mShellCommandSB.toString());
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
// TODO: handle exception
}
}

if (process != null) {
process.destroy();
}
}
}

封装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class ShellUtil {
private static final String COMMAND_LINE_END = "\n";
private static final String COMMAND_EXIT = "exit\n";

public static ExecResult execute(String command) {
return execute(new String[]{command});
}

public static ExecResult execute(String[] commands) {
if (commands == null || commands.length == 0) {
return new ExecResult(false, "empty command");
}
int result = -1;
Process process = null;
DataOutputStream dataOutputStream = null;
BufferedReader sucResult = null, errResult = null;
StringBuilder sucMsg = null, errMsg = null;

try {
// 获取shell级别的process
process = Runtime.getRuntime().exec("sh");
dataOutputStream = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) continue;
System.out.println("execute command: " + command);
// 执行指令
dataOutputStream.write(command.getBytes());
dataOutputStream.writeBytes(COMMAND_LINE_END);
// 刷新
dataOutputStream.flush();
}
dataOutputStream.writeBytes(COMMAND_EXIT);
dataOutputStream.flush();
result = process.waitFor();
sucMsg = new StringBuilder();
errMsg = new StringBuilder();
sucResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = sucResult.readLine()) != null) {
sucMsg.append(s);
}
while ((s = errResult.readLine()) != null) {
errMsg.append(s);
}

} catch (IOException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
// 关闭资源,防止内存泄漏
assert dataOutputStream != null;
dataOutputStream.close();
assert sucResult != null;
sucResult.close();
assert errResult != null;
errResult.close();
} catch (IOException e) {
e.printStackTrace();
}
process.destroy();
}
ExecResult execResult;
if (result == 0) {
execResult = new ExecResult(true, sucMsg.toString());
} else {
execResult = new ExecResult(false, errMsg.toString());
}
// 返回执行结果
return execResult;
}

public static class ExecResult {
private boolean success;
private String message;

public ExecResult(boolean success, String message) {
this.success = success;
this.message = message;
}

public boolean getSuccess() {
return this.success;
}

public String getMessage() {
return this.message;
}
}
}

Xposed

Xposed可以用来hook Android应用的Java层,hook代码编写比较方便,不过需要在手机上安装Xposed框架,且每次修改需要重启手机才能生效。Xposed使用中需要注意以下问题:

  1. 这个框架的核心点是系统进程注入技术,那么如果要注入系统进程,就必须要有一个root的设备。
  2. 不是所有的设备所有的系统都支持这个框架的使用。
  3. Xposed框架针对不同系统也发布了多个版本,所以得针对于自己的设备系统安装正确的Xposed版本。

CydiaSubstrate

这个框架可以hook Java和Native层的代码,在手机上需要安装这个框架,也可能会失败。

Frida

Legend

对比

  1. XPOSED

    优点:

    1. 代码编写方便,开发速度较快。

    2. 有许多现成的模块可以用,而且很多模块也是开源的,方便学习研究。

      缺点:

    3. 每次编写代码需要重启手机生效。

    4. 不支持native的HOOK。

    5. 独立性较差,需要依赖XPOSED installer,不易单独分发。

  2. substrate

    优点:

    1. 比较擅长在native层的HOOK。

    2. 独立性较好,实现的功能可以封装在单独APP里分发给用户使用,因此也是较大型外挂辅助工具的首选。

      缺点:

    3. 每次编写代码需要重启手机生效。

    4. 开发效率较低,成本较高。

  3. frida

    优点:

    1. 无须重启手机和目标APP,这个可以节省很多时间,如果APP测试的点需要很复杂地搭建好环境,一旦重新启动就意味着很麻烦地再重新搭建环境,例如账号登录,进入特定关卡等。

    2. JS脚本编写,灵活方便,再也不用担心多参数个数和类型问题了。

    3. 可以直接使用或修改对象的成员变量,非常方便。

    4. 配合PC终端命令行使用,脚本编写出错也不会导致APP崩溃,只需修改后重新来过即可,有时会有问题,这个时候需要重启下APP或手机即可。

      缺点:

    5. JS脚本套在python脚本里面,编写JS脚本时候不是很方便,容易出错,好在即使出错也不会导致APP崩溃掉,修改后重新来过即可。

    6. 该工具配合PC终端使用,更适合专业者,不利于分发给用户使用。

  4. Legend

    优点:

    1. 不需要root权限。

      缺点:

    2. 只能hook自身。

基本概念

Node 与 Cluster

Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。

Index

Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。

Document

Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。Document 使用 JSON 格式表示,下面是一个例子:

1
2
3
4
5
{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}

同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。

Type

Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。不同的 Type 应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如products和logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

根据规划,Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。

分片和复制(Shards & Replicas)

我们在一个索引里存储的数据,潜在的情况下可能会超过单节点硬件的存储限制。因此Elasticsearch提供了分片的能力,它可以将索引细分成多个部分。当创建一个索引的时候,可以简单的定义想要的分片的数量。每个分片本身是一个全功能的完全独立的“索引”,它可以部署在集群中的任何节点上。

分片对于以下两个主要原因很重要:

  • 它允许你水平切分你的内容卷
  • 它允许你通过分片来分布和并行化执行操作来应对日益增长的执行量
  • 一个分片是如何被分配以及文档又是如何被聚集起来以应对搜索请求的,它的实现技术由Elasticsearch完全管理,并且对用户是透明的。

在一个网络环境下或者是云环境下,故障可能会随时发生,有一个故障恢复机制是非常有用并且是高度推荐的,以防一个分片或节点不明原因下线,或者因为一些原因去除没有了。为了达到这个目的,Elasticsearch允许你制作分片的一个或多个拷贝放入一个叫做复制分片或短暂复制品中。

复制对于以下两个主要原因很重要:

  • 高可用。它提供了高可用来以防分片或节点宕机。为此,一个非常重要的注意点是绝对不要将一个分片的拷贝放在跟这个分片相同的机器上。
  • 高并发。它允许你的分片可以提供超出自身吞吐量的搜索服务,搜索行为可以在分片所有的拷贝中并行执行。

总结一下,每个索引可以被切分成多个分片,一个索引可以被复制零次(就是没有复制)或多次。一旦被复制,每个索引将会有一些主分片(就是那些最原始不是被复制出来的分片),还有一些复制分片(就是那些通过复制主分片得到的分片)。主分片和复制分片的数量可以在索引被创建时指定。索引被创建后,你可以随时动态修改复制分片的数量,但是不能修改主分片的数量。

默认情况下,在Elasticsearch中的每个索引被分配5个主分片和一份拷贝,这意味着假设你的集群中至少有两个节点,你的索引将会有5个主分片和5个复制分片(每个主分片对应一个复制分片,5个复制分片组成一个完整拷贝),总共每个索引有10个分片。

每个Elasticsearch分片是一个Lucene索引。在一个Lucene索引中有一个文档数量的最大值。截至LUCENE-5843,这个限制是2,147,483,519 (= Integer.MAX_VALUE - 128)个文档。可以使用_cat/shards API监控分片大小。

基本用法

启动

./elasticsearch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl localhost:9200
{
"name" : "UJ35DEz",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "9Re_329IRJK3Oyvg9prFug",
"version" : {
"number" : "6.4.0",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "595516e",
"build_date" : "2018-08-17T23:18:47.308994Z",
"build_snapshot" : false,
"lucene_version" : "7.4.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}

索引操作

  • API基本格式:http://:/<索引>/<类型>/<文档id>
  • 常用http动词 GET,POST,PUT,DELETE。
  • pretty参数时输出格式化

例:curl -X PUT ‘localhost:9200/weather?pretty’
例:curl -X DELETE ‘localhost:9200/weather’
例获取所有索引:curl -X GET ‘http://localhost:9200/_cat/indices?v'

数据操作

插入记录

指定文档id:

1
2
3
4
5
6
curl -H "Content-Type: application/json" -X PUT 'localhost:9200/accounts/person/1' -d '
{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}'

Kibana中:

1
2
3
4
5
6
PUT /accounts/person/1
{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}

系统生成id:

1
2
3
4
5
6
curl -X POST 'localhost:9200/accounts/person' -d '
{
"user": "李四",
"title": "工程师",
"desc": "系统管理"
}'

Kibana中:

1
2
3
4
5
6
POST /accounts/person
{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}

查看记录

$ curl get ‘localhost:9200/accounts/person/1?pretty=true’

上面代码请求查看/accounts/person/1这条记录,URL 的参数pretty=true表示以易读的格式返回。

返回的数据中,found字段表示查询成功,_source字段返回原始记录。

1
2
3
4
5
6
7
8
9
10
11
12
{
"_index" : "accounts",
"_type" : "person",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理"
}
}

如果 Id 不正确,就查不到数据,found字段就是false。

删除记录

$ curl -X DELETE ‘localhost:9200/accounts/person/1’

更新记录

实例:

1
2
3
4
5
6
$ curl -X PUT 'localhost:9200/accounts/person/1' -d '
{
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理,软件开发"
}'

返回:

1
2
3
4
5
6
7
8
9
{
"_index":"accounts",
"_type":"person",
"_id":"1",
"_version":2,
"result":"updated",
"_shards":{"total":2,"successful":1,"failed":0},
"created":false
}

上面代码中,将原始数据从”数据库管理”改成”数据库管理,软件开发”。 返回结果里面,有几个字段发生了变化。可以看到,记录的 Id 没变,但是版本(version)从1变成2,操作类型(result)从created变成updated,created字段变成false,因为这次不是新建记录。

查询操作

返回所有记录

使用 GET 方法,直接请求/Index/Type/_search,就会返回所有记录。

例: curl ‘localhost:9200/accounts/person/_search’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"took":2,
"timed_out":false,
"_shards":{"total":5,"successful":5,"failed":0},
"hits":{
"total":2,
"max_score":1.0,
"hits":[
{
"_index":"accounts",
"_type":"person",
"_id":"AV3qGfrC6jMbsbXb6k1p",
"_score":1.0,
"_source": {
"user": "李四",
"title": "工程师",
"desc": "系统管理"
}
},
{
"_index":"accounts",
"_type":"person",
"_id":"1",
"_score":1.0,
"_source": {
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理,软件开发"
}
}
]
}
}

全文搜索

查询desc字段里面包含”软件”这个词的记录,Elastic 默认一次返回10条结果,可以通过size字段改变这个设置,还可以通过from字段指定位移((默认是从位置0开始)。

1
2
3
4
5
6
$ curl 'localhost:9200/accounts/person/_search'  -d '
{
"query" : { "match" : { "desc" : "管理" }},
"from": 1,
"size": 1
}'

逻辑运算

如果有多个搜索关键字, Elastic 认为它们是or关系。

1
2
3
4
$ curl 'localhost:9200/accounts/person/_search'  -d '
{
"query" : { "match" : { "desc" : "软件 系统" }}
}'

上面代码搜索的是软件 or 系统。如果要执行多个关键词的and搜索,必须使用布尔查询。

1
2
3
4
5
6
7
8
9
10
11
$ curl 'localhost:9200/accounts/person/_search'  -d '
{
"query": {
"bool": {
"must": [
{ "match": { "desc": "软件" } },
{ "match": { "desc": "系统" } }
]
}
}
}'

启动方式

  1. IDE启动
  2. mvn spring-boot:run
  3. mvn install 编译微jar包后,运行java -jar 项目的jar名

属性配置

Spring Boot 不单单从 application.properties 获取配置,所以我们可以在程序中多种设置配置属性。按照以下列表的优先级排列:

  1. 命令行参数
  2. java:comp/env 里的 JNDI 属性
  3. JVM 系统属性
  4. 操作系统环境变量
  5. RandomValuePropertySource 属性类生成的 random.* 属性
  6. 应用以外的 application.properties(或 yml)文件
  7. 打包在应用内的 application.properties(或 yml)文件
  8. 在应用 @Configuration 配置类中,用 @PropertySource 注解声明的属性文件
  9. SpringApplication.setDefaultProperties 声明的默认属性在.properties或.yml配置文件中配置。

yml配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 普通变量:使用时定义了变量后使用@Value(“${name}”)注解即可。
name: 刘备
age: 55

# 与JavaBean关联
# 在Person.class中,使用注解:
# @Component
# @ConfigurationProperties(prefix = "person")
# 使用时通过@Autowired注解:
# @Autowired
# private Person person;
# 通过不同的启动方式使用不同的配置
person:
name: 刘备
age: 55

# 在application-dev.yml和application-prod.yml配置文件中保存不同的变量值,在application.yml中通过以下方式使用:
spring:
profiles:
active: dev

资源映射

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/image/**") //请求的url
.addResourceLocations("file:///" + Utils.getResPath()); //文件本地目录
super.addResourceHandlers(registry);
}
}

Controller

  • @Controller:处理http请求
  • @RestController:@ResponseBody+@Controller
  • @RequestMapping:配置url映射
  • @PathVariable:获取url中参数
  • @RequestParam:获取请求参数的值
  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

注意:

  1. @RequestMapping(“/hello/{id})
    …(@PathVariable(“id”) Integer id)
    ——》这种方式的url直接在反斜杠后加参数即可访问
  2. 而如果使用传统URL(…/..?id=..),则需要使用@RequestParam注解。
  3. GetMapping= RequestMapping+GET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回Json
@RestController
public class TestController {

@Value("${name}")
private String name;

@Autowired
private Person person;

@GetMapping("/hello/{id}")
public Person hello(@PathVariable("id") String hello) {
return person;
}
}

表单验证

对某个字段添加限制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Min(value = 18, message = "未成年")
private Integer age;

@PostMapping("/user")
public User addUser(@Valid User u, BindingResult result){
if (result.hasErrors()){
System.out.println(result.getFieldError().getDefaultMessage());
return null;
}
User user = new User();
user.setAge(u.getAge());
user.setName(u.getName());
return userRepository.save(user);
}

DI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DI() {
//通过构造器注入
private DependencyA a;
@Autowired
public DI(DependencyA a) {
this.a = a;
}

//通过setter方法注入
private DependencyB b;
@Autowired
public void setDependencyB (DependencyB b){
this.b = b;
}

//通过field反射注入
@Autowired
private DependencyC c;
}
  • 构造器注入:当有十几个甚至更多对象需要注入时,你的构造函数的参数个数可能会长到无法想像。
  • field反射注入:如果不使用Spring框架,这个属性只能通过反射注入,根本不符合JavaBean规范。还有,当不是用Spring创建的对象时,还可能引起NullPointerException。并且,不能用final修饰这个属性。
  • setter方法注入:不能将属性设置为final。
  • 如果注入的属性是必选的属性,则通过构造器注入。
  • 如果注入的属性是可选的属性,则通过setter方法注入。
  • 至于field注入,不建议使用。

AOP

是一种编程思想,不是语言特有的。

  • 使用@Aspect注解将一个java类定义为切面类
  • 使用@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
  • 根据需要在切入点不同位置的切入内容
    • 使用@Before在切入点开始处切入内容
    • 使用@After在切入点结尾处切入内容
    • 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
    • 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
    • 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@org.aspectj.lang.annotation.Aspect
@Component
public class Aspect {

@Pointcut("execution(public * com.hearing.springdemo.controller.*.*(..))")
public void log(){
}

@Before("log()")
public void doBefore(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("doBefore...");
}

@AfterReturning(returning = "object", pointcut = "log()")
public void doAfter(Object object){
System.out.println("doAfter...");
}
}

Listener

执行顺序:监听器、过滤器、拦截器。

分类

  • 监听域对象自身的创建和销毁的事件监听器
    ServletContextListener,HttpSessionListener,ServletRequestListener。
  • 监听域对象中属性的增加和删除的事件监听器
    ServletContextAttributeListener,HttpSessionAttributeListener,ServletRequestAttributeListener。
  • 监听绑定到HttpSession域中的某个对象的状态的事件监听器
    HttpSessionBindingListener:对象的绑定与解绑(将要绑定对象实现该接口)
    HttpSessionActivationListener:对象的钝化与活化(将要绑定对象实现该接口)
  • Session钝化机制:将服务器中不经常使用的Session对象暂时序列化到文件系统或数据库系统中,当被使用时反序列化到内存。

使用

@WebListener方式

主类添加@ServletComponentScan注解

1
2
3
4
5
6
7
8
9
10
11
12
13
@WebListener
public class MyServletContextListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println(sce.getServletContext().getServletContextName()+" init");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println(sce.getServletContext().getServletContextName()+" destroy");
}
}

ServletListenerRegistrationBean代码注册

1
2
3
4
5
6
7
8
9
10
@Configuration
public class ListenerConfigure {

@Bean
public ServletListenerRegistrationBean<MyHttpSessionListener> serssionListenerBean(){
ServletListenerRegistrationBean<MyHttpSessionListener>
sessionListener = new ServletListenerRegistrationBean<MyHttpSessionListener>(new MyHttpSessionListener());
return sessionListener;
}
}

Filter

概述

与Servlet相似,过滤器是一些web应用程序组件,可以绑定到一个web应用程序中。但是与其他web应用程序组件不同的是,过滤器是”链”在容器的处理过程中的。这就意味着它们会在servlet处理器之前访问一个进入的请求,并且在外发响应信息返回到客户前访问这些响应信息。这种访问使得过滤器可以检查并修改请求和响应的内容。

chain.doFilter将请求转发给过滤器链下一个filter , 如果没有filter那就是你请求的资源。可通过配置CharacterEncodingFilter来解决请求乱码的问题。

步骤

  1. 实现Filter【javax.servlet.Filter】接口,实现Filter方法。
  2. 添加 @Configuration 注解,将自定义Filter加入过滤链;或Filter中添加@WebFilter注解,主类添加@ServletComponentScan注解
1
2
3
4
5
6
7
8
9
10
11
@Bean
public FilterRegistrationBean testFilterRegistration() {

FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new TestFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("testFilter");
registration.setOrder(1);
return registration;
}

或:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Order(1)
@WebFilter(filterName = "testFilter1", urlPatterns = "/*")
public class TestFilterFirst implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("TestFilter1");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {

}
}

拦截器

概述

SpringBoot的拦截器只能拦截流经DispatcherServlet的请求,对于自定义的Servlet无法进行拦截。

SpringMVC中的拦截器有两种:HandlerInterceptor和WebMvcInterceptor。

拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。

  1. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计等。
  2. 权限检查:如登录检测,如果没有直接返回到登录页面;
  3. 性能监控:可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
  4. 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息,解决请求乱码等,只要是多个处理器都需要的即可使用拦截器实现。
  5. OpenSessionInView:如hibernate,在进入处理器打开Session,在完成后关闭Session。

拦截器是AOP的一种实现,底层通过动态代理模式完成。区别:

  • 拦截器是基于Java的反射机制的,而过滤器是基于函数回调。
  • 拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
  • 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
  • 拦截器可以访问action上下文、值栈里的对象,而过滤器不能。
  • 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/** 
*
* 注册拦截器
*/
public class WebAppConfig extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器,添加拦截路径和排除拦截路径
registry.addInterceptor(new InterceptorConfig()).addPathPatterns("api/path/**").excludePathPatterns("api/path/login");
}
}

public class InterceptorConfig implements HandlerInterceptor{

private static final Logger log = LoggerFactory.getLogger(InterceptorConfig.class);

/**
* 进入controller层之前拦截请求
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

log.info("---------------------开始进入请求地址拦截----------------------------");
HttpSession session = httpServletRequest.getSession();
if(!StringUtils.isEmpty(session.getAttribute("userName"))){
return true;
}
else{
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.write("{code:0,message:\"session is invalid,please login again!\"}");
return false;
}

}

@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
log.info("--------------处理请求完成后视图渲染之前的处理操作---------------");
}

@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
log.info("---------------视图渲染之后的操作-------------------------0");
}
}

执行顺序

跨域

概述

同源策略[same origin policy]是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。 同源策略是浏览器安全的基石。

源[origin]就是协议、域名和端口号。若地址里面的协议、域名和端口号均相同则属于同源。

哪些操作不受同源策略限制:

  • 页面中的链接,重定向以及表单提交是不会受到同源策略限制的;
  • 跨域资源的引入是可以的。但是JS不能读写加载的内容。如嵌入到页面中的