Java基础知识
TOC
[TOC]
目标
以面试为导向,知识点要全面,查漏补缺。突出重点。
学完后有一个基本的认知是什么,对于重点内容要知道为什么,了解实现原理。
基础
强引用、弱引用、虚引用、软引用
背景
简单了解即可。和引用关联的知识点,不得不提对象的生命周期,再远点就是GC如何管理对象的生命周期的。这里假设读者已经知道这些内容了。
引用强度认知:
引用强度从强到弱分别为:强引用、软引用、弱引用、虚引用。
强引用 StrongReference
默认引用形式,使用时不需要显示定义。任何通过强引用所使用的对象不管系统资源有多紧张,Java GC都不会主动回收具有强引用的对象。
弱引用 WeakReference
如果一个对象只具有弱引用,无论内存充足与否,Java GC后对象如果只有弱引用将会被自动回收。
软引用 SoftReference
软引用和弱引用的特性基本一致, 主要的区别在于软引用在内存不足时才会被回收。如果一个对象只具有软引用,Java GC在内存充足的时候不会回收它,内存不足时才会被回收。
虚引用 PhantomReference
从PhantomReference类的源代码可以知道,它的get()方法无论何时返回的都只会是null。所以单独使用虚引用时,没有什么意义,需要和引用队列ReferenceQueue类联合使用。当执行Java GC时如果一个对象只有虚引用,就会把这个对象加入到与之关联的ReferenceQueue中。
文字干巴巴,去看原文代码
小结
强引用是 Java 的默认引用形式,使用时不需要显示定义,是我们平时最常使用到的引用方式。不管系统资源有多紧张,Java GC都不会主动回收具有强引用的对象。
弱引用和软引用一般在引用对象为非必需对象的时候使用。它们的区别是被弱引用关联的对象在垃圾回收时总是会被回收,被软引用关联的对象只有在内存不足时才会被回收。
虚引用的get()方法获取的永远是null,无法获取对象实例。Java GC会把虚引用的对象放到引用队列里面。可用来在对象被回收时做额外的一些资源清理或事物回滚等处理。
由于无法从虚引获取到引用对象的实例。它的使用情况比较特别,所以这里不把虚引用放入表格进行对比。这里对强引用、弱引用、软引用进行对比:

