每日十道面试题(七)
每天看一看小知识增加一下
1. 谈一谈Springboot的启动类注解
@springbootApplication其实是一个复合注解,它其实里面由==@SpringbootConfiguration+@EnableAutoConfiguration+@ComponentScan==三大打手,第一个其实就是标明主启动类是一个配置类,因为点进去你会发现它就是一个@Configuration的注解,这里先要声明了解一个javaConfig配置的形式@Configuration+@Bean可以配置到ioc容器中
然后我们接下来重点说剩下两个,@ComponentScan的功能是扫描符合条件的组件把,具体我们点进去看其实它的功能就是自动扫描并且加载符合条件的组件,讲那些@Component或者那些bean加载到ioc容器中,当然我们可以通过basePackges来特定一些包来扫描,如果不指定的话就默认扫描==@ComponentScan该类所在的package进行扫描==
然后讲一下@EnableAutoConfiguration,这个最重要,它了。内部有一个@AutoConfigurationPackage+@import,一个是自动配置包,一个是导入自动配置的组件
@AutoConfigurationPackage它里面注册了一个bean然后它==new packageImport(metadata).getPackageName(),==这一句话是说明返回了当前主程序的同级和子级的包组件,这也是为什么我们一般那些包都和启动类同级
接下来说@import它导入的是一个AutoConfigurationImportSelector.class这个类又继承了ImportSelector,这个里面它加载了WEB/INF/spring.factories外部文件,里面有很多自动配置的类信息,然后这个自动导入选择器会选择符合条件的也就是你需要用到的然后它会自动配置到ioc容器中
它为什么能自动配置到ioc去,因为SpringFactoriesLoader工具类,它是来帮助从外部文件加载配置的,将那个org.springfamework.boot.autofigure.EnabletoConfiguration对应的配置项通过反射实例化为javaConfig形式注入到ioc容器中,后面开启了自动配置的功能,帮助寻找配置文件然后加载到ioc容器
2. 你了解反射,那你知道动态代理吗?spring中是用哪一种,为什么不用其它的
实际上这里挖了个坑,就是看你对源码了不了解,
动态代理可以在运行期间动态的获取目标源的信息,从而执行它的方法,静态代理是编译期就确定好的,实质是代码改变的话,动态代理比较好,不经常变需求的话,静态代理比较好
下面是jdk动态代理
public class ProxyHandller implements InvocationHandler {
private Object target;
public ProxyHandller(Object target){
this.target=target;
}
public Object getProxy() throws Exception {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=method.invoke(target,args);
return result;
}
}
实际jdk是需要代理接口的,通过获取代理的接口类加载器和接口,来获取接口类的实例,从而再利用实例调用invoke反射执行接口的方法
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
-
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
-
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
-
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目标对象
//重写拦截方法
@Override
public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
System.out.println("Cglib动态代理,监听开始!");
Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组
System.out.println("Cglib动态代理,监听结束!");
return invoke;
}
//定义获取代理对象方法
public Object getCglibProxy(Object objectTarget){
//为目标对象target赋值
this.target = objectTarget;
Enhancer enhancer = new Enhancer();
//设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(objectTarget.getClass());
enhancer.setCallback(this);// 设置回调
Object result = enhancer.create();//创建并返回代理对象
return result;
}
大致流程和jdk动态代理没差,只是他有用到enhancer,这只是个工具类方便用来实例成子类对象的,然后继承父类
spring其实两种都可以使用,没有说固定了哪一种,但是有接口的话一般都是默认jdk,因为强制使用cglib的话还要加一串代码,所以一般没有用cglib,而且cglib重写父类会覆盖方法,通过修改其字节码生成子类来处理,个人感觉效率上会比jdk慢很多,毕竟涉及到了字节码,但是没有接口的话,spring会强制转换成cglib代理
有接口但想强制使用CGLIB实现AOP:
(1)添加CGLIB库,SPRING_HOME/cglib/*.jar
(2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
3.讲一下SpringMVC的拦截器
实际上就是讲HandlerInterceptor这个类,我们可以通过实现这个接口,然后重写里面的
preHandle:调用Controller之前执行,可以利用这个做权限啥的拦截比如shiro和Sercurity,
当定义多个拦截器的时候,只要一个返回为false,Controller就会不执行,
posthandler: 调用Controller之后执行
afterCompletion: 无视异常照常执行,且在在preHandle返回为true的时候可以执行,比如页面渲染后
preHandle(true)->preHandle(false)仍然会执行返回true的afterCompletion
这个实现原理和Aop一样织入日志和事务,在执行方法前后加上拦截器
4. SpringMVC的拦截器和Filter过滤器的区别
- 功能相同: 两者无太大差别,都是先实现拦截,再做自己的操作
- 容器不同: 拦截器是构建在SpringMVC里面,Filter是构建在Servlet容器上
- 便利性不同:拦截器有三个时机,而过滤器仅提供一个方法,如果要实现其它的还要自己去写,
- 如果纯粹的拦截一些东西的话我觉得过滤器比较好,要是需要返回值或者判断什么东西,加上一些操作的话,比如动态代理一样,加入日志等我还是推荐拦截器
比如Cloud里面网关的过滤阿,shiro,springSecuritty的拦截,静态资源的放行,过滤请求等很多地方都用到
5. 反射中,Class.forName 和ClassLoader区别?
这两者,都可用来对类进行加载。差别在于:
●Class#forName(…) 方法,除了将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static 块。
●ClassLoader只干一-件事情,就是将 .class文件加载到JVM中,不会执行static 中的内容,只有在newInstance才会去执行static块。
Class.forName(name, initialize, loader)方法,带参函数也可控制是否加载static 块,并且只有调用了newInstance方法才能调用构造函数,创建类的对象。
6. 迭代器Iterator是什么?和ListIterator有什么区别
Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Iterator:它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。ListIterator是专门为遍历List而存在的
而ListIterator继承了Iterator,扩展了更多关于list的方法
使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类
APi比较:
1) add(E e) 将指定的元素插入列表,插入位置为迭代器当前位置之前
2) set(E e) 迭代器返回的最后一个元素替换参数e
3) hasPrevious() 迭代器当前位置,反向遍历集合是否含有元素
4) previous() 迭代器当前位置,反向遍历集合,下一个元素
5) previousIndex() 迭代器当前位置,反向遍历集合,返回下一个元素的下标
6) nextIndex() 迭代器当前位置,返回下一个元素的下标
//使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类
ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能
ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以
ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以
ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改
ps:如果map要使用lterator,首先要转成set,再使用
Set<Map.Entry<Integer,String>> set = map.entrySet();
Iterator<Map.Entry<Integer,String>> iterator = set.iterator();
while (iterator.hasNext()){
Map.Entry<Integer,String> entry = iterator.next();
System.out.println(entry.getKey()+"="+entry.getValue());
}
7. 什么是守护线程?常见的有哪些?
分为(用户)前台线程和后台线程,只有前台线程都结束了,后台线程才会慢慢结束,后台线程就叫做守护线程,比如我们经常用的docker可以后台运行镜像,包括服务器挂载jar包可以用后台线程执行,
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
- setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
- 守护线程创建的线程也是守护线程
- 守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
一般来说常见都有垃圾回收线程也就是gc线程是守护线程
8 . 动态规划(dp)和递归的区别
比如一个例子:斐波那契数列fn=fn-1+fn-2
如果我们使用递归也能循环调用自身,通过f2=1,f1=1这出口完成计算,但是每一步都是要算到出口的,就是没有记录状态和结果,过程就会可能计算重复的步骤,dp正是记录了每一步的结果和状态,减少了重复计算的时间复杂度
那么递归为什么时间比较久?
原因是递归调用方法的话,会不停的出栈入栈,这个可想而知
ps:递归是通过栈中子帧返回结果,然后出栈返回上一级父帧,个人感觉递归还是有点自上向下的,因为不停往下找,而dp记录了每一步结果,算下步的话,直接自下而上就可以返回了,这些是个人感觉,可不作讨论
9 .阐述静态变量和实例变量的区别,还有局部变量?
不管创建多少个对象,静态变量在内存中有且仅有一个;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。,局部变量在执行完方法后,局部变量会被清除
静态变量是类链接的时候就开始赋值初值了,实例变量和局部变量在初始化过程,按顺序执行
10 .说下原生JDBC操作数据库流程?
● 第一步:Class.forName()加载数据库连接驱动
● 第二步:DriverManager.getConnection()获取数据连接对象;
● 第三步:根据SQL获取sql会话对象,有2种方式 Statement、PreparedStatement ;
● 第四步:执行SQL,执行SQL前如果有参数值就设置参数值setXXX();
● 第五步:处理结果集;
● 第六步:关闭结果集、关闭会话、关闭连接
public class JdbcTest {
public static void main(String[] args) throws Exception {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mybatis";
String user = "root";
String password = "root";
//获取连接
con = DriverManager.getConnection(url, user, password);
String sql = "select * from tb_user where id = ? ";
//获取数据库操作对象
ps = con.prepareStatement(sql);
//设置参数
ps.setLong(1, 1);
rs = ps.executeQuery();
//处理结果集
while(rs.next()){
System.out.println(rs.getString("name"));
System.out.println(rs.getInt("age"));
System.out.println(rs.getInt("sex"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
try {
if (rs!=null) {
rs.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (ps!=null) {
ps.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (con!=null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
ps:jdbc这个流程和消息中间件的流程代码很像,比如我之前学的ActiveMQ,学习方式学会迁移哦