Fastjson 和 Spring 进行集成
在 Spring MVC 中集成 Fastjson如果你使用 Spring MVC 来构建 Web 应用并对性能有较高的要求的话,可以使用 Fastjson 提供的FastJsonHttpMessageConverter 来替换 Spring MVC 默认的 HttpMessageConverter 以提高 @RestController @ResponseBody @RequestBody 注解的 JSON序列化速度。下面是配置方式,非常简单。XML式如果是使用 XML 的方式配置 Spring MVC 的话,只需在 Spring MVC 的 XML 配置文件中加入下面配置即可。<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>通常默认配置已经可以满足大部分使用场景,如果你想对它进行自定义配置的话,你可以添加 FastJsonConfig Bean。<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="fastJsonConfig" ref="fastJsonConfig"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig">
<!-- 自定义配置... -->
</bean>编程式如果是使用编程的方式(通常是基于 Spring Boot 项目)配置 Spring MVC 的话只需继承 WebMvcConfigurerAdapter 覆写 configureMessageConverters 方法即可,就像下面这样。@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//自定义配置...
//FastJsonConfig config = new FastJsonConfig();
//config.set ...
//converter.setFastJsonConfig(config);
converters.add(converter);
}
}在 Spring Data Redis 中集成 Fastjson编程式如果是使用编程的方式(通常是基于 Spring Boot 项目)配置 RedisTemplate 的话只需在你的配置类(被@Configuration注解修饰的类)中显式创建 RedisTemplate Bean,设置 Serializer 即可。@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);//设置默认的Serialize,包含 keySerializer & valueSerializer
//redisTemplate.setKeySerializer(fastJsonRedisSerializer);//单独设置keySerializer
//redisTemplate.setValueSerializer(fastJsonRedisSerializer);//单独设置valueSerializer
return redisTemplate;
}通常使用 GenericFastJsonRedisSerializer 即可满足大部分场景,如果你想定义特定类型专用的 RedisTemplate 可以使用 FastJsonRedisSerializer 来代替 GenericFastJsonRedisSerializer ,配置是类似的。参考阿里FastJson的使用_json_qq_30920821的博客-CSDN博客https://blog.csdn.net/qq_30920821/article/details/78417690
轻量级RPC框架(0 - 项目概述)
项目概述My-RPC-Framework 是一款基于 Nacos 实现的 RPC 框架。网络传输实现了基于 Java 原生 Socket 与 Netty 版本,并且实现了多种序列化与负载均衡算法。从一个最简单的BIO + Java序列化开始,逐步完善成Netty + 多序列化方式的比较完整的框架,并且配置了Nacos服务发现。rpc框架的原理理解:客户端和服务端都可以访问到通用的接口,但是只有服务端有这个接口的实现类,客户端调用这个接口的方式:通知服务端我要调用这个接口,服务端收到之后找到这个接口的实现类并执行,将执行的结果返回给客户端,作为客户端调用该接口方法的返回值。原理很简单,但是实现值得商榷,例如客户端怎么知道服务端的地址?客户端怎么告诉服务端我要调用的接口?客户端怎么传递参数?只有接口客户端怎么生成实现类……等等等等。1.系统架构消费者调用提供者的方式取决于消费者的客户端选择,如选用原生 Socket 则该步调用使用 BIO,如选用 Netty 方式则该步调用使用 NIO。如该调用有返回值,则提供者向消费者发送返回值的方式同理。2.项目特性网络传输:实现了基于 Java 原生 Socket 传输与 Netty 传输两种网络传输方式消费端如采用 Netty 方式,会复用 Channel 避免多次连接如消费端和提供者都采用 Netty 方式,会采用 Netty 的心跳机制,保证连接序列化算法:实现了四种序列化算法,Json 方式、Kryo 算法、Hessian 算法与 Google Protobuf 方式(默认采用 Kryo方式序列化)负载均衡算法:实现了两种负载均衡算法:随机算法与轮转算法Nacos注册中心:使用 Nacos 作为注册中心,管理服务提供者信息服务提供侧自动注册服务通信协议:实现自定义的通信协议(MRF协议)接口抽象良好,模块耦合度低,网络传输、序列化器、负载均衡算法可配置3.项目模块rpc-api —— 服务端与客户端公共调用接口(通用接口)rpc-common —— 实体对象、工具类、枚举类等公共类rpc-core —— 框架的核心实现test-client —— 客户端测试代码test-server —— 服务端测试代码4.自定义传输协议调用参数与返回值的传输采用了如下 MRF 协议( My-RPC-Framework 首字母)以防止粘包:+---------------+---------------+-----------------+-------------+
| Magic Number | Package Type | Serializer Type | Data Length |
| 4 bytes | 4 bytes | 4 bytes | 4 bytes |
+---------------+---------------+-----------------+-------------+
| Data Bytes |
| Length: ${Data Length} |
+---------------------------------------------------------------+字段解释Magic Number魔数,表识一个 MRF 协议包,0xCAFEBABEPackage Type包类型,标明这是一个调用请求还是调用响应Serializer Type序列化器类型,标明这个包的数据的序列化方式Data Length数据字节的长度Data Bytes传输的对象,通常是一个RpcRequest或RpcClient对象,取决于Package Type字段,对象的序列化方式取决于Serializer Type字段。
Swift-进阶 07:Mirror源码解析
本文主要是分析Mirror的底层实现,以及根据Mirror底层原理仿写其结构的实现在Swift-进阶 06:反射Mirror & 错误处理文章中,我们介绍了Mirror的使用,即JSON解析,对此我们有如下一些疑问:1、系统是如何通过Mirror获取对应的属性以及值的?2、Swift众所周知是一门静态语言,系统在底层到底做了什么,使swift具有了反射的特性呢?下面我们来对Mirror的底层实现进行探索Mirror底层源码分析反射的API主要是由两部分实现的一部分是通过Swift实现,即ReflectionMirror.swift一部分是通过C++实现,即ReflectionMirror.mm两者之间是通过暴露给swift的C++函数进行通信,即@_silgen_name修饰符会通知swift编译器将这个swift函数映射成C++函数的符号Mirror的源码是在Mirror.swift文件中,路径为swift->stdlib->public->core->Mirror.swiftswift 使用技巧使用@_silgen_name关键字声明的方法,实际调用是括号中的方法,例如swift_cjl_add实际调用的是c中的cjl_add通过C定义一个方法,在swift中使用<!--1、定义c方法-->
//.h声明
int cjl_add(int a, int b);
//.c中实现
int cjl_add(int a, int b){
return a+b;
}
<!--2、桥接文件中引入头文件-->
#import "test.h"
<!--3、swift使用-->
var value = cjl_add(10, 20)
print(value)
<!--4、打印结果-->
30可以将上述代码中的第2步去掉删除,采用@_silgen_name关键字<!--1、swift中针对cjl_add方法的声明-->
//通过@_silgen_name声明
@_silgen_name("cjl_add")
func swift_cjl_add(_ a: Int32, _ b: Int32) -> Int32
<!--2、使用-->
var value = swift_cjl_add(20, 30)
print(value)
<!--3、打印结果-->
50分析Mirror在Mirror.swift文件中找到Mirror,是一个结构体类型查找其初始化方法public init(reflecting subject: Any)public init(reflecting subject: Any) {
//判断 subject 是否符合 CustomReflectable动态类型
if case let customized as CustomReflectable = subject {
//如果符合,则由 customMirror 确定属性
self = customized.customMirror
} else {
//如果不符合,则由语言生成
self = Mirror(internalReflecting: subject)
}
}查找internalReflecting方法(路径为swift->stdlib->public->core->ReflectionMirror.swift)extension Mirror {
// Mirror的初始化器中检索需要的信息
/*
- subject 将要被反射的值
- subjectType 将要被反射的subject值的类型,通常是值的运行时类型
-
*/
internal init(internalReflecting subject: Any,
subjectType: Any.Type? = nil,
customAncestor: Mirror? = nil)
{
//根据_getNormalizedType获取传入的subject的真正类型,其中type(of: subject)获取的动态类型
let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
// 获取属性大小
let childCount = _getChildCount(subject, type: subjectType)
// 遍历,将属性存储到字典中
let children = (0 ..< childCount).lazy.map({
// getChild函数时C++的_getChild 函数的简单封装,将标签名字中包含的C字符串转换为Swift字符串
getChild(of: subject, type: subjectType, index: $0)
})
// 赋值给Mirror的属性children
self.children = Children(children)
// 设置父类反射
self._makeSuperclassMirror = {//按需求构建父类的Mirror的闭包
// 获取传入对象的类
guard let subjectClass = subjectType as? AnyClass,
// 获取父类
let superclass = _getSuperclass(subjectClass) else {
return nil//非类的类型、没有父类的类的Mirror,会获取到nil
}
// 调用者可以用一个可作为父类的Mirror直接返回Mirror实例来指定自定义的祖先的表现
// Handle custom ancestors. If we've hit the custom ancestor's subject type,
// or descendants are suppressed, return it. Otherwise continue reflecting.
if let customAncestor = customAncestor {
if superclass == customAncestor.subjectType {
return customAncestor
}
if customAncestor._defaultDescendantRepresentation == .suppressed {
return customAncestor
}
}
// 给相同值返回一个将 superclass作为 subjectType的新的Mirror
return Mirror(internalReflecting: subject,
subjectType: superclass,
customAncestor: customAncestor)
}
// 获取并解析显示的样式,并设置Mirror的其他属性
let rawDisplayStyle = _getDisplayStyle(subject)
switch UnicodeScalar(Int(rawDisplayStyle)) {
case "c": self.displayStyle = .class
case "e": self.displayStyle = .enum
case "s": self.displayStyle = .struct
case "t": self.displayStyle = .tuple
case "\0": self.displayStyle = nil
default: preconditionFailure("Unknown raw display style '\(rawDisplayStyle)'")
}
self.subjectType = subjectType
self._defaultDescendantRepresentation = .generated
}
// 快速查找
internal static func quickLookObject(_ subject: Any) -> _PlaygroundQuickLook? {
#if _runtime(_ObjC)
let object = _getQuickLookObject(subject)
return object.flatMap(_getClassPlaygroundQuickLook)
#else
return nil
#endif
}
}
👇
<!--superclassMirror属性的底层返回-->
public var superclassMirror: Mirror? {
return _makeSuperclassMirror()
}
nternal let _makeSuperclassMirror: () -> Mirror?1、通过_getNormalizedType获取传入的subject的真正类型2、通过_getChildCount方法获取类中的属性个数3、遍历属性,通过getChild方法(C++的_getChild 函数的简单封装)将标签名字中包含的C字符串转换为Swift字符串,并将属性存储到字典中,赋值给Mirror的属性children4、Mirror有一个属性superclassMirror,会返回该类的父类,其底层是返回一个_makeSuperclassMirror属性,用于保存父类的Mirror闭包。首先会通过subjectType获取父类,然后按照需求构建父类的Mirror闭包,如果是非类的类型、没有父类的类的Mirror,会获取到nil。反之,则直接返回一个可作为父类的Mirror的实例对象。5、获取并解析显示的样式,并设置Mirror的其他属性进入_getNormalizedType的实现,根据定义,最终会调用C++中的swift_reflectionMirror_normalizedType -> Call方法@_silgen_name("swift_reflectionMirror_normalizedType")
internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
👇
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value,
const Metadata *type,
const Metadata *T) {
return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; });
}
👇
/*
- passedValue 实际需要传入的swift的值的指针
- T 该值的静态类型
- passedType 被显式传入且会用在反射过程中的类型
- f 传递被查找到的会被调用的实现的对象引用
- 返回值:返回f参数调用时的返回值
*/
template<typename F>
auto call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType,
const F &f) -> decltype(f(nullptr))
{
// 获取type
const Metadata *type;
OpaqueValue *value;
std::tie(type, value) = unwrapExistential(T, passedValue);
// 判断传入type是否为空,如果不为空,则直接赋值给type
if (passedType != nullptr) {
type = passedType;
}
// 使用 ReflectionMirrorImpl 子类的实例去结束调用f,然后会调用这个实例上的方法去真正的工作完成
auto call = [&](ReflectionMirrorImpl *impl) {
// 返回的type是传入非type
impl->type = type;
impl->value = value;
auto result = f(impl);
return result;
};
.....
switch (type->getKind()) {
case MetadataKind::Tuple: {//元组
......
}
case MetadataKind::Struct: {//结构体
......
}
case MetadataKind::Enum://枚举
case MetadataKind::Optional: {//可选
......
}
......
}
👇
<!--unwrapExistential实现-->
static std::tuple<const Metadata *, OpaqueValue *>
unwrapExistential(const Metadata *T, OpaqueValue *Value) {
// If the value is an existential container, look through it to reflect the
// contained value.如果该值是一个存在的容器,请查看它以反映包含的值。
// TODO: Should look through existential metatypes too, but it doesn't
// really matter yet since we don't have any special mirror behavior for
// concrete metatypes yet.
while (T->getKind() == MetadataKind::Existential) {
auto *existential
= static_cast<const ExistentialTypeMetadata *>(T);
// Unwrap the existential container.打开存在容器
T = existential->getDynamicType(Value);
Value = existential->projectValue(Value);
// Existential containers can end up nested in some cases due to generic
// abstraction barriers. Repeat in case we have a nested existential.
}
return std::make_tuple(T, Value);
}
👇
<!--getDynamicType的实现-->
template<> const Metadata *
ExistentialTypeMetadata::getDynamicType(const OpaqueValue *container) const {
// 根据 获取此存在类型使用的表示形式 判断
switch (getRepresentation()) {
case ExistentialTypeRepresentation::Class: {
auto classContainer =
reinterpret_cast<const ClassExistentialContainer*>(container);
void *obj = classContainer->Value;
return swift_getObjectType(reinterpret_cast<HeapObject*>(obj));
}
case ExistentialTypeRepresentation::Opaque: {
auto opaqueContainer =
reinterpret_cast<const OpaqueExistentialContainer*>(container);
return opaqueContainer->Type;
}
case ExistentialTypeRepresentation::Error: {
const SwiftError *errorBox
= *reinterpret_cast<const SwiftError * const *>(container);
return errorBox->getType();
}
}
swift_runtime_unreachable(
"Unhandled ExistentialTypeRepresentation in switch.");
}call中主要是一个大型switch声明和一些额外的代码去处理特殊的情况,主要是会ReflectionMirrorImpl 的子类实例去结束调用 f,然后会调用这个实例上的方法去让真正的工作完成。1、首先根据unwrapExistential获取type类型,其依赖于metaData元数据2、判断传入的passedType是否为空,如果不为空,则直接赋值给type3、使用 ReflectionMirrorImpl子类的实例去结束调用f,然后会调用这个实例上的方法去真正的工作完成swift-source源码调试_getNormalizedType方法运行swift源码,在Call函数的第一行加断点在源码终端依次输入下面代码class CJLTeacher{var age = 18}
var t = CJLTeacher()
let mirror = Mirror(reflecting: t)运行结果如下,会在call的调用处断住中参数的调试结果如下:其中value 是Mirror实例type是 type(of:)获取的类型T是自己传入的类型ReflectionMirrorImpl 反射基类ReflectionMirrorImpl的种类主要有以下几种:TupleImpl 元组的反射StructImpl 结构体的反射EnumImpl 枚举的反射ClassImpl 类的反射MetatypeImpl 元数据的反射OpaqueImpl 不透明类型的反射这里主要以Struct的反射为例首先查看ReflectionMirrorImpl的底层定义// Abstract base class for reflection implementations.
struct ReflectionMirrorImpl {
const Metadata *type;
OpaqueValue *value;
// 显示的样式
virtual char displayStyle() = 0;
// 属性个数
virtual intptr_t count() = 0;
// 获取偏移值
virtual intptr_t childOffset(intptr_t index) = 0;
// 获取元数据
virtual const FieldType childMetadata(intptr_t index,
const char **outName,
void (**outFreeFunc)(const char *)) = 0;
//获取属性
virtual AnyReturn subscript(intptr_t index, const char **outName,
void (**outFreeFunc)(const char *)) = 0;
//获取枚举的case名字
virtual const char *enumCaseName() { return nullptr; }
#if SWIFT_OBJC_INTEROP
// 快速查找
virtual id quickLookObject() { return nil; }
#endif
// For class types, traverse through superclasses when providing field
// information. The base implementations call through to their local-only
// counterparts.
// 递归查找父类的属性
virtual intptr_t recursiveCount() {
return count();
}
// 递归查找父类属性的偏移值
virtual intptr_t recursiveChildOffset(intptr_t index) {
return childOffset(index);
}
// 递归获取父类的元数据
virtual const FieldType recursiveChildMetadata(intptr_t index,
const char **outName,
void (**outFreeFunc)(const char *))
{
return childMetadata(index, outName, outFreeFunc);
}
// 析构函数
virtual ~ReflectionMirrorImpl() {}
};进入StructImpl结构体的底层实现,需要注意一下几点:// Implementation for structs.
// ReflectionMirrorImpl 的子类 StructImpl 结构体反射
struct StructImpl : ReflectionMirrorImpl {
bool isReflectable() {//是否支持反射
const auto *Struct = static_cast<const StructMetadata *>(type);
const auto &Description = Struct->getDescription();
return Description->isReflectable();
}
// 用 s 的显式样式来表明这是一个结构体
char displayStyle() {
return 's';
}
intptr_t count() {
if (!isReflectable()) {
return 0;
}
// 首先也是找到metadata,然后通过metadata找到desc,然后找到fields,即 NumFields 记录属性的count
auto *Struct = static_cast<const StructMetadata *>(type);
return Struct->getDescription()->NumFields;//属性的count
}
intptr_t childOffset(intptr_t i) {
auto *Struct = static_cast<const StructMetadata *>(type);
// 边界检查
if (i < 0 || (size_t)i > Struct->getDescription()->NumFields)
swift::crash("Swift mirror subscript bounds check failure");
// Load the offset from its respective vector.
// 获取偏移值
return Struct->getFieldOffsets()[i];
}
const FieldType childMetadata(intptr_t i, const char **outName,
void (**outFreeFunc)(const char *)) {
StringRef name;
FieldType fieldInfo;
//通过getFieldAt获取属性的名称
std::tie(name, fieldInfo) = getFieldAt(type, i);
assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented");
*outName = name.data();
*outFreeFunc = nullptr;
return fieldInfo;
}
// subscript 用来获取当前属性的名称和值
AnyReturn subscript(intptr_t i, const char **outName,
void (**outFreeFunc)(const char *)) {
// 获取metaadata
auto fieldInfo = childMetadata(i, outName, outFreeFunc);
auto *bytes = reinterpret_cast<char*>(value);
// 获取属性的偏移值
auto fieldOffset = childOffset(i);
// 计算字段存储的指针
auto *fieldData = reinterpret_cast<OpaqueValue *>(bytes + fieldOffset);
return copyFieldContents(fieldData, fieldInfo);
}
};1、count方法中属性个数的获取,是通过metadata,然后找到其desc,然后找到NumFields获取的,即NumFields 记录属性的count2、subscript方法主要用来获取当前属性的名称和值首先获取metadata然后获取属性的偏移值fieldOffset通过首地址+偏移值,计算属性存储的指针其他几种的分析类似,这里不再作说明仿写Mirror结构以上源码说了这么多,是不是还是有点难以理解,下面我们通过仿写底层的结构来帮助理解Mirror获取属性和值的原理TargetStructMetadata结构体:struct的反射类从Struct的反射类StructImpl中可以知道,其type的类型是StructMetadata在Metadata.h文件中搜索StructMetadata,其真正的类型是TargetStructMetadatausing StructMetadata = TargetStructMetadata<InProcess>;从TargetStructMetadata -> TargetValueMetadata结构体,而TargetValueMetadata继承自TargetMetadata,在Swift-进阶 02:类、对象、属性文章中,我们已经知道,TargetMetadata中有一个属性kind(相当于OC中的isa),而TargetValueMetadata除了拥有父类的kind,还有一个description,用于记录元数据的描述/// The common structure of metadata for structs and enums.
template <typename Runtime>
struct TargetValueMetadata : public TargetMetadata<Runtime> {
using StoredPointer = typename Runtime::StoredPointer;
TargetValueMetadata(MetadataKind Kind,
const TargetTypeContextDescriptor<Runtime> *description)
: TargetMetadata<Runtime>(Kind), Description(description) {}
//用于记录元数据的描述
/// An out-of-line description of the type.
TargetSignedPointer<Runtime, const TargetValueTypeDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
......
}TargetValueTypeDescriptor类:记录metadata信息由上面可知,Description的类型是TargetValueTypeDescriptor,其中有两个属性NumFields 用于记录属性的countFieldOffsetVectorOffset 用于记录属性在metadata中便宜向量的偏移量template <typename Runtime>
class TargetStructDescriptor final
: public TargetValueTypeDescriptor<Runtime>,
public TrailingGenericContextObjects<TargetStructDescriptor<Runtime>,
TargetTypeGenericContextDescriptorHeader,
/*additional trailing objects*/
TargetForeignMetadataInitialization<Runtime>,
TargetSingletonMetadataInitialization<Runtime>> {
......
/// The number of stored properties in the struct.
/// If there is a field offset vector, this is its length.
uint32_t NumFields;//记录属性的count
/// The offset of the field offset vector for this struct's stored
/// properties in its metadata, if any. 0 means there is no field offset
/// vector.
uint32_t FieldOffsetVectorOffset;//记录属性在metadata中便宜向量的偏移量
......
}进入其继承链TargetValueTypeDescriptor -> TargetTypeContextDescriptor类,其中有3个属性Name 用于记录类型的名称,标识当前的类型AccessFunctionPtr 指向此类型的metadata访问函数的指针Fields 指向类型的descriptor的指针template <typename Runtime>
class TargetTypeContextDescriptor
: public TargetContextDescriptor<Runtime> {
public:
/// The name of the type. 类型的名称
TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name;
/// A pointer to the metadata access function for this type.
/// 指向此类型的元数据访问函数的指针
/// The function type here is a stand-in. You should use getAccessFunction()
/// to wrap the function pointer in an accessor that uses the proper calling
/// convention for a given number of arguments.
TargetRelativeDirectPointer<Runtime, MetadataResponse(...),
/*Nullable*/ true> AccessFunctionPtr;
/// A pointer to the field descriptor for the type, if any.指向类型的字段描述符的指针
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
/*nullable*/ true> Fields;
......
}进入TargetContextDescriptor基类的定义,其中有两个参数Flags 用于表示描述context的标志,包含kind和versionParent 用于表示父类的context,如果是在顶层,则表示没有父类,则为NULL/// Base class for all context descriptors.
template<typename Runtime>
struct TargetContextDescriptor {
/// Flags describing the context, including its kind and format version.
ContextDescriptorFlags Flags;
/// The parent context, or null if this is a top-level context.
TargetRelativeContextPointer<Runtime> Parent;
......
}从上述的分析中我们可以得知,属性的获取时通过baseDesc->Fields.get();(在ReflectionMirror.mm文件中getFieldAt方法),即是通过Description中的Fields属性获取,所以还需要分析Fields的类型TargetRelativeDirectPointer,其内部的类型是FieldDescriptorRelativeDirectPointerImpl类:存放offset偏移量TargetRelativeDirectPointer的真正类型是RelativeDirectPointer -> RelativeDirectPointerImpl,RelativeDirectPointerImpl主要用于存放offset偏移量属性RelativeOffset,用于表示属性的相对偏移值,而不是直接存储地址,如下所示其中PointerTy、ValueTy就是传入的类型T、T的指针类型template<typename T, bool Nullable, typename Offset>
class RelativeDirectPointerImpl {
private:
/// The relative offset of the function's entry point from *this.
Offset RelativeOffset;
......
public:
using ValueTy = T;//是一个值
using PointerTy = T*;//是一个指针
}
//get方法 - 用于获取属性
PointerTy get() const & {
// Check for null.检查是否为空
if (Nullable && RelativeOffset == 0)
return nullptr;
// The value is addressed relative to `this`. 值是相对于“this”寻址的
uintptr_t absolute = detail::applyRelativeOffset(this, RelativeOffset);
return reinterpret_cast<PointerTy>(absolute);
}
......
}
👇
<!--applyRelativeOffset的实现-->
template<typename BasePtrTy, typename Offset>
static inline uintptr_t applyRelativeOffset(BasePtrTy *basePtr, Offset offset) {
static_assert(std::is_integral<Offset>::value &&
std::is_signed<Offset>::value,
"offset type should be signed integer");
// 指针地址
auto base = reinterpret_cast<uintptr_t>(basePtr);
// We want to do wrapping arithmetic, but with a sign-extended
// offset. To do this in C, we need to do signed promotion to get
// the sign extension, but we need to perform arithmetic on unsigned values,
// since signed overflow is undefined behavior.
auto extendOffset = (uintptr_t)(intptr_t)offset;
return base + extendOffset;//指针地址+存放的offset(偏移地址) -- 内存平移获取值
}FieldDescriptor类:存放属性进入FieldDescriptor类的定义,如下所示class FieldDescriptor {
const FieldRecord *getFieldRecordBuffer() const {
return reinterpret_cast<const FieldRecord *>(this + 1);
}
public:
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> Superclass;
......
const FieldDescriptorKind Kind;
const uint16_t FieldRecordSize;
const uint32_t NumFields;
......
// 获取所有属性,每个属性用FieldRecord封装
llvm::ArrayRef<FieldRecord> getFields() const {
return {getFieldRecordBuffer(), NumFields};
}
......
} FieldRecord类:封装属性进入FieldRecord类,其定义如下class FieldRecord {
const FieldRecordFlags Flags;
public:
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> FieldName;上面主要分析了结构体通过Mirror获取属性和值涉及的类和结构体,其结构仿写代码如下/// metadata元数据
struct StructMetadata {
// (取自类 - TargetMetadata:kind)
//(继承关系:TargetStructMetadata -> TargetValueMetadata -> TargetMetadata)
var kind: Int
// (取自结构体 - TargetValueMetadata:Description)
var desc: UnsafeMutablePointer<StructMetadataDesc>
}
/// metada的描述信息
struct StructMetadataDesc {
// (取自底层结构体 - TargetContextDescriptor:flags + parent)
var flags: Int32
var parent: Int32
// (取自底层类 - TargetTypeContextDescriptor:name + AccessFunctionPtr + Fields)
//type的名称
//相对指针位置的存储
var name: RelativeDirectPointer<CChar>
//补充完整
var AccessFunctionPtr: RelativeDirectPointer<UnsafeRawPointer>
//是通过Fields的getFiledName获取属性名称
var Fields: RelativeDirectPointer<FieldDescriptor>
// (取自底层类 - TargetClassDescriptor:NumFields + FieldOffsetVectorOffset)
//属性的count
var NumFields: Int32
var FieldOffsetVectorOffset: Int32
}
/// 属性的描述信息
//(取自底层类 - FieldDescriptor)
struct FieldDescriptor {
var MangledTypeName: RelativeDirectPointer<CChar>
var Superclass: RelativeDirectPointer<CChar>
var Kind: UInt16
var FieldRecordSize: Int16
var NumFields: Int32
//每个属性都是FieldRecord,记录在这个结构体中
var fields: FieldRecord//数组中是一个连续的存储空间
}
/// 属性封装类
//(取自底层类 - FieldRecord)
struct FieldRecord{
var Flags: Int32
var MangledTypeName: RelativeDirectPointer<CChar>
var FieldName: RelativeDirectPointer<CChar>
}
/// 记录offset偏移值
struct RelativeDirectPointer<T>{
var offset: Int32
//模拟RelativeDirectPointerImpl类中的get方法 this+offset指针
mutating func get() -> UnsafeMutablePointer<T>{
let offset = self.offset
return withUnsafePointer(to: &self) { p in
/*
获得self,变为raw,然后+offset
- UnsafeRawPointer(p) 表示this
- advanced(by: numericCast(offset) 表示移动的步长,即offset
- assumingMemoryBound(to: T.self) 表示假定类型是T,即自己制定的类型
- UnsafeMutablePointer(mutating:) 表示返回的指针类型
*/
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}其使用如下定义一个CJLTeacher类struct CJLTeacher {
var age = 18
var name = "CJL"
}1、首先获取指向metadata的指针2、然后获取字符串的地址,例如name,并读取内存值/将t1绑定到StructMetadata(unsafeBitCast-按位强转,非常危险,没有任何校验、没有任何修饰)
//unsafeBitCast - 所有的内存按位转换
//1、先获取指向metadata的指针
let ptr = unsafeBitCast(CJLTeacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
//2、然后获取字符串的地址
/*
ptr.pointee 表示StructMetadata
ptr.pointee.desc.pointee 表示StructMetadataDesc
ptr.pointee.desc.pointee.name 表示RelativeDirectPointer<T>
*/
let namePtr = ptr.pointee.desc.pointee.name.get()
print(String(cString: namePtr))
//读取内存值
print(ptr.pointee.desc.pointee.NumFields)3、获取首地址,通过指针移动来获取访问属性的地址,例如输出age属性//获取首地址
let filedDescriptorPtr = ptr.pointee.desc.pointee.Fields.get()
//指针移动来获取访问属性的地址
let recordPtr = withUnsafePointer(to: &filedDescriptorPtr.pointee.fields) {
/*
- UnsafeRawPointer + assumingMemoryBound -- 类型指针
- advanced 类型指针只需要移动 下标即可
*/
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: 0))
}
//输出age属性
print(String(cString: recordPtr.pointee.FieldName.get()))如果将advanced中的0改成1,输出name总结所以综上所述,Mirror反射干的事情:1、Mirror在实例对象的metadata中找到Descriptor2、在Descriptor中找到name,获取类型(相当于type名称)找到numFields,获取属性个数3、找到FieldDescriptor中的fields,来找到对当前属性的描述,然后通过指针移动,获取其他属性以上就是整个mirror在底层做的事情,如下所示,以struct为例的一个流程【补充】swift中的type(of:)、dump(t)就是基于Mirror的反射原理来实现的swift中JSON解析的三方库HandyJSON其原理就是Mirror反射的原理,本质就是利用metadata元数据中的descriptor,然后通过字段的访问,做内存的赋值(后续会完整分析HandyJSON)所以,针对我们开头的两个问题,可以通过上面的Mirror反射的原理来进行解释1、系统是如何通过Mirror获取对应的属性以及值的?参考上述的Mirror原理总结2、Swift众所周知是一门静态语言,系统在底层到底做了什么,使swift具有了反射的特性呢?Swift 的反射机制是基于一个叫 Mirror 的 struct 来实现的。即为具体的 subject 创建一个 Mirror,然后就可以通过它查询这个对象subject。简单理解就是 Mirror通过meatadata,在其内部创建了一个结构,用于输出metadata中的descriptor
RPC框架(4 - 实现一个基于 Kryo 的序列化器)
5.4实现一个基于 Kryo 的序列化器上一节我们实现了一个通用的序列化框架,使得序列化方式具有了较高的扩展性,并且实现了一个基于 JSON 的序列化器。但是,我们也提到过,这个基于 JSON 的序列化器有一个毛病,就是在某个类的属性反序列化时,如果属性声明为 Object 的,就会造成反序列化出错,通常会把 Object 属性直接反序列化成 String 类型,就需要其他参数辅助序列化。并且,JSON 序列化器是基于字符串(JSON 串)的,占用空间较大且速度较慢。这一节我们就来实现一个基于 Kryo 的序列化器。那么,什么是 Kryo?Kryo 是一个快速高效的 Java 对象序列化框架,主要特点是高性能、高效和易用。最重要的两个特点:一是基于字节的序列化,对空间利用率较高,在网络传输时可以减小体积;二是序列化时记录属性对象的类型信息,这样在反序列化时就不会出现之前的问题了。5.4.1实现通用的序列化框架添加依赖<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>我们在上一节定义了一个通用的序列化接口:public interface CommonSerializer {
byte[] serialize(Object obj);
Object deserialize(byte[] bytes, Class<?> clazz);
int getCode();
static CommonSerializer getByCode(int code) {
switch (code) {
case 1:
return new JsonSerializer();
default:
return null;
}
}
}这里我们可以把 Kryo 的编号设为 0,后续会作为默认的序列化器,在静态方法的 switch 中加一个 case 即可。根据接口,我们的主要任务就是实现其中的主要两个方法,serialize() 和 deserialize() ,如下:public class KryoSerializer implements CommonSerializer {
private static final Logger logger = LoggerFactory.getLogger(KryoSerializer.class);
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.register(RpcResponse.class);
kryo.register(RpcRequest.class);
kryo.setReferences(true);
kryo.setRegistrationRequired(false);
return kryo;
});
@Override
public byte[] serialize(Object obj) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream)){
Kryo kryo = kryoThreadLocal.get();
kryo.writeObject(output, obj);
kryoThreadLocal.remove();
return output.toBytes();
} catch (Exception e) {
logger.error("序列化时有错误发生:", e);
throw new SerializeException("序列化时有错误发生");
}
}
@Override
public Object deserialize(byte[] bytes, Class<?> clazz) {
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Input input = new Input(byteArrayInputStream)) {
Kryo kryo = kryoThreadLocal.get();
Object o = kryo.readObject(input, clazz);
kryoThreadLocal.remove();
return o;
} catch (Exception e) {
logger.error("反序列化时有错误发生:", e);
throw new SerializeException("反序列化时有错误发生");
}
}
@Override
public int getCode() {
return SerializerCode.valueOf("KRYO").getCode();
}
}这里 Kryo 可能存在线程安全问题,文档上是推荐放在 ThreadLocal 里,一个线程一个 Kryo。在序列化时,先创建一个 Output 对象(Kryo 框架的概念),接着使用 writeObject 方法将对象写入 Output 中,最后调用 Output 对象的 toByte() 方法即可获得对象的字节数组。反序列化则是从 Input 对象中直接 readObject,这里只需要传入对象的类型,而不需要具体传入每一个属性的类型信息。最后 getCode 方法中事实上是把序列化的编号写在一个枚举类 SerializerCode 里了:public enum SerializerCode {
KRYO(0),
JSON(1);
private final int code;
}5.4.2替换序列化器测试我们只需要把 NettyServer 和 NettyClient 责任链中的 CommonEncoder 传入的参数改成 KryoSerializer 即可使用 Kryo 序列化。- pipeline.addLast(new CommonEncoder(new JsonSerializer()));
+ pipeline.addLast(new CommonEncoder(new KryoSerializer()));最后运行之前的测试,测试结果与之前相同即没问题。
RPC框架(3 - 实现Netty传输和通用序列化接口)
5.3实现Netty传输和通用序列化接口核心:将传统的 BIO 方式传输换成效率更高的 NIO 方式,使用Netty(并非Java原生NIO);实现通用的序列化接口,为多种序列化支持做准备,自定义传输的协议。5.3.1Netty 服务端与客户端首先就需要在 pom.xml 中加入 Netty 依赖:<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-version}</version>
</dependency>netty 的最新版本可以在 maven repository查到,注意使用 netty 4 而不是 netty 5。为了保证通用性,我们可以把 Server 和 Client 抽象成两个接口,分别是 RpcServer 和 RpcClient:public interface RpcServer {
void start(int port);
}
public interface RpcClient {
Object sendRequest(RpcRequest rpcRequest);
}而原来的 RpcServer 和 RpcClient 类实际上是上述两个接口的 Socket 方式实现类,改成 SocketServer 和 SocketClient 并实现上面两个接口即可,几乎不需要做什么修改。我们的任务,就是要实现 NettyServer 和 NettyClient。这里提一个改动,就是在 DefaultServiceRegistry.java 中,将包含注册信息的 serviceMap 和 registeredService 都改成了 static ,这样就能保证全局唯一的注册信息,并且在创建 RpcServer 时也就不需要传入了。NettyServer的实现很传统:public class NettyServer implements RpcServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
@Override
public void start(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.option(ChannelOption.SO_BACKLOG, 256)
.option(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new CommonEncoder(new JsonSerializer()));
pipeline.addLast(new CommonDecoder());
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.error("启动服务器时有错误发生: ", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}了解过 Netty 的同学可能知道,Netty 中有一个很重要的设计模式——责任链模式,责任链上有多个处理器,每个处理器都会对数据进行加工,并将处理后的数据传给下一个处理器。代码中的 CommonEncoder、CommonDecoder和NettyServerHandler 分别就是编码器,解码器和数据处理器。因为数据从外部传入时需要解码,而传出时需要编码,类似计算机网络的分层模型,每一层向下层传递数据时都要加上该层的信息,而向上层传递时则需要对本层信息进行解码。而 NettyClient 的实现也很类似:public class NettyClient implements RpcClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
private String host;
private int port;
private static final Bootstrap bootstrap;
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
static {
EventLoopGroup group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new CommonDecoder())
.addLast(new CommonEncoder(new JsonSerializer()))
.addLast(new NettyClientHandler());
}
});
}
@Override
public Object sendRequest(RpcRequest rpcRequest) {
try {
ChannelFuture future = bootstrap.connect(host, port).sync();
logger.info("客户端连接到服务器 {}:{}", host, port);
Channel channel = future.channel();
if(channel != null) {
channel.writeAndFlush(rpcRequest).addListener(future1 -> {
if(future1.isSuccess()) {
logger.info(String.format("客户端发送消息: %s", rpcRequest.toString()));
} else {
logger.error("发送消息时有错误发生: ", future1.cause());
}
});
channel.closeFuture().sync();
AttributeKey<RpcResponse> key = AttributeKey.valueOf("rpcResponse");
RpcResponse rpcResponse = channel.attr(key).get();
return rpcResponse.getData();
}
} catch (InterruptedException e) {
logger.error("发送消息时有错误发生: ", e);
}
return null;
}
}在静态代码块中就直接配置好了 Netty 客户端,等待发送数据时启动,channel 将 RpcRequest 对象写出,并且等待服务端返回的结果。注意这里的发送是非阻塞的,所以发送后会立刻返回,而无法得到结果。这里通过 AttributeKey 的方式阻塞获得返回结果:AttributeKey<RpcResponse> key = AttributeKey.valueOf("rpcResponse");
RpcResponse rpcResponse = channel.attr(key).get();通过这种方式获得全局可见的返回结果,在获得返回结果 RpcResponse 后,将这个对象以 key 为 rpcResponse 放入 ChannelHandlerContext 中,这里就可以立刻获得结果并返回,我们会在 NettyClientHandler 中看到放入的过程。5.3.2自定义协议与编解码器在传输过程中,我们可以在发送的数据上加上各种必要的数据,形成自定义的协议,而自动加上这个数据就是编码器的工作,解析数据获得原始数据就是解码器的工作。我们定义的协议是这样的:+---------------+---------------+-----------------+-------------+
| Magic Number | Package Type | Serializer Type | Data Length |
| 4 bytes | 4 bytes | 4 bytes | 4 bytes |
+---------------+---------------+-----------------+-------------+
| Data Bytes |
| Length: ${Data Length} |
+---------------------------------------------------------------+4 字节魔数,表示一个协议包,通过固定数值和字符串标识这个协议包。Package Type,标明这是一个调用请求还是调用响应Serializer Type 标明了实际数据使用的序列化器,这个服务端和客户端应当使用统一标准Data Length 就是实际数据的长度,设置这个字段主要防止粘包,最后就是经过序列化后的实际数据,可能是 RpcRequest 也可能是 RpcResponse 经过序列化后的字节,取决于 Package Type。规定好协议后,我们就可以来看看 CommonEncoder(编码器) 了:public class CommonEncoder extends MessageToByteEncoder {
private static final int MAGIC_NUMBER = 0xCAFEBABE;
private final CommonSerializer serializer;
public CommonEncoder(CommonSerializer serializer) {
this.serializer = serializer;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
out.writeInt(MAGIC_NUMBER);
if(msg instanceof RpcRequest) {
out.writeInt(PackageType.REQUEST_PACK.getCode());
} else {
out.writeInt(PackageType.RESPONSE_PACK.getCode());
}
out.writeInt(serializer.getCode());
byte[] bytes = serializer.serialize(msg);
out.writeInt(bytes.length);
out.writeBytes(bytes);
}
}CommonEncoder 继承了MessageToByteEncoder 类,见名知义,就是把 Message(实际要发送的对象)转化成 Byte 数组。CommonEncoder 的工作很简单,就是把 RpcRequest 或者 RpcResponse 包装成协议包。 根据上面提到的协议格式,将各个字段写到管道里就可以了,这里serializer.getCode() 获取序列化器的编号,之后使用传入的序列化器将请求或响应包序列化为字节数组写入管道即可。而 CommonDecoder 的工作就更简单了:public class CommonDecoder extends ReplayingDecoder {
private static final Logger logger = LoggerFactory.getLogger(CommonDecoder.class);
private static final int MAGIC_NUMBER = 0xCAFEBABE;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int magic = in.readInt();
if(magic != MAGIC_NUMBER) {
logger.error("不识别的协议包: {}", magic);
throw new RpcException(RpcError.UNKNOWN_PROTOCOL);
}
int packageCode = in.readInt();
Class<?> packageClass;
if(packageCode == PackageType.REQUEST_PACK.getCode()) {
packageClass = RpcRequest.class;
} else if(packageCode == PackageType.RESPONSE_PACK.getCode()) {
packageClass = RpcResponse.class;
} else {
logger.error("不识别的数据包: {}", packageCode);
throw new RpcException(RpcError.UNKNOWN_PACKAGE_TYPE);
}
int serializerCode = in.readInt();
CommonSerializer serializer = CommonSerializer.getByCode(serializerCode);
if(serializer == null) {
logger.error("不识别的反序列化器: {}", serializerCode);
throw new RpcException(RpcError.UNKNOWN_SERIALIZER);
}
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes);
Object obj = serializer.deserialize(bytes, packageClass);
out.add(obj);
}
}CommonDecoder 继承自 ReplayingDecoder ,与 MessageToByteEncoder 相反,它用于将收到的字节序列还原为实际对象。主要就是一些字段的校验,比较重要的就是取出序列化器的编号,以获得正确的反序列化方式,并且读入 length 字段来确定数据包的长度(防止粘包),最后读入正确大小的字节数组,反序列化成对应的对象。5.3.3序列化接口序列化器接口(CommonSerializer)如下:public interface CommonSerializer {
byte[] serialize(Object obj);
Object deserialize(byte[] bytes, Class<?> clazz);
int getCode();
static CommonSerializer getByCode(int code) {
switch (code) {
case 1:
return new JsonSerializer();
default:
return null;
}
}
}主要就是四个方法,序列化,反序列化,获得该序列化器的编号,已经根据编号获取序列化器,这里我已经写了一个示例的 JSON 序列化器,Kryo 序列化器会在后面讲解。作为一个比较简单的例子,我写了一个 JSON 的序列化器:public class JsonSerializer implements CommonSerializer {
private static final Logger logger = LoggerFactory.getLogger(JsonSerializer.class);
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public byte[] serialize(Object obj) {
try {
return objectMapper.writeValueAsBytes(obj);
} catch (JsonProcessingException e) {
logger.error("序列化时有错误发生: {}", e.getMessage());
e.printStackTrace();
return null;
}
}
@Override
public Object deserialize(byte[] bytes, Class<?> clazz) {
try {
Object obj = objectMapper.readValue(bytes, clazz);
if(obj instanceof RpcRequest) {
obj = handleRequest(obj);
}
return obj;
} catch (IOException e) {
logger.error("反序列化时有错误发生: {}", e.getMessage());
e.printStackTrace();
return null;
}
}
/*
这里由于使用JSON序列化和反序列化Object数组,无法保证反序列化后仍然为原实例类型
需要重新判断处理
*/
private Object handleRequest(Object obj) throws IOException {
RpcRequest rpcRequest = (RpcRequest) obj;
for(int i = 0; i < rpcRequest.getParamTypes().length; i ++) {
Class<?> clazz = rpcRequest.getParamTypes()[i];
if(!clazz.isAssignableFrom(rpcRequest.getParameters()[i].getClass())) {
byte[] bytes = objectMapper.writeValueAsBytes(rpcRequest.getParameters()[i]);
rpcRequest.getParameters()[i] = objectMapper.readValue(bytes, clazz);
}
}
return rpcRequest;
}
@Override
public int getCode() {
return SerializerCode.valueOf("JSON").getCode();
}
}JSON 序列化工具我使用的是 Jackson,在 pom.xml 中添加依赖即可。序列化和反序列化都比较循规蹈矩,把对象翻译成字节数组,和根据字节数组和 Class 反序列化成对象。这里有一个需要注意的点,就是在 RpcRequest 反序列化时,由于其中有一个字段是 Object 数组,在反序列化时序列化器会根据字段类型进行反序列化,而 Object 就是一个十分模糊的类型,会出现反序列化失败的现象,这时就需要 RpcRequest 中的另一个字段 ParamTypes 来获取到 Object 数组中的每个实例的实际类,辅助反序列化,这就是 handleRequest() 方法的作用。上面提到的这种情况不会在其他序列化方式中出现,因为其他序列化方式是转换成字节数组,会记录对象的信息,而 JSON 方式本质上只是转换成 JSON 字符串,会丢失对象的类型信息。5.3.4NettyServerHandler 和 NettyClientHandlerNettyServerHandler 和 NettyClientHandler 都分别位于服务器端和客户端责任链的尾部,直接和 RpcServer 对象或 RpcClient 对象打交道,而无需关心字节序列的情况。NettyServerhandler 用于接收 RpcRequest,并且执行调用,将调用结果返回封装成 RpcResponse 发送出去。public class NettyServerHandler extends SimpleChannelInboundHandler<RpcRequest> {
private static final Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);
private static RequestHandler requestHandler;
private static ServiceRegistry serviceRegistry;
static {
requestHandler = new RequestHandler();
serviceRegistry = new DefaultServiceRegistry();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) throws Exception {
try {
logger.info("服务器接收到请求: {}", msg);
String interfaceName = msg.getInterfaceName();
Object service = serviceRegistry.getService(interfaceName);
Object result = requestHandler.handle(msg, service);
ChannelFuture future = ctx.writeAndFlush(RpcResponse.success(result));
future.addListener(ChannelFutureListener.CLOSE);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("处理过程调用时有错误发生:");
cause.printStackTrace();
ctx.close();
}
}处理方式和 Socket 中的逻辑基本一致,不做讲解。NettyClientHandlerpublic class NettyClientHandler extends SimpleChannelInboundHandler<RpcResponse> {
private static final Logger logger = LoggerFactory.getLogger(NettyClientHandler.class);
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponse msg) throws Exception {
try {
logger.info(String.format("客户端接收到消息: %s", msg));
AttributeKey<RpcResponse> key = AttributeKey.valueOf("rpcResponse");
ctx.channel().attr(key).set(msg);
ctx.channel().close();
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error("过程调用时有错误发生:");
cause.printStackTrace();
ctx.close();
}
}这里只需要处理收到的消息,即 RpcResponse 对象,由于前面已经有解码器解码了,这里就直接将返回的结果放入 ctx 中即可。5.3.5测试netty方式public class NettyTestServer {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
ServiceRegistry registry = new DefaultServiceRegistry();
registry.register(helloService);
NettyServer server = new NettyServer();
server.start(9999);
}
}
public class NettyTestClient {
public static void main(String[] args) {
RpcClient client = new NettyClient("127.0.0.1", 9999);
RpcClientProxy rpcClientProxy = new RpcClientProxy(client);
HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
HelloObject object = new HelloObject(12, "This is a message");
String res = helloService.hello(object);
System.out.println(res);
}
}注意这里 RpcClientProxy 通过传入不同的 Client(SocketClient、NettyClient)来切换客户端不同的发送方式。执行后可以获得与之前类似的结果。
Swift-进阶 06:反射Mirror & 错误处理
本文主要介绍Mirror的使用以及使用Mirror进行JSON解析的错误处理反射Mirror反射:是指可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性,在上面的分析中,我们已经知道,对于一个纯swift类来说,并不支持直接像OC runtime那样的操作但是swift标准库依旧提供了反射机制,用来访问成员信息,即Mirror一般使用class CJLTeacher: NSObject {
var age: Int = 18
}
let mirror = Mirror(reflecting: CJLTeacher.self)
for pro in mirror.children{
print("\(pro.label): \(pro.value)")
}运行上面代码,发现没有任何打印,为什么?是因为Mirror中传入的参数不对,应该是传入实例对象,修改如下class CJLTeacher: NSObject {
var age: Int = 18
}
var t = CJLTeacher()
//传入t也可以
let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{
print("\(pro.label): \(pro.value)")
}查看Mirror定义进入Mirror初始化方法,发现传入的类型是Any,则可以直接传tpublic init(reflecting subject: Any)进入childrenpublic let children: Mirror.Children
👇
//进入Children,发现是一个AnyCollection,接收一个泛型
public typealias Children = AnyCollection<Mirror.Child>
👇
//进入Child,发现是一个元组类型,由可选的标签和值构成,
public typealias Child = (label: String?, value: Any)这也是为什么能够通过label、value打印的原因。即可以在编译时期且不用知道任何类型信息情况下,在Child的值上用Mirror去遍历整个对象的层级视图JSON解析根据Mirror的这个特性,我们思考下,可以通过Mirror做什么?首先想到的是JSON解析,如下所示,我们定义了一个CJLTeacher类,然后通过一个test方法来解析tclass CJLTeacher {
var age = 18
var name = "CJL"
}
<!--JSON解析-->
func test(_ obj: Any) -> Any{
let mirror = Mirror(reflecting: obj)
//判断条件 - 递归终止条件
guard !mirror.children.isEmpty else {
return obj
}
//字典
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let keyName = children.label {
//递归调用
keyValue[keyName] = test(children.value)
}else{
print("children.label 为空")
}
}
return keyValue
}
<!--使用-->
var t = CJLTeacher()
print(test(t))代码的打印结果如下,打印出了key-valueJSON解析封装如果我们想大规模的使用上述的JSON解析,上面只是针对CJLTeacher的JSON解析,所以,为了方便其他类使用,可以将JSON解析抽取成一个协议,然后提供一个默认实现,让类遵守协议//1、定义一个JSON解析协议
protocol CustomJSONMap {
func jsonMap() -> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//递归终止条件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存储json数据
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
print("key是nil")
}
}else{
print("当前-\(children.value)-没有遵守协议")
}
}
return keyValue
}
}
//3、让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
class CJLTeacher: CustomJSONMap {
var age = 18
var name = "CJL"
}
//使用
var t = CJLTeacher()
print(t.jsonMap())【问题】:运行代码发现,并不是我们想要的结果,原因是因为CJLTeacher中属性的类型也需要遵守CustomJSONMap协议【修改】:所以在原有基础上增加以下代码//Int、String遵守协议
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}修改后的运行结果如下错误处理为了让我们自己封装的JSON解析更好用,除了对正常返回的处理,还需要对其中的错误进行处理,在上面的封装中,我们目前采用的是print打印的,这样并不规范,也不好维护及管理。那么如何在swift中正确的表达错误呢?首先,Swift中,提供了Error协议来标识当前应用程序发生错误的情况,其中Error的定义如下public protocol Error {
}Error是一个空协议,其中没有任何实现,这也就意味着你可以遵守该协议,然后自定义错误类型。所以不管是我们的struct、Class、enum,我们都可以遵循这个Error来表示一个错误所以接下来,对我们上面封装的JSON解析修改其中的错误处理定义一个JSONMapError错误枚举,将默认实现的print替换成枚举类型//定义错误类型
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
//1、定义一个JSON解析协议
protocol CustomJSONMap {
func jsonMap() -> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//递归终止条件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存储json数据
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
return JSONMapError.emptyKey
}
}else{
return JSONMapError.notConformProtocol
}
}
return keyValue
}
}但是这里有一个问题,jsonMap方法的返回值是Any,我们无法区分返回的是错误还是json数据,那么该如何区分呢?即如何抛出错误呢?在这里可以通过throw关键字(即将Demo中原本return的错误,改成throw抛出)//2、提供一个默认实现
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
//递归终止条件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存储json数据
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}发现改成throw抛出错误后,编译器提示有错,其原因是因为方法并没有声明成throws所以还需要在方法的返回值箭头前增加throws(表示将方法中错误抛出),修改后的默认实现代码如下所示//1、定义一个JSON解析协议
protocol CustomJSONMap {
func jsonMap() throws-> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//递归终止条件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存储json数据
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = value.jsonMap()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}由于我们在jsonMap方法中递归调用了自己,所以还需要在递归调用前增加 try 关键字//2、提供一个默认实现
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//递归终止条件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存储json数据
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = try value.jsonMap()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
<!--使用时需要加上try-->
var t = CJLTeacher()
print(try t.jsonMap())到此,一个完整的错误表达方式就完成了swift中错误处理的方式swift中错误处理的方式主要有以下两种:【方式一】:使用try关键字,是最简便的,即甩锅,将这个抛出给别人(向上抛出,抛给上层函数)。但是在使用时,需要注意以下两点:try? 返回一个可选类型,只有两种结果:要么成功,返回具体的字典值要么错误,但并不关心是哪种错误,统一返回niltry! 表示你对这段代码有绝对的自信,这行代码绝对不会发生错误【方式二】:使用do...catch【方式一】try通过try来处理JSON解析的错误//CJLTeacher中定义一个height属性,并未遵守协议
class CJLTeacher: CustomJSONMap {
var age = 18
var name = "CJL"
var height = 1.85
}
/*****1、try? 示例*****/
var t = CJLTeacher()
print(try? t.jsonMap())
/*****打印结果*****/
nil
/*****2、try! 示例*****/
var t = CJLTeacher()
print(try! t.jsonMap())
/*****打印结果*****/
Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
2020-12-20 18:27:28.305112+0800 05-MirrorAndError[18642:1408258] Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
<!--3、如果直接使用try,是向上抛出-->
var t = CJLTeacher()
try t.jsonMap()
/*****打印结果*****/
Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200
2020-12-20 18:31:24.837476+0800 05-MirrorAndError[18662:1410970] Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200从上面可以知道,错误是向上抛出的,即抛给了上层函数,如果上层函数也不处理,则直接抛给main,main没有办法处理,则直接报错,例如下面的例子//使用
var t = CJLTeacher()
func test() throws -> Any{
try t.jsonMap()
}
try test()其运行结果如下方式二:do-catch通过do-catch来处理JSON解析的错误var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print(error)
}运行结果如下LocalError协议如果只是用Error还不足以表达更详尽的错误信息,可以使用LocalizedError协议,其定义如下public protocol LocalizedError : Error {
/// A localized message describing what error occurred.错误描述
var errorDescription: String? { get }
/// A localized message describing the reason for the failure.失败原因
var failureReason: String? { get }
/// A localized message describing how one might recover from the failure.建议
var recoverySuggestion: String? { get }
/// A localized message providing "help" text if the user requests help.帮助
var helpAnchor: String? { get }
}继续修改上面的JSON解析,对JSONMapError枚举增加一个扩展,遵守LocalizedError协议,可以打印更详细的错误信息//定义更详细的错误信息
extension JSONMapError: LocalizedError{
var errorDescription: String?{
switch self {
case .emptyKey:
return "key为空"
case .notConformProtocol:
return "没有遵守协议"
}
}
}
<!--使用-->
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print(error.localizedDescription)
}运行结果如下CustomNSError协议CustomNSError相当于OC中的NSError,其定义如下,有三个默认属性public protocol CustomNSError : Error {
/// The domain of the error.
static var errorDomain: String { get }
/// The error code within the given domain.
var errorCode: Int { get }
/// The user-info dictionary.
var errorUserInfo: [String : Any] { get }
}继续修改JSON解析中的JSONMapError,让其遵守CustomNSError协议,如下所示//CustomNSError相当于NSError
extension JSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
<!--使用-->
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print((error as? JSONMapError)?.errorCode)
}运行结果如下总结Error是swift中错误类型的基本协议,其中LocalizedError、CustomNSError都遵守Error如果在方法中,想要同时返回正常值和错误,需要通过throw返回错误,并且在方法返回值的箭头前面加throws关键字,再调用方法时,还需要加上try关键字,或者使用do-catch,使用try时,有以下两点需要注意:try? 返回的是一个可选类型,要么成功,返回正常值,要么失败,返回niltry! 表示你对自己的代码非常自信,绝对不会发生错误,一旦发生错误,就会崩溃使用建议:建议使用try?,而不是try!最后附上完整的自定义JSON解析代码//定义错误类型
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
//定义更详细的错误信息
extension JSONMapError: LocalizedError{
var errorDescription: String?{
switch self {
case .emptyKey:
return "key为空"
case .notConformProtocol:
return "没有遵守协议"
}
}
}
//CustomNSError相当于NSError
extension JSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return 404
case .notConformProtocol:
return 504
}
}
}
//1、定义一个JSON解析协议
protocol CustomJSONMap {
func jsonMap() throws-> Any
}
//2、提供一个默认实现
extension CustomJSONMap{
func jsonMap() throws -> Any{
let mirror = Mirror(reflecting: self)
//递归终止条件
guard !mirror.children.isEmpty else {
return self
}
//字典,用于存储json数据
var keyValue: [String: Any] = [:]
//遍历
for children in mirror.children {
if let value = children.value as? CustomJSONMap {
if let keyName = children.label {
//递归
keyValue[keyName] = try value.jsonMap()
}else{
throw JSONMapError.emptyKey
}
}else{
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
//3、让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)
class CJLTeacher: CustomJSONMap {
var age = 18
var name = "CJL"
var height = 1.85
}
//使用
var t = CJLTeacher()
do{
try t.jsonMap()
}catch{
print((error as? JSONMapError)?.errorCode)
}
站在Java的视角,深度分析防不胜防的小偷——“XSS”
1你的网站存在XSS漏洞!yrzx404这两天比较闲,说我们给小伙伴们来写一个用来提建议的网站吧,能让小伙伴第一时间将想要说的话反馈给我们,并且所有小伙伴都可以看到其他小伙伴提出的建议。这还不简单,就一个单页网站,再结合CRUD就可以搞定,那这么简单的任务就分配给znlover那家伙去搞吧。那znlover是如何实现这个小网站的?说来有一套,znlover采用了spring-boot+jpa+thymeleaf技术栈不到半个小时就搞定了,虽然界面简陋,但功能基本完成了,界面如下:东哥看到这个网站后,测试了5分钟,说“你这个网站不安全,有XSS漏洞!”。XSS漏洞?一个留言板,怎么会存在漏洞,znlover十分不解。通过查看东哥的留言,发现确实网站的运行状态不正常了,现在一打开留言网站,就会弹出一个提醒框:这是什么鬼?html就没有使用过alert函数啊,怎么会有弹框?通过数据库查询,发现东哥输入的留言是这样的:2FXSS漏洞攻击原理首先我们来看一下造成XSS漏洞攻击的原因,获取留言与展示留言的前端代码如下:可以看到,前端通过ajax向后台异步请求json数据,然后通过字符串拼接成一个table DOM的方式来实现留言的显示,这是一种很常见也很常规的前后端数据交互的代码写法,在正常情况下是不会有问题的。但与SQL注入的原理本质相同,问题就出现在HTML代码与用户的输入产生了拼接,这就为恶意漏洞利用者提供了代码执行的机会。正如在上一节中,yrzx404输入的留言是一段合法的javasrcipt代码,在通过拼接后生成的的html页面,浏览器就会将其解析成可执行的javasrcipt代码进行执行,因此造成了XSS跨站脚本攻击(Cross Site Scripting),造成XSS执行拼接后的Html片段如下:所以,XSS漏洞的执行本质上与很多漏洞的造成原因相同,都是由于“数据与代码未严格分离”,前端将用户的数据当做代码来执行了。3FXSS漏洞的危害XSS漏洞是在客户端执行,如果XSS攻击发生在访问量很大的页面,那将会是很严重的安全事件。试想,如果一个门户网站被小黑客利用XSS搞了个弹框恶作剧,那将会极大的损害公司的声誉与口碑,为公司带来无形的损失。如果仅仅是恶作剧,可能只是一次是不成熟的白帽行为,想借此提醒开发者网站存在漏洞,请尽快修补。但往以获取用户数据为目的黑客,会很隐蔽地收集用户的隐私数据,cookie,详细的用户主机信息,最严重的是进行种网马。这将会使我们的用户处于极其不安全的环境中,这也是为什么XSS漏洞攻击常年在OWASP榜首的位置。这里我们来看看黑客通过XSS获取用户cookie的恶意脚本:上面js脚本构造一个<img> DOM,并通过document.cookie获取到了用户当前会话的cookie并藏在了img标签的url中,这样该页面被浏览器加载后便会带着用户的cookie去某一服务器访问图片了。黑客只需简单搭建一个Http服务器,看访问日志即可收货偷来的cookie:我们知道,cookie是网站服务器用于与客户端保持会话与身份认证的关键属性,一旦黑客获取到了用户cookie,往往意味着黑客同时获取到了客户当前会话的访问权利,从而可以不用登陆验证便可进入用户的系统,这太危险了!4FCookie的防御HttpOnly 为了解决XSS攻击漏洞会造成cookie被劫持攻击的问题,微软在IE 6开始支持Cookie?HttpOnly标准,HttpOnly的作用是,如果在Http Response中,使用HttpOnly来标识的Cookie在浏览器中将无法被JavaScript访问,也就是说document.cookie就失效了。从Java EE 6.0开始,便支持通过Cookie.setHttpOnly(true)来设置HttpOnly类型的Cookie,代码如下:此外还可以直接在WEB-INF/web.xml中将Session相关的Cookie添加HttpOnly标记,代码如下:可以看到,我们的XSS Payload已经无法通过获取到用户的cookie了:如今基本所有公开浏览器都对HttpOnly提供了支持,可以如此高效简单的防御一个web安全问题的手段可真不多见,真可谓四两拨千斤,但悲哀的是HttpOnly标准出现了15年了(2002年制定),但清楚知道其作用的网站开发人员却并不多。 5F前端XSS防护既然XSS漏洞造成原因是由于执行了用户输入数据造成的,那么最直接的防御策略就是对用户的输入进行处理,将有可能造成XSS攻击的字符进行转义,这样就可以防止恶意XSS数据在浏览器中解析成合法的JavaScript脚本来执行。这里我们将留言进行敏感字符转意。作者这里使用了一个开源的用于XSS过滤转义的javascript库 -- js-xss,其支持以npm bower的方式依赖引入,在其github主页有详细安装与使用方法介绍。js-xss库提供了一个非常简单方便的XSS过滤转义函数filterXSS(),我们直接用它来对提交的留言进行过滤处理,代码如下:此时,我们在将XSS Payload提交测试,可以发现浏览器没有触发XSS执行,因为js脚本确实被转义了:这里filteXSS函数将敏感的括号尖括号等字符进行了转义,因此浏览器解析时XSS Payload就失效了。此外,通过测试,用来偷Cookie的XSS Payload也同样失效了。OK,防御策略小有成效,值得庆祝,产品可以上线了!但真的没有问题了吗,这样防御真的可以解决XSS攻击吗?6F我们真的可以相信前端吗?在上一节我们通过将用户的输入内容使用js-xss进行编码转义,便达到了防止用户输入恶意脚本的效果,但是我们能确保向服务器提交的数据一定是通过网站前端正常逻辑进行提交的吗?显然不能,如果黑客通过分析浏览器向服务器提交留言请求的Http数据包格式,手工构造向服务器留言的Http请求数据包,那就绕过了网站前端的XSS过滤逻辑,这里我们模仿黑客通过PostMan来向网站提交留言:不出所料,留言提交成功了,打开网站前端,可以看到XSS Payload成功执行。前端绕过问题,是一个具有普遍性的安全问题,这里安全不仅仅是指网络攻防安全,更多的情况是发生在业务安全中。作者曾接手过一个项目就是这样,系统中有一个类似消费转账的功能,既然是消费,那余额肯定应该是越消费越少,最起码消费金额不能是负数。网站前端对消费金额进行了数字,正负数和金额范围的校验,从而保证用户的消费金额一定是一个合法有效的数字。但是后端接收订单的接口却没有对消费金额的合法性进行判断,导致用户可以绕过前端向接口提交负数的消费金额的问题,用户的余额可以越花越多!虽然这个业务安全漏洞没有被非法利用,但是却为档次参与该项目的开发人员敲响了警钟,一切我们无法保证来源的输入,都应该校验。回归XSS防御的主题,既然我们不能依赖前端过滤,那还有必要在前端进行XSS的防御吗?作者认为,还是有必要的,一方面可以防御DOM Base类型的XSS;另一方面,从网站业务的角度来说,在前端对用户的输入进行合法性校验,可以避免合法用户误操作的问题,在一定程度减轻了服务器的请求压力。7F依赖Thymeleaf模板作者在开发该网站的过程中使用了thymeleaf模板作为前端view层,那么我们一起来看看thymeleaf对于XSS的防御效果如何。这里前端不再通过ajax异步请求的方式获取历史留言,而是采用务器端渲染,直接使用ModeAndView向页面传值:现在进行XSS攻击测试,依然使用alert?XSS Payload进行留言,可以发现XSS并没有触发执行,反而被按照正常的字符串显示出来了,打开控制台,可以发现XSS被转义了:thymeleaf模板在对th:text标签进行渲染的时候,默认对于特殊字符进行了转义处理,这很符合“Security by Default”的安全原则。但是thymeleaf同时也提供了不转义的文本标签th:utext,使用th:utext将会按照数据原有的格式进行渲染,在页面拥有XSS漏洞情况下,依然会有HTML代码拼装的问题,所以这里需要大家在开发与审计过程中要多多留意,能不使用th:utext的地方尽量不要使用,如果必须使用,也要结合具体业务综合考虑此处XSS安全防御的问题。即便如此,作者还是认为将安全问题交由公用框架还是相对靠谱的SDL(安全开发生命周期),结合当下很多公司采用前后端完全分离的技术框架,您可能会问,如果我们没有使用模板怎么办?前端可以考虑使用angular、vue等框架来构建。总之,一个原则就是尽量依赖专家代码(虽然并不能百分百保证安全)。8FOWASP ESAPI企业级防护OWASP中的ESAPI项目是专为解决web应用程序安全问题的开源项目,是由安全专家写的专家代码,很多著名公司都使用了ESAPI来为网站进行安全加固,ESAPI并不仅仅只提供了对java语言的支持,还提供了很多其他语言的版本库,但对java语言的支持是最完善的。java开发者可以很容易的将ESAPI应用入现有的项目。ESAPI Java项目大多以静态方法的方式提供接口,这里我们使用ESAPI的encoder模块对留言的输入进行编码转义:在实际项目中这样处理会存在一个问题,我们持久化的数据实际是被转义处理后的数据,并非用户的实际输入数据,会造成存储与实际输入不一致的问题,因此可以不在留言输入的接口中做编码,可以在输出接口中对留言做编码,也同样达到了防御XSS的效果,并且保证了数据的一致性。此外,ESAPI还有很多有用的解决web安全的功能,作者以后会陆续推出有关ESAPI使用的文章。9F关于富文本编辑器的处理在网站的一些业务需求中,往往需要允许用户上传具?有自定义样式的文本,例如广告、文章、公告等,而这些具有样式的文本就是富文本,实际上富文本就是一段HTML代码,因此浏览器可以无缝对其提供支持,为用户提供了极大的方便。但富文本的特性也使其成为了XSS攻击的重灾区,因此必须对其防范。关于富文本的XSS防范,我们不能简单的采用前文的方法将输入进行转义,否则转以后的富文本将无法被浏览器渲染。那应该这么办?处理富文本时,要严禁对任何JavaScript事件的支持的,因为在一般情况下,富文本的展示中不应该包含JavaScript事件这种动态效果。此外我们应考虑将一些敏感标签过滤掉,例如<script>、<iframe>、<base>、<form>等不应该出现在富文本中的HTML标签。此外在富文本中对于CSS的过滤也是件很麻烦的事情,需要检查其中是否包含恶意代码。对于威胁的监测,我们需要借助Html解析来进行识别过滤,好在对于富文本的防御过滤,OWASP开源了一个非常棒的项目Anti-Samy:在OWASP ESAPI中的validator模块下?HTMLValidationRule?便是直接引用了AntiSamy,因此如果项目中已近引用了ESAPI,那么可以很方便的对富文本进行过滤:10F?关于源码应广小伙伴的要求,本文所涉及的示例代码都已上传至公众号专用代码仓库,看一遍不如写一遍,有兴趣的小伙伴可以去阅读原文去下载。此外关于XSS的攻击,还有很多绕过技巧,感兴趣的小伙伴可以阅读以下文章:XSS学习笔记【一】XSS学习笔记【二】小结作为抛砖引玉之文,本文并无法覆盖到了XSS防御的方方面面,意在让web开发者重视XSS漏洞的问题。具体的防护方案还需要考虑系统的方方面面,此外,本文只探讨了基于HTML页面的存储型XSS的攻击原理,XSS攻击并不仅仅只发生在html页面中,CSS、Flash等也有可能存在XSS漏洞,相对于HTML更加隐蔽,不易被发现,限于本文篇幅已经过长,剩余的内容将会在以后的文章中来展现,尽情支持和与关注。
别人都不愿意告诉你的Chrome插件,你用过了几个?
为什么喜欢Chrome呢?启动速度快,对各个平台支持都比较友好,书签等服务可以同步(前提需要vpn)。插件资源丰富;自己开发插件方便。开发工具强大,可与firebug媲美。常用的Chrome插件1. ?新媒体管家这款插件对做自媒体的小伙伴来说简直就是福利,为每天编辑文章节约了大量的时间。2. ?Markdown Here这款插件可以帮我们快速的把Markdown语法转换成HTML代码,而且可以自定义样式,简直是太方便了。3. Hunter这款插件小伙伴们也许有点陌生,它能帮我们快速的找到想要的邮件地址。4. ?Axure RP这款插件对于做开发和产品应该不会陌生,它帮助我们快速浏览产品需求文档。5. Gliffy Diagrams流程图制作必不可少的插件,支持离线,还支持保存到Google Drive。6. JSON Editor和JSONView?对于做开发的小伙伴来说,这两款插件太有用了,在查看JSON的时候太有用了。7. Octotree我们需要在github上面查看源码的时候,它树形的方式展示出来,帮我们节约了大量的时间,能让我们快速的定位代码。8. OneTab这款插件节省高达95%的内存,并减轻标签页混乱现象。9. Postman在开发API的时候,这款插件真的太酸爽了,帮我们快速的测试API,并且还支持在现同步功能。10. SwitchySharp这款插件轻松快捷地管理和切换多个代理设置。11. SimilarWeb可以快速帮我们查看真实的深入网站参与、流量来源和网站排名信息。12. Tampermonkey它可以说是一个伟大的Chrome扩展,它的脚步文件就是它的魅力所在,它丰富的脚步,帮我们解决了大量的繁杂的web交互工作。13. Vimium一款以模拟VIM编辑器的操作方式来操作Chrome的谷歌浏览器插件,让我们这些熟悉VIM的玩家可以好好过把瘾。14. Vysor对于Android开发者来说,它算是一个福利,能够在 Chrome 里通过 USB 直接控制 Android 设备,无需 root。15. AdBlock帮助我们屏蔽烦人的广告不可缺少的神器,也可以根据自己的需求设置相应的白名单和黑名单。16. Adblock for Youtube对于我这种喜欢逛Youtube的人来说,这款插件挺不错的。17. 惠惠购物助手对于喜欢剁手的朋友来说这款插件太有用了,可以帮我们分析一款产品的价格走势图。18. Wappalyzer它是一款可以分析不同网站所使用的各种技术的插件,检测还是比较准确。声明其实好用的插件也许还有很多,我只是根据自己使用频率最高的这几款插件做了推荐,如果小伙伴们有更好用的插件,欢迎留言推荐。
初学者都能学会的ElasticSearch入门实战《玩转ElasticSearch 2》-3
四、倒排索引倒排索引是由单词词典、倒排列表两部分组成,单词词典记录的所有文档的单词,记录单词倒排列表的关联关系倒排列表记录了单词对应的文档结合,由倒排索引项组成,分别为文档ID、词频TF、位置、偏移案例:ElasticSearch可以为json文档中的每个字段设置自己的倒排索引,也可以指定某些字段不做倒排索引若不做倒排索引,虽可以节省存储空间,但字段无法被搜索五、使用Analyzer进行分词首先你得知道什么是分词:Analysis把全文本转换为一系列单词的过程叫做分词Analysis通过Analyzer实现的,可以通过ElasticSearch内置的分析器、或使用定制分析器分词器除了写入时转换此条,查询query时也需要用相同的分析器对查询语句进行分析案例:ElasticSearch kaka通过分词就转化为 elasticSearch和kaka,这里需要注意的是通过分词转化后把单词的首字母变为小写Analyzer的组成Character Fiters :针对原始文本处理,例如去除htmlTokenizer : 按照规则切分单词Token Filter : 将切分的单词进行加工,转为小写,删除stopwords并增加同义词ElasticSearch的内置分词器# Standard Analyzer - 默认分词器,按词切分,小写处理
# 只做单词分割、并且把单词转为小写
get _analyze
{
"analyzer":"standard",
"text":"If you don't expect quick success, you'll get a pawn every day"
}
# Simple Analyzer - 按照非字母切分(符号被过滤),小写处理
# 按照非字母切分例如字母与字母之间的——,非字母的都被去除例如下边的 2
get _analyze
{
"analyzer" :"simple",
"text":"3 If you don't expect quick success, you'll get a pawn every day kaka-niuniu"
}
# Whitespace Analyzer - 按照空格切分,不转小写
# 仅仅是根据空格切分,再无其它
get _analyze
{
"analyzer":"whitespace",
"text":"3 If you don't expect quick success, you'll get a pawn every day"
}
# Stop Analyzer - 小写处理,停用词过滤(the,a, is)
# 按照非字母切分例如字母与字母之间的——,非字母的都被去除例如下边的 2
# 相比Simple Analyze,会把the,a,is等修饰性词语去除
get _analyze
{
"analyzer":"stop",
"text":"4 If you don't expect quick success, you'll get a pawn every day"
}
# Keyword Analyzer - 不分词,直接将输入当作输出
# 不做任何分词,直接把输入的输出,假如你不想使用任何分词时就可以使用这个
get _analyze
{
"analyzer":"keyword",
"text":"5 If you don't expect quick success, you'll get a pawn every day"
}
# Patter Analyzer - 正则表达式,默认\W+(非字符分隔)
# 通过正则表达式进行分词,默认是\W+,非字符的符号进行分割
get _analyze
{
"analyzer":"pattern",
"text":"6 If you don't expect quick success, you'll get a pawn every day"
}
# Language 一提供了30多种常见语言的分词器
# 通过不同语言进行分词
# 会把复数转为单数 ,会把单词的ing去除
get _analyze
{
"analyzer":"english",
"text":"7 If you don't expect quick success, you'll get a pawn every day kakaing kakas"
}
# 中文分词器
# 这个需要安装
# 执行: ./bin/elasticsearch-plugin install analysis-icu
# 重启:nohup ./bin/elasticsearch > /dev/null 2>&1 &
get _analyze
{
"analyzer":"icu_analyzer",
"text":"你好,我是咔咔"
}
其它中文分词用的最多的IK分词,只是自定义词库,支持热更新分词字典清华大学自然语言一套分词器Thulac六、Search Api通过Url query 实现搜索例如:get /movies/_search?q=2012&df=title&sort=year:descq:指定查询语句,使用Query String Syntaxdf:查询字段,不指定时,会对所有字段进行查询sort:排序、from和size用于分页Profile:可以查看查询是如果被执行的指定字段查询、泛查询指定字段查询就是加上df即可、泛查询什么都不加,看案例通过下图右侧信息可得知,指定字段查询的是title中存在2012的数据同样也可以这样来写指定字段查询get /movies/_search?q=2012&df=title
{
"profile":true
}通过下图右侧可得知,泛查询则是在所有字段中查找存在2012的数分组与引号查询若你查询值为Beautiful Mind 则等效于Beautiful OR Mind ,类似于MySQL中的or语句,意思为查询的字段中包含 Beautiful 或者 Mind 都会被查询出来若你查询值为"Beautiful Mind" 则等效于Beautiful AND Mind ,类似于MySQL中的and语句,意思为查询的字段中不仅要包含Beautiful 而且还需要包含 Mind ,跟MySQL中不同的是顺序也不能变注意:这里你乍一眼看过去没啥区别, 其实区别就在于有无引号# PhraseQuery
# 需要字段title中存在beautiful 和 mind,并且两者的顺序不能乱
# "description" : """title:"beautiful mind""""
get /movies/_search?q=title:"Beautiful Mind"
{
"profile":"true"
}
# TermQuery
# 需要字段title中出现beautiful 或 mind 都可以
# "type" : "BooleanQuery",
# "description" : "title:beautiful title:mind",
get /movies/_search?q=title:(Beautiful Mind)
{
"profile":"true"
}
布尔操作可以使用AND / OR / NOT 或者 && / || / ! 这里你会发现使用的都是大写,+表示must(必须存在),-表示not mast(必须不存在)接下来看案例# title 里边必须有beautiful 和 mind
# "description" : "+title:beautiful +title:mind"
get /movies/_search?q=title:(Beautiful AND Mind)
{
"profile":"true"
}
# title里边包含beautiful 必须没有mind
# "description" : "title:beautiful -title:mind"
get /movies/_search?q=title:(Beautiful NOT Mind)
{
"profile":"true"
}
# title里包含beautiful ,必须也包含mind
# "description" : "title:beautiful +title:mind"
get /movies/_search?q=title:(Beautiful %2BMind)
{
"profile":"true"
}
范围查询、通配符查询、模糊匹配# year年份大于1996的电影
# 注意一下[] 为闭区间 {}为开区间
# "description" : "year:[1997 TO 9223372036854775807]"
get /movies/_search?q=year:>1996
{
"profile":"true"
}
# title 中存在b的数据
# "description" : "title:b*"
get /movies/_search?q=title:b*
{
"profile":"true"
}
# 对于模糊匹配还是非常有必要的,因为会存在一起用户会输错单词,我们就可以给做近似度匹配
# "description" : "(title:beautiful)^0.875"
get /movies/_search?q=title:beautifl~1
{
"profile":"true"
}
初学者都能学会的ElasticSearch入门实战《玩转ElasticSearch 2》-2
三、文档的基本CRUD操作create 一个文档支持自动生成文档ID和指定文档ID两种方式通过调用post /movies/_doc 系统会自动生成文档ID使用http put movies/_create/1 创建时,url中显示指定_create ,如果该id的文档已经存在,操作失败Index 文档Index和Create区别在于,如果文档不存在,就索引新的文档。否则现有文档会被删除,新的文档被索引并且版本信息+1可以看到之前的文档已经被更新为最新的niuniu,是因为之前就存在文档id=1,并且能看到版本信息也加了1update 文档update方法不会删除原有文档,而是实现真正的数据更新get 一个文档检索文档找到,返回状态码200,文档元信息,这里需要注意一下版本信息,同一个id的文档,即被删除版本号也会不断增加找不到文档,返回状态码404Bulk Api支持在一次Api调用中,对不同的索引进行操作,支持index、create、update、delete可以在url中指定index,也可以在请求的payload中进行操作中单条操作失败,不会影响其它继续操作,并且返回结果包括了每一条操作执行的结果多索引bulk批量操作案例:post _bulk
{"index":{"_index" : "test1","_id" : "1"}}
{"name":"kaka_bulk"}
{"delete":{"_index":"test1","_id":"2"}}
{"create":{"_index":"test2","_id":"3"}}
{"name":"kaka_create"}
{"update":{"_id":"1","_index":"test1"}}
{"doc":{"name":"kaka_bulk"}}
返回结果:{
"took" : 165,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "test1",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 201
}
},
{
"delete" : {
"_index" : "test1",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"result" : "not_found",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1,
"status" : 404
}
},
{
"create" : {
"_index" : "test2",
"_type" : "_doc",
"_id" : "3",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1,
"status" : 201
}
},
{
"update" : {
"_index" : "test1",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "noop",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"status" : 200
}
}
]
}
这里需要大家注意:bulk api 对json语法有严格的要求,每个json串不能换行,只能放一行,同时一个json和另一个json串之间必须有一个换行。单索引bulk批量操作如果操作的是同一个索引时,bulk语句还可以变化为以下方式post test1/_bulk
{"index":{"_id" : "1"}}
{"name":"kaka_bulk"}
{"delete":{"_id":"2"}}
{"create":{"_id":"3"}}
{"name":"kaka_create"}
{"update":{"_id":"1"}}
{"doc":{"name":"kaka_bulk"}}
单条的返回结果可以自己尝试一下,可以看到单索引bulk跟多索引bulk之间的区别显而易见。bulk size的最佳大小bulk request 会加载到内存里,如果太大的话,性能反而会下降,因此需要不断尝试最佳的bulk size,大小最好控制在5~15MB即可,至于条数需要根据当下数据量再调整。批量读取_mget道理跟MySQL都一样,只要是批量在一定合理的范围内都会减少网络连接所产生的开销,从而提高性能需要注意批量获取每个json之间是需要逗号隔开的,否则会报json解析异常get /_mget
{
"docs": [
{"_index":"test","_id":"1"},
{"_index":"movies","_id":"2"}
]
}
批量搜索_msearchpost kibana_sample_data_ecommerce/_msearch
{}
{"query":{"match_all":{}},"size":1}
{"index":"kibana_smaple_sample_data_flights"}
{"query":{"match_all":{}},"size":1}