扫一扫
关注公众号
本文更多关注平时容易忽略的技巧或者细节,不是条条框框的JAVA入门教程。想到什么或者看到什么比较合适就写下来了,不定期更新
随着微服务的推行,越来越多的服务转变成远程服务调用,中间最重要的就是序列化和反序列化,那么数据传输的安全性就不可轻视,不少黑客就是通过序列化漏洞获取到敏感数据,我们应该对重要字段进行过滤
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private static String job; // 静态字段不参与序列化
private transient String birthday; // 敏感数据
private transient String socialSecurityNumber; // 敏感数据
// private static final ObjectStreamField[]
// serialPersistentFields = {
// new ObjectStreamField("firstName", Person.class),
// new ObjectStreamField("lastName", Person.class)
// };
}
1、POJO一般都实现Serializable并且设置serialVersionUID的值,以便对象传递和保证完整性,如果有父子类则父类必须实现
2、必须实现set\get\toString方法,但是不要在set\get方法上处理业务逻辑,因为如果直接访问属性,后续扩展权限非常难处理
3、int和Integer也就是基本类型和对象类型的使用,虽然有装箱和拆箱机制,但是Integer是有缓存的,是没有默认值的,原生的int运算时可能会出现溢出
4、private static final long serialVersionUID = 1L; 在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号
private static final long serialVersionUID = -2805284943658356093L;是根据类的结构产生的hash值,增减一个属性、方法等,都可能导致这个值产生变化
5、在同一个流ObjectOutputStream中writeObject相同对象,序列化后的对象长度不会叠加,只是会多了引用长度
6、深克隆与浅克隆,默认实现Cloneable的POJO的clone()是浅克隆,可以通过序列化和反序列实现深克隆
7、设计实现了Serializable接口的单例,序列化会通过反射调用无参构造器返回新对象,我们可以添加readResolve()方法,自定义返回对象策略
当我们为父类添加新方法、变更方法的规范、改变未继承实现的方法都会对子类有影响。比如说我们有个子类继承了HashTable类进行敏感数据的处理,在get\set时进行鉴权,突然某天HashTable添加了EntrySet方法,那么就有可能通过它绕过权限校验造成问题。
对于数量有限的对象,应该优先考虑使用Enum,减少实例的数量,减少内存的使用。另外一种好处就是减少用数字表示,代码可读性更好
package com.front.ops.soa;
public enum HelloWords {
ENGLISH ("ENGLISH","Hello"),
SPANISH ("Spanish","hola");
final String language;
final String greeting;
HelloWords(String language,String greeting) {
this.language = language;
this.greeting = greeting;
}
}
public class Singleton {
private volatile static Singleton instance = null;//volatile禁止指令重排序优化,因为初始化Singleton和将对象赋给instance字段的顺序是不确定的,容易出现NPE异常
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) { // 第一次校验,保证只需要使用重量级锁synchronized一次
synchronized (Singleton.class) {
if (instance == null) { // 第二次校验,避免线程并发出现多次创建
instance = new Singleton();
}
}
}
return instance;
}
}
需要知道的一点是在触发InterruptedException异常的同时,JVM会同时把线程的中断标记位清除掉
Thread th = Thread.currentThread();
while(true) {
if(th.isInterrupted()) {
break;
}
// 省略业务代码无数
try {
Thread.sleep(100);
}catch (InterruptedException e){
// 必须重新设置中断标记位
th.interrupt();
e.printStackTrace();
}
}
多用工具库,少造轮子,比如常见的List转成有特殊分隔符的String、Curl请求,下面是RestTemplate比较常见的用法
示例一
HttpEntity<String> entity = null;
try {
entity = new HttpEntity<>(objectMapper.writeValueAsString(data));
} catch (IOException e) {
e.printStackTrace();
}
ResponseEntity<String> response = restTemplate.postForEntity(getAllInterfaceAliasesByIpsUrl, entity, String.class);
示例二
String entity = null;
try {
entity = objectMapper.writeValueAsString(data);
} catch (IOException e) {
e.printStackTrace();
}
ParameterizedTypeReference<String> responseType = new ParameterizedTypeReference<String>() {
};
RequestEntity<String> request = RequestEntity.post(URI.create(getServerStatusListByInterfaceUrl)).body(entity);
ResponseEntity<String> response = restTemplate.exchange(request, responseType);
需要关闭的连接资源等等,更加推荐使用try-with-resources语句,因为可以更好处理异常,另外需要了解的一点是,下面这种情况的finally是不会执行的。另外现在finally也不推荐使用了
try {
// do something
System.exit(1);
} finally {
System.out.println("finally run");
}
final是在return表达式运行后执行的,此时要将return的结果暂存起来,待finally代码块执行完成后再返回缓存的结果
尽量一早就开始使用maven管理多模块和依赖
project
|-- pom.xml
|-- module-dao/
| `-- pom.xml
|-- module-service/
| `-- pom.xml
|-- module-scraper/
| `-- pom.xml
`-- webapp/
`-- pom.xml
<assembly xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/assembly-1.0.0.xsd">
<id>package</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/config</directory>
<outputDirectory>resources</outputDirectory>
</fileSet>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>resources</outputDirectory>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
常见线程池:
线程池使用注意事项
1、只有写锁支持条件变量(比如队列是否为空是否已满),读锁调用newCondition()会抛出异常
2、先获取读锁再获取写锁是不允许的,但是获取到写锁后可以降级为读锁
锁升级是不允许的,如下代码会阻塞
// 读缓存
r.lock();
try {
v = m.get(key);
if (v == null) {
w.lock();
try {
// 再次验证并更新缓存
// 省略详细代码
} finally{
w.unlock();
}
}
} finally{
r.unlock();
}
锁的降级是可以的
class CachedData {
Object data;
volatile boolean cacheValid;
final ReadWriteLock rwl =
new ReentrantReadWriteLock();
// 读锁
final Lock r = rwl.readLock();
// 写锁
final Lock w = rwl.writeLock();
void processCachedData() {
// 获取读锁
r.lock();
if (!cacheValid) {
// 释放读锁,因为允许读锁的升级
r.unlock();
// 获取写锁
w.lock();
try {
// 再次检查状态
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 释放写锁前,降级为读锁
// 降级是可以的
r.lock();
} finally {
// 释放写锁
w.unlock();
}
}
// 此处仍然持有读锁
try {use(data);}
finally {r.unlock();}
}
}
Father father = new Son();
//Son覆写了此方法
father.doSomething();
覆写要注意两大一小原则,子类的访问权限只能相同或者变大,抛出异常和返回类型只能变小,方法名和参数必须完全相同,一般情况下我们都会使用override注解,以便编译器检测是否符合覆写条件
JVM在重载方法中,选择合适的目标方法的顺序如下:
泛型可以避免重复定义方法,也可以避免使用Object作为输入输出,带来强制转换的风险(ClassCastException)
List list = Collections.
synchronizedList(new ArrayList());
synchronized (list) { //这行必须添加
Iterator i = list.iterator();
while (i.hasNext())
foo(i.next());
}
实际上Collections.synchronizedList类似封装如下
SafeArrayList<T>{
List<T> c = new ArrayList<>();
synchronized
T get(int idx){
return c.get(idx);
}
synchronized
void add(int idx, T t) {
c.add(idx, t);
}
synchronized
boolean addIfNotExist(T t){
if(!c.contains(t)) {
c.add(t);
return true;
}
return false;
}
}
上面的addIfNotExist其实包含了组合操作,每个操作是原子性的,但是组合操作往往是非原子性的。
CopyOnWriteArrayList就是在写的时候不对原集合进行修改,而是重新复制一份修改完后,再修改指针,所以会导致有短暂的数据不一致。另外它的迭代器是只读的,不支持增删改,因为遍历的只是快照。
假如有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:
生产者伪代码:
count+1
notify()
消费者伪代码:
while(count<=0){
wait()
}
count--
但是实际上可能消费者在检查count到调用wait()之间,count就可能被改掉了,导致消费者无法执行业务操作,这是很常见的一种竞态条件,所以需要把wait和notify的调用放在同步代码块中,否则会出现IllegalMonitorStateException问题
thread_fun()
{
prepare_word.....
while (1)//没有sleep(0)的话,这里会一直浪费CPU时间做死循环的轮询,无用功
{
if (A is finish)
break;
else
sleep(0); //这里会交出B的时间片,下一次调度B的时候,接着执行这个循环
}
process A's data
}
Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”,竞争的结果也许是当前线程仍然获得CPU控制权。因为0的原因,线程直接回到就绪队列,而非进入等待队列,只要进入就绪队列,那么它就参与cpu竞争
Exception应该也是返回值的一部分,应该设计成Checked Exception,尽量让调用方能够显式的处理
设计者应该避免太多findBy方法和各自的重载,正确的打开方式应该类似组合模式
public interface StudentApi{
Student findBySpec(StudentSpec spec);
List<Student> findListBySpec(StudentListSpec spec);
Page<Student> findPageBySpec(StudentPageSpec spec);
}
public class TestDemo { public static void main(String[] args ) { String str = new StringBuilder("计算机软件").append("软件").toString(); String str1 = new StringBuilder("ja").append("va").toString(); String str2 = new StringBuilder("jaa").append("va").toString(); String str3 = new StringBuilder("cl").append("ass").toString(); String str4 = new StringBuilder("Inte").append("ger").toString(); String st5 = new StringBuilder("in").append("t").toString(); System.out.println(str3=="jaava");//false System.out.println(str.intern()==str);//true System.out.println(str1.intern().equals(str1));//true System.out.println(str1.intern()==str1);//false System.out.println(str2.intern()==str2);//true System.out.println(str3.intern()==str3);//true System.out.println(str4.intern()==str4);//true System.out.println(st5.intern()==st5);//false // String str = "计算机"+"软件"; // String str1 = "ja"+"va"; // String str2 = "jaa"+"va"; // String str3 = "cl"+"ass"; // String str4 = "en"+"um"; // String st5 = "in"+"t"; // System.out.println(str.intern()==str);//true // System.out.println(str1.equals(str1.intern()));//true // System.out.println(str1.intern()==str1);//true // System.out.println(str2.intern()==str2);//true // System.out.println(str3.intern()==str3);//true // System.out.println(str4.intern()==str4);//true // System.out.println(st5.intern()==st5);//true }
class Singleton { private byte[] a = new byte[6*1024*1024]; private static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return singleton; } } class Obj { private byte[] a = new byte[3*1024*1024]; } public class Client{ public static void main(String[] args) throws Exception{ Singleton.getInstance(); while(true){ new Obj(); } }
运行结果:
……
[Full GC 18566K->6278K(20352K), 0.0101066 secs]
[GC 18567K->18566K(20352K), 0.0001978 secs]
[Full GC 18566K->6278K(20352K), 0.0088229 secs]
hotspot虚拟机的垃圾收集算法使用根搜索算法,可以作为根的对象有:
方法区是jvm的一块内存区域,用来存放类相关的信息。很明显java中单例模式创建的对象被自己类中的静态属性所引用,符合第二条,因此单例对象不会被jvm垃圾收集
我们习惯使用Arrays#asList将Array转成ArrayList,但是其实Arrays#asList返回的其实是java.util.ArrayList,它是Arrays的定长集合类,它实现了set\get\contains方法,但是没有实现add\remove方法,调用它的add\remove方法实际上会调用父类AbstractList的方法,但是没有具体实现,仅仅抛出UnsupportedOperationException异常。同时java.util.ArrayList参数为可变长泛型,当调用其size方法时得到的将会是泛型对象个数,也就是一。另外asList得到的ArrayList其实是引用赋值,也就是说当外部数组或集合改变时,数组和集合会同步变化