RocketMq源码解析 (三)
目录
这一期还是接着分析MixAll这个类的功能
object2Porperties(final Object object)
public static Properties object2Porperties(final Object object){
Properties properties=new Properties();
Field[] fields=object.getClass().getDeclaredFields();
for(Field field:fields){
if(Modifier.isStatic(field.getModifiers())){
String name=field.getName();
if(!name.startsWith("this")){
Object value=null;
try {
field.setAccessible(true);
value=field.get(object);
}catch (IllegalArgumentException | IllegalAccessException e){
e.printStackTrace();
}
if(value!=null){
properties.setProperty(name,value.toString());
}
}
}
}
return properties;
}
在上一期的基础上,这一段就很容易理解了,把对象的静态字段且不是以this开头的装换为Properties,例如
String static IsOpen="test"
转换后就变成了key-value的形式,原理上一期已经解释过了。进入下一个函数
properties2Object(final Properties properties,final Object object)
/**
* 将参数注入到类的set方法中,完成参数的初始化
* @param properties
* @param object
*/
public static void properties2Object(final Properties properties,final Object object) {
Method[] methods = object.getClass().getMethods();
for (Method method : methods) {
String mn = method.getName();
if (mn.startsWith("set")) {
try {
//判断是否以set开头
String tmp = mn.substring(4);
String first = mn.substring(3, 4);
//setArgs转换为args
String key = first.toLowerCase() + tmp;
String property = properties.getProperty(key);
if (property != null) {
//获取方法的参数列表
Class<?>[] pt = method.getParameterTypes();
if (pt != null && pt.length > 0) {
//第一个参数,
String cn = pt[0].getSimpleName();
Object arg = null;
if (cn.equals("int") || cn.equals("Integer")) {
arg = Integer.parseInt(property);
} else if (cn.equals("long") || cn.equals("Long")) {
arg = Long.parseLong(property);
} else if (cn.equals("double") || cn.equals("Double")) {
arg = Double.parseDouble(property);
} else if (cn.equals("boolean") || cn.equals("Boolean")) {
arg = Boolean.parseBoolean(property);
} else if (cn.equals("float") || cn.equals("Float")) {
arg = Float.parseFloat(property);
} else if (cn.equals("String")) {
arg = property;
} else {
continue;
}
method.invoke(object, arg);
}
}
} catch (Throwable ignored) {
}
}
}
}
这个方法的作用很明显,把属性注入到对应的对象中,一个对象中的属性值我们都回提供set方法来注入属性,我们只要把所有set开头的方法
提取出来即可,object.getClass().getMethods(); 则是用到了java的反射机制,把对象的所有
方法放到Method对象里,我们看看这个方法到返回了什么
而且还有没有显示声明,从父类继承而来的方法,比如notify等,而set标准写法为setProperties(),对应的属性名为properties,这也是语言规范,故需要大小写转换,接着获取对应方法的参数列表。如图,会显示方法所有的参数,对于set方法来说只有一个参数,且参数类型应当和字段类型相同,
至于getSimpleName 我们看看源码
int length = simpleName.length();
int index = 1;
while (index < length && isAsciiDigit(simpleName.charAt(index)))
index++;
// Eventually, this is the empty string iff this is an anonymous class
return simpleName.substring(index);
}
不难发现就是返回最后一个 . 后面的字符串,自然就是我们需要的属性类型,然后就是把参数中key(属性名)对应的值类型转换为字段需要的类型从而进行注入,这里不难发现转换只支持五大基本数据类型和对应的包装类加上String,说明注入的都是一些基本类型。最后就是invoke了这也是java反射机制中一个强大的功能,这里是吧object中对应的method参数注入为arg并执行,想要深入了解可以看一看java反射之Method的invoke方法实现,其详细解释了其实现原理。
getLocalInetAddress()
/**
* 判断两个属性对象是否相等
* @param p1 属性1
* @param p2 属性2
* @return 是否相等
*/
public static boolean isPropertiesEqual(final Properties p1, final Properties p2) {
return p1.equals(p2);
}
/**
* 获取本机的网络地址
* @return 本机网络地址
*/
public static List<String> getLocalInetAddress(){
List<String> inetAddressList=new ArrayList<String>();
try {
//获取本机所有的网络接口
Enumeration<NetworkInterface> enumeration=NetworkInterface.getNetworkInterfaces();
while(enumeration.hasMoreElements()){
NetworkInterface networkInterface=enumeration.nextElement();
Enumeration<InetAddress> addres=networkInterface.getInetAddresses();
while (addres.hasMoreElements()){
//主机名传入
inetAddressList.add(addres.nextElement().getHostName());
}
}
}catch (SocketException e){
throw new RuntimeException("get local inet Address fail",e);
}
return inetAddressList;
}
/**
* 判断是否为本机地址
* @param address 网络地址
* @return
*/
public static boolean isLocalAddr(String address){
for(String addr:LOCAL_INET_ADDRESS){
if(address.contains(addr)){
return true;
}
}
return false;
}
这里用到了NetworkInterface.getNetworkInterfaces() 用来获取机器上所有的网络接口,在本机上执行结果如下,估计是把虚拟网络接口也算进去了。作用暂时未知。
后续则是一个判断是否为本机地址的函数,作用未知。
compareAndIncreaseOnly(final AtomicLong target,final long value)
/**
* 确保target只能增长
* @param target 目标
* @param value 值
* @return 是否增长
*/
public static boolean compareAndIncreaseOnly(final AtomicLong target,final long value){
long prev=target.get();
while (value > prev){
boolean update=target.compareAndSet(prev,value);
if(update){
return true;
}
prev=target.get();
}
return false;
}
这个函数用来确保target只能增长的,这里解决了CAS的一个漏洞,也就是ABA问题,确保增长的情况下ABA问题则不存在。
获取本机地址,这里不再详细介绍
/**
* 获取本机地址
* @return 机器名
*/
public static String localhost(){
try {
return InetAddress.getLocalHost().getHostName();
}catch (UnknownHostException e){
throw new RuntimeException("InetAddress java.net.InetAddress.getLocalHost() throws UnknownHostException"
+ FAQURL.suggestTodo(FAQURL.UNKNOWN_HOST_EXCEPTION),
e);
}
}
humanReadableByteCount(long bytes,boolean si)
/**
* 把字节数装换为可以读懂的单位,
* @param bytes 字节数
* @param si 单位为1000还是1024
* @return
*/
public static String humanReadableByteCount(long bytes,boolean si){
int unit = si ? 1000 : 1024;
if (bytes < unit)
return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
这里有一个细节,形参的修饰符没有final了,所以应该不是同一个人写的,而这个作用也很明显,就是把字节转换成合适的单位。有几个细节需要注意一个是大小写k,然后就是kiB和kB,这是国际上关于计算机单位的问题,事实上1kB=1000byte 1KiB=1024byte,有时候表达严谨而是用kiB单位。
list2Set(List< String > values)
/**
* list装换为set
* @param values
* @return
*/
public Set<String> list2Set(List<String> values) {
Set<String> result = new HashSet<String>();
for (String v : values) {
result.add(v);
}
return result;
}
/**
* set装换为list
* @param values
* @return
*/
public List<String> set2List(Set<String> values) {
List<String> result = new ArrayList<String>();
for (String v : values) {
result.add(v);
}
return result;
}
这两个没什么好说的,两种类型的装换。
至此,MixAll的所有函数分析完毕,大概的印象应该有了,具体功能差不多也知道了,就是把配置文件类似于XML这样的进行类的初始化,就像tomcat配置文件一样,而联系MixAll这个类名,不难想象可能是把所有的相关文件装配到系统中,具体我们下一期将对这个类进行测试。分析其可以改进的地方。