那些年,我们踩过的 Java 坑(落魄的java开发)

作者|常毅
来源|阿里巴巴中间件(ID: aliware _ 2018)
图|东方IC
中国有句古话叫‘事不过三’,意思是一个人同样的错误,一次两次三次都可以原谅,三次以上就不能原谅了。有人指出,这个“三”是虚数,用来指代很多次,所以‘无非三’不包括“三”。至于‘无非三件事’,套餐不包括‘三’,可能跟大家的底线有关系。属于哲学范畴,不在本文讨论范围。
写代码也是如此。一样的代号“坑”。第一步叫‘成长经历’,第二步叫‘加深印象’,第三步叫‘心胸狭窄’,第三步叫‘不可救药’。本文作者总结了一些代码坑,描述了问题现象,分析了问题,并给出了避免代码坑的方法。希望大家在日常编码中遇到这种代码坑时能提前避开。
对象比较方法JDK1.7提供了Objects.equals方法,非常方便比较对象,有效避免繁琐的空指针检查。
1.1.问题现象在JDK1.7之前,判断短整型、整型、长整型包数据类型是否等于常量时,我们一般这样写:
short short value=(short)12345;system . out . println(short value==12345);//true system . out . println(12345==short value);//true integer int value=12345;system . out . println(int value==12345);//true system . out . println(12345==int value);//true long long value=12345 l;system . out . println(long value==12345);//true system . out . println(12345==long value);//true从JDK1.7开始,提供Objects.equals方法,推荐函数式编程。代码更改如下:
short short value=(short)12345;system . out . println(objects . equals(short value,12345));//false system . out . println(objects . equals(12345,short value));//false integer int value=12345;system . out . println(objects . equals(int value,12345));//true system . out . println(objects . equals(12345,int value));//true long long value=12345 l;system . out . println(objects . equals(long value,12345));//false system . out . println(objects . equals(12345,long value));//false为什么用Objects.equals方法直接替换==会导致不同的输出结果?
1.2.问题分析通过反编译第一段代码,我们得到语句\ ‘ system . out . println(short value==12345);“”的字节码指令如下:
7 get static Java . lang . system . out : Java . io . printstream[22]10 aload _ 1[short value]11 invokevirtual Java . lang . short . short value : short[28]4 sipush 1234517 if _ icmp ne 2420 iconst _ 121 goto 2524 iconst _ 025 invokebirtualjava . io . printstream . println(Boolean)3360 void[32]原来编译器会),相当于编译器自动强制常量的数据类型转换。
为什么编译器采用Objects.equals方法后不自动强制转换常量的数据类型?通过反编译第二段代码,我们得到语句\ ‘ system . out . println(objects . equals(short value,12345));“”的字节码指令如下:
7 get static java . lang . system . out : Java . io . printstream[22]10 aload _ 1[short value]11 si push 1234514 invoke static Java . lang . integer . value of(int): Java . lang . integer[28]17 invoke static Java . util . objects . equals(Java . lang . object,Java .lang . object)3360 boolean[33]20 invokevirtual Java . io . printstream . println(boolean)3360 void[39]原来根据字面意思,编译器认为常量12345默认的基本数据类型是int,所以会自动转换成包装器数据类型Integer。
在Java语言中,整数的默认数据类型是int,小数的默认数据类型是double。
让我们分析Objects.equals方法的代码实现:
public static boolean equals(Object a,Object b) {return (a==b) || (a!=a .等于(b));}其中语句“a.equals(b)”会使用Short.equals方法。
Short.equals方法的代码实现如下:
public boolean equals(Object obj){ if(obj instance of Short){返回值==((短)obj).短值;}返回false}通过代码实现分析:对应语句\ ‘系统。出去。println(对象。equals(短值,12345));\’,因为对象。等于的两个参数对象类型不一致,一个是包装数据类型短,另一个是包装数据类型整数,所以最终的比较结果必然是错误。同样,语句系统。出去。println(对象。equals(int value,12345));因为对象。等于的两个参数对象类型一致,都是包装数据类型整数且取值一样,所以最终的比较结果必然是没错。
1.3.避坑方法1、保持良好的编码习惯,避免数据类型的自动转化。为了避免数据类型自动转化,更科学的写法是直接声明常量为对应的基本数据类型。
第一段代码可以这样写:
空头空头值=(空头)12345;系统。出去。println(short value==(short)12345);//真系统。出去。println((short)12345==短值);//真整数int值=12345;系统。出去。println(int value==12345);//真系统。出去。println(12345==int value);//true long long值=12345 l;系统。出去。println(long值==12345 l);//真系统。出去。println(12345 l==long值);//真第二段代码可以这样写:
空头空头值=(空头)12345;系统。出去。println(对象。equals(短值,(短)12345));//真系统。出去。println(对象。等于((短)12345,短值));//真整数int值=12345;系统。出去。println(对象。equals(int value,12345));//真系统。出去。println(对象。equals(12345,int值));//true long long值=12345 l;系统。出去。println(对象。equals(长值,12345 l));//真系统。出去。println(对象。equals(12345 l,long值));//true2,借助开发工具或插件,及早地发现数据类型不匹配问题。
在黯然失色的问题窗口中,我们会看到这样的提示:
不太可能的等于参数类型: int似乎与短的无关不太可能的等于参数类型:短似乎与(同Internationalorganizations)国际组织无关不太可能的等于参数类型: int似乎与长的无关不太可能的等于参数类型:长似乎与(同Internationalorganizations)国际组织无关通过查找疯狂的插件扫描,我们会看到这样的警告:
在xxx中调用Short.equals(整数).Xxx.main(String[])【最恐怖(1),可信度高】在Xxx中调用Integer.equals(短整型).Xxx.main(String[])【最恐怖(1),可信度高】在Xxx中调用Long.equals(整数).Xxx.main(String[])【最恐怖(1),可信度高】在Xxx中调用Integer.equals(长整型).XXX。main(String[])[最恐怖(1),可信度高]3、进行常规性单元测试,尽量把问题发现在研发阶段。
“勿以善小而不为”,不要因为改动很小就不需要进行单元测试了,往往病菌都出现在自己过度自信的代码中。像这种问题,只要进行一次单元测试,是完全可以发现问题的。
三元表达式拆包三元表达式是爪哇编码中的一个固定语法格式:”条件表达式?表达式1 :表达式2 “。三元表达式的逻辑为:”如果条件表达式成立,则执行表达式1 ,否则执行表达式2 “。
2.1.问题现象布尔条件=假;double value 1=1.0 double value 2=2.0 double value 3=;双结果=条件值1 *值2 :值3;//抛出空指针异常当条件表达式情况等于错误的时,直接把两倍对象价值3赋值给两倍对象结果,按道理没有问题呀,为什么会抛出空指针异常(PointerException)?
2.2.问题分析通过反编译代码,我们得到语句\ ‘双结果=条件值1 *值2 :值3;\’的字节码指令如下:
17 iload _ 1[条件]18 ifeq 3321 aload _ 2[值1]22 invokevirtual Java . lang . Double . Double value : Double[24]25 aload _ 3[值2]26 invokevirtual Java . lang . Double . Double value : Double[24]29 dmul 30 goto 3833 aload 4[值3]35 invokevirtual Java . lang . Double . Double value : Double[24在第35行,调用double对象value3的Doublevalue方法。此时,由于value3为空对象,调用doubleValue方法必然会抛出空指针异常。但是,为什么要将空对象value3转换为基础数据类型double呢?
查阅相关资料得到三元表达式的类型转换规则:
如果两个表达式类型相同,则返回值类型为该类型;
如果两个表达式类型不同,但类型不能转换,则返回值类型为对象类型;
如果两个表达式是不同的类型,但类型可以转换,则先将封装数据类型转换为基本数据类型,然后根据基本数据类型的转换规则计算后返回基本数据类型double(根据规则分析byte,表达式1(value1 * value2)返回封装数据类型Double。根据三元表达式的类型转换规则,最终返回的类型是基本数据类型double。因此,当条件表达式条件等于false时,需要将空对象value3转换为底层数据类型double,于是调用value3的doubleValue方法并抛出空指针异常。
以下案例可用于验证三元表达式的类型转换规则:
布尔条件=假;Double value1=1.0DDouble value2=2.0Ddouble value 3=;整数值4=;//返回类型为Double,不抛出空指针异常。double result 1=condition value 1 : value 3;//如果返回类型为double,则会引发空指针异常。double result 2=condition value 1 : value 4;//返回类型为double,不抛出空指针异常。双重结果3=!条件值1 *值2 :值3;//如果返回类型为double,则会引发空指针异常。双结果4=条件值1 *值2 :值3;
2.3.避坑方法1。尽量避免使用三元表达式,可以用if-else语句代替。如果三元表达式中有算术计算和打包数据类型,可以考虑改用if-else语句。按如下方式重写代码:
布尔条件=假;Double value1=1.0DDouble value2=2.0Ddouble value 3=;双重结果;if(条件){ result=value1 * value2}否则{ result=value3}2.尽量使用基本数据类型,避免数据类型的自动转换。
如果三元表达式中有算术计算和打包数据类型,可以考虑改用if-else语句。按如下方式重写代码:
布尔条件=假;
double value1=1.0D
double value2=2.0D
double value3=3.0D
double result=条件值1 *值2 :值3;
3.进行覆盖单元测试,并尝试在R & ampd阶段。
像这样的问题,只要写一些单元测试用例,进行一些覆盖测试,就可以提前发现。
泛型对象赋值Java generics是JDK1.5中引入的新特性,其本质是参数化类型,即数据类型作为参数使用。
3.1.问题现象在做用户数据分页查询时,因为笔误写了以下代码:
1、PageDataVO.java:
/* *分页数据VO类*/@ getter @ setter @ tostring @ noargsconstructor @ allargsconstructor公共类pagedatavo {/* *总数*/private Long total count;/* *数据列表*/个人分发名单数据列表;}2、UserDAO.java:
/* *用户DAO接口*/@ mapper公共接口userdao {/* * Count用户数*/public long Count User(@ param(\ ‘ query \ ‘)userqueryvo query);/* *查询用户信息*/公共列表查询用户(@ param(\ ‘ Query \ ‘)userqueryvo Query);}3、UserService.java:
/* *用户服务类*/@ service public class userservice {/* * userdao */@ autowired private userdao userdao;/* *查询用户信息*/public pagedatavo查询用户(userqueryvo query){ list datalist=;long total count=userdao . count user(查询);if(objects . non(total count)total count . compare to(0L)0){ dataList=userdao . query user(query);}返回新的PageDataVO(totalCount,dataList);}}4、UserController.java:
/* *用户控制器类*/@ controller @ request mapping(\ ‘/user \ ‘)公共类用户控制器{/* *用户服务*/@ autowiredprivate userserviceuserservice;/* *查询用户*/@ response body @ request mapping(value=\ ‘/query \ ‘,method=requestmethod.post)公共结果查询用户(@ requestbodyuserqueryvo query){ pagedatavopagedata=userservice . query user(query);返回result builder . success(pageData);}}上面的代码没有任何编译问题,只是将UserDO中的一些机密字段返回到了前端。细心的读者可能已经发现,UserService类的queryUser方法的语句“return new pagedatavo (total count,datalist)”;\ ‘,我们将列表对象dataList赋给PageDataVO的列表字段dataList。
问题是:为什么开发工具不报告编译错误?
3.2.问题分析由于历史原因,参数化类型和原始类型需要兼容。我们以ArrayList为例,看看它是如何兼容的。
以前的写作:
ArrayList list=new ArrayList
现在的写作方式:
ArrayList list=new ArrayList
考虑到与之前代码的兼容性以及各种对象引用之间的值传递,不可避免会出现以下情况:
//第一种情况ArrayList list1=new ArrayList//第二种情况ArrayList list2=new ArrayList所以Java编译器兼容以上两种类型,不会出现编译错误,但是会有编译报警。不过我的开发工具编译的时候真的没有报警。
分析我们遇到的问题,实际上同时撞上了两种情况:
1.将列表对象分配给列表符合第一种情况;
2.将PageDataVO对象赋给PageDataVO符合第二种情况。
最终的结果是我们神奇地将List对象赋给了List。
问题的根源在于,当我们初始化PageDataVO对象时,我们不需要强制的类型检查。
3.3.避坑方法1。初始化通用对象时,建议使用菱形语法。在《阿里巴巴 Java 开发手册》中,有这样一条推荐规则:
[推荐]在JDK7和更高版本中收集泛型定义时,使用菱形语法或全部省略。注意:diamond generic,即diamond,直接用来引用前面指定的类型。示例:
//diamond HashMap user cache=new HashMap(16);//All-省略ArrayList users=new ArrayList(10);事实上,在初始化通用对象时,不建议完全省略。这将避免类型检查,从而导致上述问题。
初始化泛型对象时,建议对以下代码使用菱形语法:
返回新的PageDataVO(totalCount,dataList);
现在,在Eclipse的问题窗口中,我们将看到这样一个错误:
无法推断PageDataVO的类型参数
所以,我们知道我们忘记了将列表对象转换成列表对象。
2.进行单元测试时,需要对数据内容进行比较。
在单元测试中,正常运行是一个指标,但正确的数据是更重要的指标。
通用属性复制Spring的BeanUtils.copyProperties方法是一个非常有用的属性复制工具方法。
4.1.问题现象根据数据库开发规范,数据库表必须包含三个字段:id、gmt_create和gmt_modified。其中,id字段根据数据量的不同,可以是int或long类型(注意:Ali规范要求必须是long类型,这里比如允许是int或long类型)。
因此,取出这三个字段并定义一个BaseDO基类:
/* *基本DO类*/
@Getter
@Setter
@ToString
公共类BaseDO {
私有T id
私人日期gmtCreate
私人日期GMT修改;
}
为用户表定义了一个UserDO类:
/* *用户DO类*/
@Getter
@Setter
@ToString
公共类UserDO扩展BaseDO{
私有字符串名称;
私有字符串描述;
}
对于查询接口,定义了一个UserVO类:
/* * User VO class */@ getter @ setter @ tostring公共静态类User VO { Private Long ID私有字符串名称;私有字符串描述;}实现查询用户服务接口,实现代码如下:
/* *用户服务类*/@ service public class userservice {/* * userdao */@ autowired private userdao userdao;/* *查询用户*/公共列表查询用户(userqueryvo query){//查询用户信息listuserdolist=userdao . query user(query);if(collection utils . isempty){ return collections . empty list;}//转换用户列表list user volist=new ArrayList(userdolist . size);for(user do user do : user dolist){ user VO user VO=new user VO;bean utils . copy properties(user do,user VO);user volist . add(user VO);}//返回用户列表返回userVOList}}通过测试我们会发现一个问题:3354调用查询用户服务接口,没有返回用户ID的值。
[{\’description\’:\ ‘这是一个测试器。\ ‘,\’name\’:\’tester\’},]
4.2.问题分析按道理,UserDO类和UserVO类的id字段都是长型的。如果没有类型,就不能转换,所以应该可以正常赋值。尝试手动赋值。代码如下:
for(user do user do : user dolist){ user VO user VO=new user VO;user VO . setid(user do . getid);user VO . setname(user do . getname);user VO . set description(user do . get description);user volist . add(user VO);}经过测试,以上代码返回正常结果,成功返回用户ID的值。
然后是BeanUtils.copyProperties的工具方法有问题,在调试模式下运行,进入BeanUtils.copyProperties的工具方法,得到如下数据:
原来UserDO类的getId方法的返回类型不是长类型,而是被泛型简化为对象类型。下面的ClassUtils.isAssignable工具方法,确定对象类型是否可以赋给Long类型,当然会返回false,导致无法复制属性。
为什么作者不考虑‘先获取属性值,再判断是否可以赋值’?建议的代码如下:
对象值=read method . invoke(source);
if(objects . non(value)class utils . isassignable(write method . getparametertypes[0],value.getClass)) {
.//分配相关代码。
}
4.3.避坑方法1。不要盲目相信第三方工具包。任何工具包都可能有问题。在Java中,有很多第三方工具包,比如Apache的commons-lang3,commons-collections,Google的Guava,这些都是非常有用的第三方工具包。但是,不要盲目相信第三方工具包。任何工具包都可能有问题。
2.如果要复制的属性很少,可以手工编码复制。
用BeanUtils.copyProperties反映复制属性的主要优点是节省代码量,主要缺点是导致程序性能下降。因此,如果要复制的属性很少,可以手动编码进行属性复制。
3.一定要进行单元测试,比较数据内容。
写完代码后,一定要进行单元测试,对比数据内容。不要想当然地认为工具包成熟了,代码简单了,就不可能有问题。
在Java语言中设置重复数据删除,设置数据结构可以用于对象重复数据删除。常见的Set类有HashSet、LinkedHashSet等。
5.1.问题现象写一个城市辅助类,从CSV文件中读取城市数据:
/* *城市辅助类*/@ slf4jpublic类cityhelper {/* * test main方法*/public static void main(string[]args){ collection city collection=read cities 2(\ ‘ cities . CSV \ ‘);log . info(JSON . tojsonstring(city collection));}/* * read cities */public static collection read cities(string filename){ try(文件输入流=新文件输入流(filename);InputStreamReader reader=new InputStreamReader(流,\ ‘ GBK \ ‘);CSV parser parser=new CSV parser(reader,CSVFormat。default . with header)){ Set city Set=new HashSet(1024);迭代器iterator=parser.iteratorwhile(iterator . has next){ city set . add(parse city(iterator . next));}返回citySet} catch(io exception e){ log . warn(\ ‘读取所有城市异常\ ‘,e);} return Collections.emptyList}/* *解析城市*/私有静态城市parsecity (CSV record记录){ city city city=新城市;city . set code(record . get(0));city . set name(record . get(1));返回城市;}/* * city类*/@ getter @ setter @ tostring私有静态类city {/* * city code */私有字符串代码;/* *城市名*/私有字符串名;}}代码中使用了HashSet数据结构,为了避免城市数据重复,读取的城市数据强制重复。
当输入文件内容如下:
代码,名称010,北京020,广州010,北京解析后的JSON结果如下:
[{\’ code \’ 3360 \’ 010 \ ‘,\’ name \’ 3360 \ ‘北京\’},{\’ code \’ 3360 \’ 020 \ ‘,\’ name \’ 360
然而,城市“北京”没有重新排名。
5.2.问题分析向集合中添加对象时,首先集合计算要添加的对象的hashCode,根据这个值得到一个存储当前对象的位置。如果这个位置没有对象,那么集合集合认为该对象在集合中不存在,直接添加。如果位置上有对象,那么用equals方法把要加入集合的对象和位置上的对象进行比较:如果equals方法返回false,那么集合认为该对象不存在于集合中,把该对象放在对象之后;如果equals方法返回true,则认为该对象已经存在于集合中,不会将其添加到集合中。因此,需要使用hashCode方法和equals方法来确定两个元素在哈希表中是否重复。hashCode方法确定数据存储在表中的什么位置,而equals方法确定表中是否存在相同的数据。
分析以上问题,由于City类的hashCode方法和equals方法没有被覆盖,所以将采用Object类的hashCode方法和equals方法。它是这样实现的:
public native int hashCodepublic boolean equals(Object obj){ return(this==obj);}可以看出Object类的hashCode方法是一个局部方法,返回对象的地址;Object类的equals方法只比较对象是否相等。因此,对于两条相同的北京数据,由于解析时初始化的城市对象不同,hashCode方法和equals方法的值也不同,必然被集合视为不同的对象,因此不会重复。
然后,我们将重写City类的hashCode方法和equals方法,代码如下:
/* * city类*/@ getter @ setter @ tostring私有静态类city {/* * city code */私有字符串代码;/* *城市名*/私有字符串名;/* *判断等于*/@ override public boolean equals(object obj){ if(obj==this){ return true;} if(objects . is(obj)){ return false;}if (obj.getClass!=this . getclass){ return false;} return objects . equals(this . code,((City)obj)。码);}/* *哈希代码*/@ override public int hashcode { return objects . hashcode(this . code);}}再次支持测试程序,解析后的JSON结果如下:
[{\’ code \’ 3360 \’ 010 \ ‘,\’ name \’ 3360 \ ‘北京\’},{\’ code \’ 3360 \’ 020 \ ‘,\’ name \’ 360
因此,城市“北京”被重新排名。
5.3.避坑方法1。当确定数据是唯一的时,可以使用List而不是Set。当确定所分析的城市数据是唯一的时,不需要执行去重操作,并且可以直接使用列表来存储它。
list city set=new ArrayList(1024);迭代器iterator=parser.iteratorwhile(iterator . has next){ city set . add(parse city(iterator . next));}返回citySet2.当确定数据不唯一时,可以使用Map而不是Set。
当确定分析的城市数据不唯一时,需要安装城市名称进行去重,可以使用Map直接存储。为什么不建议实现City类的hashCode方法,然后用HashSet实现去重?首先,我不想把业务逻辑放在model DO类里;其次,将重复字段放在代码中,便于代码的阅读、理解和维护。
map city map=new HashMap(1024);迭代器iterator=parser.iteratorwhile(iterator . has next){ City City City=parse City(iterator . next);cityMap.put(city.getCode,city);}返回city map . values;3.遵循Java语言规范,重写hashCode方法和equals方法。
不重写hashCode和equals方法的自定义类不应在集合中使用。
由公共方法代理SprfinalgCGLIB proxy生成的代理类是一个继承的代理类,该代理是通过重写代理类中的非最终方法来实现的。所以SpringCGLIB代理的类不能是final,代理的方法也不能是final,这是受继承机制限制的。
6.1.问题现象这里有一个简单的例子。只有超级用户才有权删除公司,所有服务功能都被AOP拦截处理异常。示例代码如下:
1、UserService.java:
/* *用户服务类*/@ service public class userservice {/* *超级用户*/私人用户超级用户;/* *设置超级用户*/public void设置超级用户(user super user) {this.super user=超级用户;}/* * Get superuser */public最终用户Get super user { return this . super user;}}2、CompanyService.java:
/* *公司服务类*/@ servicepublic类公司服务{/* *公司道*/@ autowiredprivate公司道公司道;/* *用户服务*/@ autowiredPrivate UserserviceUserservice;/* *删除公司*/public void删除公司(长公司id,长操作){//设置超级用户(新用户(0L,\’ admin \ ‘,\ ‘超级用户\ ‘));//验证超级用户if(!Objects.equals (operated,userservice . get super user . getid)){ throw new example exception(\ ‘只有超级用户才能删除公司\ ‘);}//删除公司信息companydao.delete (companyid,operator);}}3、AopConfiguration.java:
/** AOP配置类*/@ slf4j @ aspect @ configuration public类AOP配置{/* * around方法*/@ around (\ ‘执行(* org . changyi . spring boot . service.* * (.))\)周围的公共对象(继续连接点连接点){尝试{log.info (\ ‘开始调用服务方法.\’);返回joinPoint.proceed} catch(Throwable e){ log . error(e . getmessage,e);抛出新的ExampleException(e.getMessage,e);}}}当我们调用CompanyService的deleteCompany方法时,甚至会抛出一个null PointerException,因为调用UserService类的getSuperUser方法得到的超级用户是。但是在CompanyService类的deleteCompany方法中,每次都是由UserService类的setSuperUser方法强制指定超级用户,而由UserService类的getSuperUser方法获得的超级用户应该不是。其实这个问题也是AOP代理造成的。
6.2.问题分析在使用SpringCGLIB代理类时,Spring会创建一个名为UserService $ $ enhancerbyspringglib $ $的代理类,反编译这个代理类得到如下主代码:
公共类UserService $ $ enhancerbyspringglib $ $ a2c3b 345扩展UserService实现SpringProxy,Advised,Factory {.public final void set super User(User var 1){ method interceptor var 10000=this。CG lib $ CALLBACK _ 0;if(var 10000==){ CGLIB $ BIND _ CALLBACKS(this);var10000=这个。CG lib $ CALLBACK _ 0;}if (var10000!=) {var10000.intercept(this,CGLIB$setSuperUser$0$Method,new Object[]{var1},CGLIB $ set super user $ 0 $ Proxy);} else { super . set super user(var 1);}} .}正如您所看到的,这个代理类继承了UserService类并代理了setSuperUser方法,但没有代理getSuperUser方法。因此,当我们调用setSuperUser方法时,我们设置原始对象实例的超级用户字段值;当我们调用getSuperUser方法时,我们获得代理对象实例的超级用户字段值。如果这两个方法的最终修饰符互换,也存在获取超级用户行为的问题。
6.3.避坑方法1。严格遵循CGLIB代理规范,不要给代理类和方法添加final修饰符。严格遵循CGLIB代理规范,不要在被代理的类和方法中添加final修饰符,避免动态代理操作对象实例不同(原始对象实例和代理对象实例)导致的数据不一致或空指针问题。
2.缩小CGLIB代理类的范围,不能用就不要代理类。
缩小CGLIB代理类的范围,如果不能使用代理类,就不要做代理类,这样可以节省内存开销,提高函数调用效率。
在fastjson被强行升级到1.2.60的时候,公场代理踩了一个坑。为了快速发展,作者定义:
公共类ParseConfig {
public final SymbolTable SymbolTable=new SymbolTable(4096);
.
}
我们在项目中继承了这个类,同时又是用AOP动态表示的,所以一行代码就造成了“血案”。
7.1.问题现象还是用上一章的例子,只是删除了采集和设置方法,定义了一个公共字段。示例代码如下:
1、UserService.java:
/* *用户服务类*/@ servicepubliclassuserservice {/* *超级用户*/PublicFinal User超级用户=新用户(0L,\’ admin \ ‘,\ ‘超级用户\ ‘);}2、CompanyService.java:
/* *公司服务类*/@ servicepublic类公司服务{/* *公司道*/@ autowiredprivate公司道公司道;/* *用户服务*/@ autowiredPrivate UserserviceUserservice;/* *删除公司*/public void删除公司(长公司id,长操作){//验证超级用户if(!Objects.equals (operated,userservice . super user . getid)){ throw new example exception(\ ‘只有超级用户才能删除公司\ ‘);}//删除公司信息companydao.delete (companyid,operator);}}3、AopConfiguration.java:
同上一章,AopConfiguration.java。
当我们调用CompanyService的deleteCompany方法时,我们抛出一个null PointerException。经过调试和打印,发现UserService的超级用户变量是。如果删除AopConfiguration,就不会出现空指针异常,说明这个问题是AOP代理引起的。
7.2.问题分析使用SpringCGLIB代理类时,Spring会创建一个名为UserService $ $ enhancerbyspringglib $ $的代理类,这个代理类final继承了UserService类,并覆盖了UserService类中的所有非final公共方法。然而,这个代理类不调用超基类的方法;相反,它创建UserService的一个成员,并指向userService类对象的原始实例。现在,内存中有两个对象实例:一个是原来的UserService对象实例,另一个是指向UserService的代理对象实例。这个代理类只是一个虚拟代理。它继承了UserService类,并具有与UserService相同的字段,但它从不初始化和使用它们。因此,一旦通过这个代理类对象实例获得了公共成员变量,就会返回一个默认值。
7.3.避坑方法1。当确定该字段是不可变的时,可以将其定义为公共静态常量。当确定该字段不可变时,可以将其定义为公共静态常量,并通过类名字段名进行访问。不管类实例的动态代理是什么,类字段名都会访问公共静态常量。
/* *用户服务类*/@ servicepubliclassuserservice {/* *超级用户*/PublicStatic最终用户Super _ User=新用户(0L,\’ admin \ ‘,\ ‘超级用户\ ‘);}/* *使用代码*/if(!Objects.equals (operated,userservice . super _ user . getid)){ throw new example exception(\ ‘只有超级用户才能删除公司\ ‘);}2.当确定该字段不可变时,可以将其定义为私有成员变量。
当某个字段不可变时,可以定义为私有成员变量,并提供一个公共方法来获取变量值。当这个类实例被动态代理时,代理方法将调用被代理的方法,从而返回被代理的类的成员变量值。
/* *用户服务类*/@ service public class userservice {/* *超级用户*/private user super user=new user(0l,\’ admin \ ‘,\ ‘ super user \ ‘);/* * Get超级用户*/public用户getSu* 使用代码 */if (!Objects.equals(operatorId, userService.getSuperUser.getId)) {throw new ExampleException(\”只有超级用户才能删除公司\”);}3、遵循 JavaBean 编码规范,不要定义公有成员变量。
遵循 JavaBean 编码规范,不要定义公有成员变量。JavaBean 规范如下:
(1)JavaBean类必须是一个公共类,并将其访问属性设置为public,如:public class User{……}(2)JavaBean类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器(3)一个JavaBean类不应有公共实例变量,类变量都为private,如:private Integer id;(4)属性应该通过一组getter/setter方法来访问。
人类受益于“类比”思维,举一反三就是人类的智慧,每当遇到新生事物时,人们往往用类似的已知事物作为参考,能够加速对新生事物的认知。而人类又受制于“定势”思维,因为已知事物并不能代表新生事物,而人们又容易形成先入为主的概念,最终导致对新生事物产生误判。
作者简介:陈昌毅,花名常意,高德地图技术专家,2018 年加入阿里巴巴,一直从事地图数据采集的相关工作。
☞讯飞智能语音先锋者:等到人机交互与人类交流一样自然时,真正的智能时代就来了!
☞企业打造自己的数据中台,需要的是一套硅谷方法论(文末有福利!)
☞从Nginx到Pandownload,程序员如何避免面向监狱编程?
☞只会高中数学运算就能发现算法?Google开源的AutoML-Zero有多厉害
☞Spring Cloud云架构下的微服务架构:部门微服务(Dept)
☞从Spring Cloud到Service Mesh,微服务架构治理体系如何演进?

其他教程

动漫公司的总公司是什么样的?JC的建筑很宏伟,骨头会有点寒酸。

2022-9-8 8:23:05

其他教程

3dmax的安装包(3dmax软件怎么安装)

2022-9-8 8:25:15

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索