final关键字的作用 (方法、变量、类)
应用在方法、变量、类上的作用不赘述,已经掌握,只查漏补缺
-
被final修饰的方法,不可以被重写。但是不影响本类的重载以及重载函数的重写。
-
final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
不可变的类是天然无状态的,线程安全的。
如何写一个不可变类呢?
- 将类声明为final,所以它不能被继承
- 将所有的成员声明为私有的,这样就不允许直接访问这些成员
- 对变量不要提供setter方法
- 将所有可变的成员声明为final,这样只能对它们赋值一次
- 通过构造器初始化所有成员,进行深拷贝(deep copy)
- 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
具体可参考String和Integer等的写法
和static一起使用
对于静态final变量,我们可以直接初始化,或者使用静态代码块。而不可以使用构造函数或者构造代码块。
因为static要求在编译期间就确定值,然后放入静态区。而构造函数和构造代码块发生在运行期间。
final和private
类中所有的private方法都隐式的指定为final的,由于无法取用private方法,所以也就无法覆盖它,可以对private方法添加final修饰符,但并没有添加任何额外意义。(了解即可)
泛型、泛型继承、泛型擦除
泛型擦除
使用泛型的话,运行期把对象都是当成object来处理的,所以可以运用的方法都是object的方法,且在赋值操作时,编译器会自动强转为指定泛型类型,另一个好处就是在编译期更早的发现向下转型可能出现的错误,因为向下转型是不安全的.
带来什么样的问题?
在运行时反编译添加不同类型的数据,数据错误,代码不报错。
泛型继承,带上界的擦除
当使用上界时泛型擦除擦除为上界的类型,因此也就解释了为啥可以调用上界的方法.并且会和赋值操作的时候一样自动强转为对应的泛型,向上转型,为安全的操作。
带通配符的上界
带通配符的下界
总结
泛型的出现是为了减少向下转型出现的错误,泛型的目的是尽可能的在编译器发现转型时的错误,所以对于不安全的操作(编译器认为的)会绝对禁止,存储进去的都是绝对安全(编译器认为的)的数据.
jdk ServiceLoader
参见Java SPI机制:ServiceLoader实现原理及应用剖析
SPI,全称Service Provider Interfaces
,服务提供接口。可以用来解耦服务的实现和使用,增强应用的可扩展性。
JDK中,基于SPI的思想,提供了默认具体的实现,ServiceLoader
。利用JDK自带的ServiceLoader
,可以轻松实现面向服务的注册与发现
,完成服务提供与使用的解耦
。
使用
外部使用时,往往通过load(Class service, ClassLoader loader)
或load(Class service)
调用,最后都是在reload
方法中创建了LazyIterator
对象,LazyIterator
是ServiceLoader
的内部类,实现了Iterator
接口,其作用是一个懒加载的迭代器,在hasNextService
方法中,完成了对位于META-INF/services/
目录下的配置文件的解析,并在nextService
方法中,完成了对具体实现类的实例化。
META-INF/services/
,是ServiceLoader
中约定的接口与实现类的关系配置目录,文件名是接口全限定类名,内容是接口对应的具体实现类,如果有多个实现类,分别将不同的实现类都分别作为每一行去配置。解析过程中,通过LinkedHashMap<String,S>
数据结构的providers
,将已经发现了的接口实现类进行了缓存,并对外提供的iterator()
方法,方便外部遍历。
总结
基于服务提供与发现的思想,系统自带的ServiceLoader
以及基于此思想基础上的演化形式,被广泛的使用到实际的项目中。本质上,通过服务接口约定、服务注册与服务发现,完成将服务提供方与服务使用方的解耦,大大扩展了系统的可扩展性。服务注册的本质,是将服务接口与具体服务实现的映射关系注册到系统或特定实现中。服务发现的过程,本质上是向系统或特定实现去匹配对应的具体实现类,但在写法上是基于接口的编程方式,因为服务使用方和服务提供方彼此都是透明与未感知的。基于SPI
思想的ServiceLoader
实现及演化,在项目的组件化,或实现扩展性功能,甚至完成具有可插拔能力的插件化模块时,往往都被广泛使用到。
LinkedList、LinkedHashMap、LRU
LinkedList
底层数据结构
设计模式
装饰者模式
代理模式
责任链模式
工厂模式
适配器模式
建造者模式
单例模式
模板模式
观察者模式
关于精度损失问题:int、long 超过最大值
关于注解:元注解的种类、继承java.lang.Annotation、注解的基础类型、注解的常用方法
关于ClassLoader,类加载器,双亲委派模型
Spring Boot
如何自定义starter
引入spring-boot-autoconfigure依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
- 编写JavaBean
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "SimpleBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
- 编写配置类
@Configuration
@ConditionalOnClass//当类路径下有指定当类自动配置
public class MyAutoConfiguration {
static {
System.out.println("MyAutoConfiguration init....");
}
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
}
- 在resources下创建/META-INF/spring.fatories文件
在该文件中配置自己的自动配置类,多个配置用逗号隔开
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAutoConfiguration
Spring Boot启动流程
@SpringBootApplication
@SpringBootApplication
注解里定义了3个事情:
@SpringBootConfiguration
声明使用该注解的类是配置类,由Spring IOC容器托管。
@EnableAutoConfiguration
声明开启自动配置。
@ComponentScan
声明包扫描的范围。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@SpringBootConfiguration
@SpringBootConfiguration
注解内部有一个核心注解@Configuration,表示当前类是一个配置类,并可以被组建扫描器扫描。
@EnableAutoConfiguration
@EnableAutoConfiguration
主要干了两件事:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage
自动配置包
@Import(AutoConfigurationImportSelector.class)
自动配置类扫描导入
源码剖析
自动配置入口AbstractApplicationContext.invokeBeanFactoryPostProcessors
自动配置关键类方法AutoConfigurationImportSelector.getAutoConfigurationEntry
具体细节:
在getCandidateConfigurations
方法中调用SpringFactoriesLoader.loadFactoryNames
,
从类路径下的META-INF/spring.factories
文件中加载自动配置类
小结
@EnableAutoConfiguration
就是从classpath中搜寻META-INF/spring.factories
配置文件,并将其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration
对应的配置项通过反射实例化对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中。
@ComponentScan注解
扫描启动类所在包及其子包下的类,扫描过程中由@AutoConfigurationPackage注解进行解析,从而得到启动类所在包的具体位置。
总结:
@SpringBootApplication注解主要是3个注解的组合注解。

