Bootstrap

一些容易遗忘或者弄混淆的Java知识点(不断更新)

目录

一、try-catch-finally内部代码执行顺序

二、synchronized和Lock区别是什么?

三、线程休眠(阻塞)的几种方法

休眠方法之间的联系与区别

四、notify是随机唤醒还是顺序唤醒

五、maven是怎样解决依赖冲突的

六、Spring Boot的starter机制是什么

七、Springboot自动配置具体实现步骤

八、Spring的@Autowired注解为什么用在接口上

九、Spring Bean的生命周期

十、Java动态代理InvocationHandler和Proxy

1、Proxy

2、InvocationHandler

3、实例


一、try-catch-finally内部代码执行顺序

try{

    代码段1;

}catch(Exception e){

    代码段2;

}finally{

    代码段3;    

}

执行顺序:

        先执行try中代码,如果没异常就在try中return语句执行之后,返回之前跳转执行finally中代码。

        如果有异常,就在遇到异常之后跳转执行catch中代码,并在catch中return语句执行之后,返回之前跳转执行finally中代码。

        但需要注意的是,在执行finally中代码之前,try中return的值已经确定,例如若try中代码最后为return a+b,此时a=1,b=2。则程序会将3保存起来,不管finally中对a、b做什么改变,最终都会返回3。但若干return的数据时引用数据类型,则在finally中对该引用数据类型的属性值的改变起作用。

        finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。

二、synchronized和Lock区别是什么?

1、类型不同:synchronized是一个关键字,而lock是一个接口,例如ReentrantLock类就是其一个实现类。

2、获取锁和释放锁方式不同:synchronized是隐式的加锁,会自动加锁和释放锁。lock是显示的加锁,在使用之前需要先创建一个 Lock 对象,然后使用lock()和unlock()方法手动加锁和释放锁。

3、用法不同:synchronized 可用来方法和代码块,而 Lock 只能用在代码块上。

4、锁类型不同:synchronized 属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁(默认非公平)。                                                                                                                                 公平锁指多个线程按照先到先得的策略获取锁。好处是所有线程都有机会获得锁,不会饿死。坏处是由于所有线程都会经历阻塞态,因此唤醒阻塞线程的开销会很大。                                               非公平锁指所有的线程拼运气,谁运气好,谁就获取到锁。好处是可以减少CPU唤醒线程的开销,整体的吞吐效率会高。坏处是可能会有线程长时间甚至永远获取不到锁,导致饿死。

5、响应中断不同:Lock 可以使用 lockInterruptibly 获取锁并响应中断指令,而 synchronized 不能响应中断,也就是如果发生了死锁,使用 synchronized 会一直等待下去,而使用 Lock 可以响应中断并释放锁,从而解决死锁的问题。

6、底层实现不同:synchronized采用的是monitor对象监视器,lock的底层原理是AQS。

参考来源:

http://t.csdn.cn/hUNow

三、线程休眠(阻塞)的几种方法

1、线程睡眠Thread.sleep

2、线程等待Object.wait

3、线程暂停LockSupport.park

休眠方法之间的联系与区别

1、sleep()和wait()以及park()都是可中断方法,但被中断后只有sleep()和wait()会收到中断异常。

2、sleep()后线程状态为TIMED_WAITING(限期等待),wait()以及park()后线程状态为WAITING(无限期等待)。

3、sleep()以及park()不会释放持有的锁,而wait()会释放持有的锁。

4、三个方法所属类不同。

5、wait()必须在同步方法中进行,sleep()不需要。

参考来源:http://t.csdn.cn/6A9k1

四、notify是随机唤醒还是顺序唤醒

notify 唤醒线程的规则是随机唤醒还是顺序唤醒取决于 JVM 的具体实现,作为主流的 HotSpot 虚拟机中的 notify 的唤醒规则是顺序的,也就是 notify 会按照线程的休眠顺序,依次唤醒线程。

五、maven是怎样解决依赖冲突的

1、路径最近者优先

相同jar不同版本,根据依赖的路径长短来决定引入哪个依赖。

     该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。 

2、第一声明原则

在pom.xml配置文件中,如果有两个名称相同,版本的不同依赖声明,先写的会生效,所以先声明自己要用的版本。这里的名称相同,版本不同的依赖声明,既可以是直接依赖,也可以是传递依赖。

六、Spring Boot的starter机制是什么

和自动配置一样,Spring Boot Starter的目的也是简化配置,而Spring Boot Starter解决的是依赖管理配置复杂的问题,有了它,当我需要构建一个Web应用程序时,不必再遍历所有的依赖包,一个一个地添加到项目的依赖管理中,而是只需要一个配置spring-boot-starter-web, 同理,如果想引入持久化功能,可以配置spring-boot-starter-data-jpa。所有官方的starter都以spring-boot-starter-*的规则命名。

参考来源:https://zhuanlan.zhihu.com/p/291708888

七、Springboot自动配置具体实现步骤

1.SpringBoot启动的时候加载主启动类,通过@EnableAutoConfiguration开启自动配置功能。

2.@EnableAutoConfiguration利用AutoConfigurationImportSelector给容器中导入一些组件。

