这篇文章说的到内容不是很难,但是在实际开发场景中经常用的,并且很容易会被问到的。下面一起看看今天要说的两道面试题。
Mapper只是一个接口并没有具体的方法实现,它是如何实现数据操作的?
在mybatis中对Mapper虽然是接口类,没有具体的实现,但是在mybatis的执行过程中,通过了动态代理的方式生成了对应的代理对象,在mybatis的binding包下面有主要的几个类分别是MapperMethod
、MapperProxy
、MapperProxyFactory
和MapperRegistry
。在MapperRegistry
中的getMapper
方法中会构建MapperProxyFactry
对象,然后调用MapperProxyFactory
中的newInstance
方法,此方法会根据接口类生成对应的代理对象MapperProxy
,此时使用的是JDK代理方式。在业务代码中调用接口类中方法的时候,实际是调用MapperProxy
内的invoke
方法,在invoke
方法的参数中有一个method,可以通过method构建出对应的MapperStatement
对象以及构建后执行的CRUD过程。
对应的MapperRegistry
中的源码如下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根据type到knowMappers中获取MapperProxyFactory实例
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//mapperProxyFactory生成对象实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
先是构建MapperProxyFactory
工厂类对象,然后调用newInstance
方法。
在工厂类中实现的源码:
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//构建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//返回创建完成的代理对象
return newInstance(mapperProxy);
}
在这里就是构建代理对象的过程,并将构建完成的代理对象返回。MapperProxy
实现了InvocationHandler
类,使用的JDK的代理方式。
String、StringBuffer、StringBuilder的区别?
String是字符串类型,创建后是不可修改的,在做字符串组装的时候,每次都是开辟一个新的空间存储,会产生很多失去引用的字符串值,这些值会在常量池中占用大量的空间,另外在开辟新的内存以及对象引用变化的过程也是比较耗费性能的。
StringBuffer是在jdk1.0就出现的,在创建StringBuffer对象的时候,对象内部会维护两个字段信息,分别存储字符串值value和字符串的长度count,字符串值采用的是char类型数组,初始大小是16。在组装字符串的时候,首先会去判断count和新加入字符串的长度之和needSpace是否已经大于value数组的大小,如果没有大于直接将新字符串放入到char中的空闲空间中,如果大于了char数组的长度,就要对value数组进行扩容,在扩容的时候首先是将char扩大两倍,如果这个两倍长度还是没有needSpace大,就直接让value的长度设置为needSpace,如果大于needSpace就直接采用。这里在扩容的时候采用的是两倍的方式,在组装次数多的时候,扩容的次数就会变少,因此涉及到的新内存开辟和引用的变化就会少,大大的提高了性能。另外StringBuffer是线程安全的,对每个方法都加了synchronized关键字,这样同时增加了锁的竞争,从而导致另一种新能的消耗。
StringBuilder是在jdk1.5出现的,这个类的内部实现逻辑和StringBuffer是相同的,但是不同之处在于StringBuilder是完全放开的,不考虑线程安全问题,没有进行加锁,从而解决了锁竞争对性能消耗。
综合来说,如果是少量的字符串组装就直接使用String即可,如果字符串组装量比较大,且涉及到多线程操作同一个字符串的组装,那么就要使用StringBuffer来做,能保证线程安全,如果是单线程的大量字符串组装,不涉及到线程安全问题,那么就选择StringBuilder,性能更高。
关于字符串的组装,上面的是文字描述部分,要是想具体的了解内部源码和实现过程,可以参考:组装字符串String、StringBuilder、StringBuffer比较