执行原理
SpringApplication实例的初始化创建
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
项目的初始化启动
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//1 获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//3 创建Spring容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//4 Spring容器前置处理
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 5 刷新容器
refreshContext(context);
//6 容器后置处理
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//7 发出结束执行的事件
listeners.started(context);
//返回容器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

Spring
IOC
什么是IOC
控制反转,管理对象的生命周期及对象之间的依赖关系
对象的创建,new的方式,改为从容器中拿。
构造器注入和set方法注入。
什么是对象的生命周期

实例化bean,设置属性值,调用BeanPostProcessor的初始化前方法,调用初始化方法,调用BeanPostProcessor的初始化后方法。
延迟加载
被修饰为lazy-init的bean ,Spring容器初始化阶段不会进行初始化,第一次进行getBean调用时,才初始化调用
如何处理循环依赖
用了三级缓存。
-
单例bean构造器参数循环依赖(无法解决)
-
prototype原型bean循环依赖(无法解决)
-
单例bean通过set注入或@Autowired循环依赖(可以解决)
原理:基于Java引用传递,当获得对象的引用时,对象的属性是可以延迟后设置的,但是构造器必须在获取引用之前。
通过提前暴露一个ObjectFactory对象来完成,简单来说,ClassA在调用构造器完成对象初始化之后,在调用ClassA的serClassB方法之前,就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

AOP
面向切面编程
在不改变原有业务逻辑的基础上,增强横切逻辑,根本上解耦合,避免横切逻辑代码重复。
事务
本质,是对Connection的管理。将连接绑定到当前线程,从当前线程获取连接,运用ThreadLocal。
DataSourceTransactionManager
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
//从ThreadLoacal拿连接
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
@Nullable
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();//看resources这个变量
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
看一下resources
变量在TransactionSynchronizationManager
中的定义
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
Spring事务管理基于AOP,如果需要增强Bean。使用AOP添加事务支持。
AOP是基于Spring扩展点BeanPostProcessor.postProcessAfterInitailization
同一个类中非事务方法调用@Transaction注解的事务方法事务失效问题
@Service
class A{
@Transactinal
method b(){...}
method a(){ //标记1
b();
}
}
//Spring扫描注解后,创建了另外一个代理类,并为有注解的方法插入一个startTransaction()方法:
class proxy$A{
A objectA = new A();
method b(){ //标记2
startTransaction();
objectA.b();
}
method a(){ //标记3
objectA.a(); //由于a()没有注解,所以不会启动transaction,而是直接调用A的实例的a()方法
}
}
当我们调用A的bean的a()方法的时候,也是被proxyA拦截,执行proxyA.a()(标记3),然而,由以上代码可知,这时候它调用的是objectA.a(),也就是由原来的bean来调用a()方法了,所以代码跑到了“标记1”。由此可见,“标记2”并没有被执行到,所以startTransaction()方法也没有运行。
https://zhuanlan.zhihu.com/p/101396825
https://mp.weixin.qq.com/s/1TEBnmWynN4nwc6Q-oZfvw
https://juejin.im/post/5dc21bdff265da4d4e300413
https://blog.csdn.net/levae1024/article/details/82998386
https://blog.csdn.net/m0_38027656/article/details/84190949?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2
解决方法:
springboot启动类加上注解:@EnableAspectJAutoProxy(exposeProxy = true)
。
使用AopContext.currentProxy()
获取代理对象
Redis
持久化方式
AOF,RDB
优缺点
健过期策略
集群方案
Codis
分布式事务
2PC
结合案例,应用存储数据,然后投递到MQ,如何保障事务。
开启事务,协调者向所有的事务参与者发送事务内容
1.数据存储失败,应用服务自己回滚。
2.数据存储成功,消息投递失败,消息没有发送成功,抛出了异常,定时重新发。
3.数据存储成功,消息投递失败,消息发送成功,消费端自己挂了,没有接收到。
这种情况,如果需要保证客户端收到,mq需要支持收到确认,只有消费者收到并发送确认到ack给mq,mq才知道消费者收到,否则,mq自己重发。
如果消息是可丢失的,不重要,那么就不需要客户端确认,不用管消费端挂了,在不在线。

Time waits for no one.