3.通过protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)获取候选的配置和核心方法,扫描所有jar包类路径下"META-INF/spring.factories",通过@AutoConfigurationPackage自动配置包。

4.把扫描到的文件包装成Properties对象。

5.从properties中获取到EnableAutoConfiguration.class类名对应的值,并把他们添加在容器中。

6.整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中。

7.根据@Conditional注解中的条件判断,决定这个配置是否生效。

8.初始化Bean,自动配置完成。

总结
   SpringBoot通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类。



参考来源:https://blog.csdn.net/yanghang1122/article/details/124970078

八、Spring的@Autowired注解为什么用在接口上

例如在Controller中使用@Autowired时,为什么是用在Service上而不是ServiceImpl上。

        这里就要说到@Autowired的注入原理:@Autowired是Spring的注解,Autowired默认先按byType,如果发现找到多个bean,则再按照byName方式比对,如果还有多个bean,则报错Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException;                              再来说Controller获取实例的过程:使用@Autowired,程序在spring的容器中查找类型时TestService的bean,刚好找到有且只有一个此类型的bean,即TestServiceImpl,所以就把testServiceImpl自动装配到了Controller的实例TestService中。

        如果一个接口有多个实现类时,@Autowired需要结合@Qualifier("类名")使用,注:实现类的开头小写类名TestServiceImpl2->testServiceImpl2。                      

@Autowired
@Qualifier("testServiceImpl2")
private TestService testService; 

问:为什么非要调用接口来多此一举,而不直接调用实现类serviceImpl的bean来得简单明了呢?

答:直接使用serviceImpl的bean是可以的,这样加一层接口的原因:1.AOP程序的思想,给别人调用的接口,调用这只想知道方法和功能,而对于这个方法内部逻辑实现并不关心。2.降低各个模块间的关联,实现松耦合、程序分成、最主要的体现了继承只能单继承,接口却可以多实现

参考来源:https://blog.csdn.net/m0_61442607/article/details/124374190

九、Spring Bean的生命周期

 内容较多,结合着看:Springbean生命周期详解_爱吃巧克力的小男孩的博客-CSDN博客_springbean生命周期详解

十、Java动态代理InvocationHandler和Proxy

        提到动态代理,就不得不提到其两个重要的类和接口InvocationHandler(接口)Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心。

1、Proxy

首先简单梳理一下,假设存在一个被代理类Teacher,我们要生成其的代理类proxy,我们需要调用 Proxy 的 newProxyInstance 方法来生成代理类,而newProxyInstance方法有三个参数:              参数1 类加载器
        ClassLoader classLoader = person.getClass().getClassLoader();
参数2 被代理对象实现的所有的接口的字节码数组
        Class[] interfaces =person.getClass().getInterfaces();// {Court.class , ... , ...};
        Class[] interfaces={Court.class};
参数3 执行处理器 用于定义方法的增强规则(加强后的方法)
        InvocationHandler handler =new InvocationHandler(){}                                                            这个参数3就是要将代理对象关联到InvocationHandler实现类对象上。

2、InvocationHandler

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:

 /**
 * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
 * method:我们所要调用某个对象真实的方法的Method对象
 * args:指代代理对象方法传递的参数
 */
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

我们可以在这个invoke方法中对被代理对象的方法进行增强。 

3、实例

- 首先我们定义一个接口People

package reflect;

public interface People {

    public String work();
}

 - 再定义一个Teacher类,实现People接口,这个类是真实的对象,也是我们的被代理类

package reflect;

public class Teacher implements People{

    @Override
    public String work() {
        System.out.println("老师教书育人...");
        return "教书";
    }

}

 - 现在我们要定义一个代理类的调用处理程序,每个代理类的调用处理程序都必须实现InvocationHandler接口,调用处理程序如下:

package reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class WorkHandler implements InvocationHandler{

    //代理类中的真实对象  
    private Object obj;

    public WorkHandler() {
        // TODO Auto-generated constructor stub
    }
    //构造函数,给我们的真实对象赋值
    public WorkHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在真实的对象执行之前我们可以添加自己的操作
        System.out.println("before invoke。。。");
        Object invoke = method.invoke(obj, args);
        //在真实的对象执行之后我们可以添加自己的操作
        System.out.println("after invoke。。。");
        return invoke;
    }

}

- 最后看一下主类

package reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) {
        //要代理的真实对象
        People people = new Teacher();
        //代理对象的调用处理程序,我们将要代理的真实对象传入代理对象的调用处理的构造函数中,最终代理对象的调用处理程序会调用真实对象的方法
        InvocationHandler handler = new WorkHandler(people);
        /**
         * 通过Proxy类的newProxyInstance方法创建代理对象,我们来看下方法中的参数
         * 第一个参数:people.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象
         * 第二个参数:people.getClass().getInterfaces(),这里为代理类提供的接口是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
         * 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上
         */
        People proxy = (People)Proxy.newProxyInstance(handler.getClass().getClassLoader(), people.getClass().getInterfaces(), handler);
        //System.out.println(proxy.toString());
        System.out.println(proxy.work());
    }
}

- 输出结果

before invoke。。。
老师教书育人...
after invoke。。。
教书

 参考来源:http://t.csdn.cn/i2HGd

;