org.springframework.beans.factory.BeanCreationException:
Error creating bean with name ‘fooController’:
Failed to introspect bean class [com.foo.FooController] for lookup method metadata: could not find class that it depends on;
nested exception is java.lang.NoClassDefFoundError: com/foo/Result
// 该方法的返回类型 ResponseResult extend Result
@RequestMapping("/getList")
public ResponseResult<Foo> getList(@RequestBody Query params){
//...
}
根据错误提示,在代码中并没有找到 Result 类直接使用。
再进一步看日志,introspect bean 出现的问题,推测是进行反射的操作异常。
通过查看方法的返回类型,其父类就是Result,但是在当前的工程中,并没有引入对应的maven 依赖。
结果就是编译通过,但是运行时异常。
引入需要的jar 到当前的工程(classpath)。
上述提到的introspect bean 过程,在Spring 中的相关源码
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) {
// Let's check for lookup methods here..
if (!this.lookupMethodsChecked.contains(beanName)) {
try {
// Spring 初次缓存beanClass 的相关method。
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
// ...
}
});
}
// 本次的异常打印日志
catch (NoClassDefFoundError err) {
throw new BeanCreationException(beanName, "Failed to introspect bean class [" + beanClass.getName() +
"] for lookup method metadata: could not find class that it depends on", err);
}
this.lookupMethodsChecked.add(beanName);
}
}
应用为了数据加密,引用了一个加密组件。使用的Maven 依赖,启动应用后,直接报错:
java.lang.NoClassDefFoundError: Could not initialize class com.foo.security.handle.AcesCipherText
一开始错误理解为
ClassNotFoundException
,以为是包下载、编译的问题,反复clean,update snapshot。后来才发现是 ClassLoader 试图加载类的定义时出现错误,是在加载到类文件之后发生的。
public final class AcesCipherTextHandle<E> extends BaseTypeHandler<E> {
public AcesPlainTextHandle() {
}
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
return AcesCipherTextService.getIns().getResult(rs, columnName);
}
static {
// 没有找到对应的配置文件,导致必要的加密参数为空,直接 throw RuntimeException。结果是Class加载失败。
// 发生在Class.forName() 调用过程中
LoadProperties.loadProps();
}
}
A class initialization block is a block of statements preceded by the
static
keyword that’s introduced into the class’s body.When the class loads, these statements are executed.
并发编程在实际生产应用中,如果使用不当,会有意想不到的诡异问题。
文章摘取一段Java AtomicLong 在统计功能上的使用,分析并发计数应该注意的点。
/**
* jetty 服务中,需要统计全局的连接数、请求数量之类的指标。提供了CounterStatistic 类用于统计这些数据。
* 其中,Statistic 就是借助AtomicLong 来实现total, maximum 数量的并发计数。
*
* @from org.eclipse.jetty.util.statistic.CounterStatistic
*/
public class CounterStatistic {
protected final AtomicLong _max = new AtomicLong();
protected final AtomicLong _curr = new AtomicLong();
/**
* 计数增加的操作,同时会更新max 的值。
* @param delta the amount to add to the count,传入负值就是减少操作
*/
public void add(final long delta) {
long value=_curr.addAndGet(delta);
// max 赋值并没有使用 AtomicLong#getAndSet, why?
// 因为在并发环境下,只有确定当前的max 是真正的最大值时,才能赋值到max 变量中。
long oldValue = _max.get();
// case1:如果第一次 value <= oldValue,那么直接跳过,max 维持不变。
// case2: 如果第一次 value > oldValue,但是因为并发问题,没有compareAndSet 成功。再比较时,value <= oldValue,则直接跳过。需要考虑重试赋值过程中并发的问题。
while (value > oldValue) {
// 如果赋值成功,则说明value 是此刻的最大值。
if (_max.compareAndSet(oldValue, value))
break;
oldValue = _max.get();
}
}
}
CAS 全称是 compare and swap
,是一种用于在多线程环境下实现同步功能的机制。
CAS 是一条 CPU 的原子指令( cmpxchg 指令),不会造成数据不一致问题。Java Unsafe提供的 CAS 方法(如 compareAndSwapXXX )底层实现即为 CPU 指令 cmpxchg。
Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力。
/**
* str 变量持有的是 String 对象的内存地址。传递到函数中时,形参str2 是str 值拷贝(内存地址)。
* 由于String 类的特殊性,重新赋值就相当于给str2 赋值新的内存地址。和原有的str 没有关系。
*
* 运行结果:“字符串修改后:zhangsan”
*/
public class TestStr {
public static void main(String[] args) {
String str = "zhangsan";
changeStr(str);
System.out.println("字符串修改后:"+str);
}
/**
* @param str2 函数的形参是被调用时所传实参的副本。引用类型,对应的value 是地址副本
*/
private static void changeStr(String str2) {
// 相当于 str2 = new String("lisi");
str2 = "lisi";
}
}
值传递(pass by value):在调用函数时,将实际参数复制一份传递到函数中,这样在函数中对参数进行修改,就不会影响到原来的实际参数;
When a parameter is pass-by-value, the caller and the callee method operate on two different variables which are copies of each other. Any changes to one variable don’t modify the other.
It means that while calling a method, parameters passed to the callee method will be clones of original parameters. Any modification done in callee method will have no effect on the original parameters in caller method.
引用传递(pass by reference): 在调用函数时,将实际参数的地址直接传递到函数中。这样在函数中对参数进行的修改,就会影响到实际参数;
When a parameter is pass-by-reference, the caller and the callee operate on the same object.
It means that when a variable is pass-by-reference, the unique identifier of the object is sent to the method. Any changes to the parameter’s instance members will result in that change being made to the original value.
在 Java 中,原始类型变量( Primitive variables )存储的就是实际的值,非原始变量存储的引用变量( reference variables )(指向它们所引用的对象的地址 the addresses of the objects)。
值和引用都存储在堆栈内存中 ( Both values and references are stored in the stack memory )。
ECMAScript中所有函数的参数都是按值传递的。- 《JavaScript高级程序设计》
var name = 'zhangsan';
function setName (n) {
n = 'lisi';
console.log(n); //output: lisi
}
setName(name);
console.log(name); //output: zhangsan
#include<iostream>
using namespace std;
void change(int &i) {
i = i+2;
}
int main() {
int i = 0;
cout << "Value of i before change is :" << i << endl;
change(i);
cout << "Value of i now is :" << i << endl;
}
使用方式
$ g++ change.cpp -o change
$ ./change
[Pass-By-Value as a Parameter Passing Mechanism in Java | Baeldung](https://www.baeldung.com/java-pass-by-value-or-pass-by-reference) |
<resultMap id="detailResultMap" type="com.foo.model.FooBase">
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<!-- 通过内嵌查询,实现类似Hibernate 级联查询的功能,支持延时查询-->
<collection property="detailList" column="id" javaType="java.util.ArrayList"
ofType="com.foo.model.FooDetail"
<!-- 注意:select 支持默认补充 namespace。如果查询语句在当前 namespace, 可以省略 -->
select="com.foo.dao.FooDetailMapper.selectByBaseId" fetchType="lazy"/>
</resultMap>
关键信息:
ResultMapping#nestedQueryId
/**
* mybatis 解析resultMap childNodes
*
* @see org.apache.ibatis.builder.xml.XMLMapperBuilder#buildResultMappingFromContext
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
// 关键属性select, 对应 ResultMapping#nestedQueryId
// Tips: 后续调用applyCurrentNamespace() 检测和补充查询方法的namespace,检测符号“.”
String nestedSelect = context.getStringAttribute("select");
// 注意,内嵌query 和内嵌resultMap 的两种使用方法,彼此互斥。
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
// 懒加载属性,默认 configuration.isLazyLoadingEnabled() = false。
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
}
嵌套查询的过程:先执行主查询,在返回结果ResultSet 匹配到ResultMap过程中,发现有nestedQuery,就会再次发起查询(或者lazy模式下,缓存查询的对象、方法和参数,必要的时候发起查询)
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
final String nestedQueryId = propertyMapping.getNestedQueryId();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
// 首先判断查询的参数不能为空
if (nestedQueryParameterObject != null) {
final Class<?> targetType = propertyMapping.getJavaType();
// 扩展 DeferredLoad 缓存机制,本次不做解读
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERED;
} else {
// 延时加载委托类ResultLoader,保存必要的查询信息,用于嵌套查询使用
// 必要的信息对应的是, BaseExecutor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
if (propertyMapping.isLazy()) {
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERED;
} else {
// 正常情况下,非延时,直接发起查询。BaseExecutor.query(...)
value = resultLoader.loadResult();
}
}
}
return value;
}
mybatis 延时查询原理:在将结果集转化为值对象时,会对映射到的属性进行实例化操作。借助动态代理,会将lazyLoader 作为属性添加到值对象中。真正对该值对象读取操作时,代理类拦截并发起真正的数据库查询。
注意:仅限嵌套查询才有延时查询的概念。
/**
* @see org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject
*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
// 值对象实例化,constructor.newInstance()
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 检测到存在嵌套查询,并且需要延时加载,对原对象进行代理操作。
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// proxy.callback = lazyLoader, 拦截getSet方法,在realObject 读写之前,结束延时的状态。
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();
return resultObject;
}
/**
* 借助查询委托类resultLoader,查询得到的结果,赋值到realObject,达到延时替换的目的。
* @see org.apache.ibatis.executor.loader.ResultLoaderMap.LoadPair#load
*/
public void load(final Object userObject) throws SQLException {
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
Namespaces are now required and have a purpose beyond simply isolating statements with longer, fully-qualified names.
为了隔离和区分 statements, result maps, caches,Mybatis 在xml 解析过程中,对针对一些id 进行默认的 namespace 补充。smart !!!
/**
* 在需要进行唯一识别的场景,会进行默认namespace 补充
* @see org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespace
*/
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {
return base;
}
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
return currentNamespace + "." + base;
}