目录
十、Java动态代理InvocationHandler和Proxy
一、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。
参考来源:
三、线程休眠(阻塞)的几种方法
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()不需要。
四、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。。。
教书