你真正了解@Resource和@Autowired这两个注解吗
我们一起来探讨这两个注解以及使用不当引发的案例
大部分人都知道@Resource是通过“beanName”注入,@Autowired是通过类型注入,但是真的只有这么简单吗?
先抛问题
项目中采用spring+mybatis框架,同时引入了zebra(https://github.com/Meituan-Dianping/Zebra),mybatis采用基于2.0方式(自定义DaoImpl,并且命名空间和方法自定义)。
如下面写法:
public interface DemoDao {
List<Demo> selectByIdList(List<Long> list)
}
public abstract class BaseDaoImpl {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
SqlSession getSqlSession() {
return sqlSessionTemplate;
}
}
@Repository
@Primary
public class DemoDaoImpl extends BaseDaoImpl implements DemoDao {
@Override
public List<Demo> selectByIdList(List<Long> list) {
return getSqlSession().selectList("demotest.Demo.selectByIdList", list);
}
}
@Service
public class DemoServiceImpl implements DemoService {
@Resource
private DemoDao demoDao;
@Override
public List<Demo> selectByIdList(List<Long> list) {
return getSqlSession().selectList("demotest.Demo.selectByIdList", list);
}
}
mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="demotest.Demo">
<select id="selectByIdList" >
...
</select>
</mapper>
这样的项目构建,在启动时是没有任何问题的,但是在运行时就会报异常:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.test.dao.DemoDao.selectByIdList
问题描述很清晰,异常提示找不到mapper文件的映射,但是项目里是有的,我们这时候把@Resource换成@Autowired试一下,程序正常运行,这是为啥呢??????
首先整理一下问题:
1、我们自定义的命名空间为:demotest.Demo,项目在运行时变为:com.test.dao.DemoDao
2、为什么使用@Resource就会报异常,而@Autowired却不会呢。
我们一起看下启动时mybatis\zebra都干了哪些事
我们在使用mybatis和spring集成的时候,一般都会把接口的包路径(com.demo.dao)配置在mybatis的配置下面,而这时候发生了很多事,我们来深入了解一下:
1、zebra.jar下面的ZebraMapperScannerConfigurer类将通过扫描“com.demo.dao”包下面所有的接口,对一些sql和其它配置做一些封装。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
String[] beanDefinitionNames = registry.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
String beanClassName = beanDefinition.getBeanClassName();
if(SqlSessionFactoryBean.class.getName().equals(beanClassName)){
beanDefinition.setBeanClassName(FixedSqlSessionFactoryBean.class.getName());
}
}
ZebraClassPathMapperScanner scanner = new ZebraClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
2、执行ZebraClassPathMapperScanner.doScan(String… basePackages)生成BeanDefinition并装载到spring的ioc容器里,并且调用“ZebraMapperFactoryBean”为每一个BeanDefinition生成beanClass。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '"
+ definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(ZebraMapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory",
new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName()
+ "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
3、ZebraClassPathMapperScanner.doScan方法调用了父类(ClassPathBeanDefinitionScanner)的doScan方法生成BeanDefinition,在生成BeanDefinition的时候调用AnnotationBeanNameGenerator.buildDefaultBeanName(BeanDefinition definition)生成beanName。
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
}
public class AnnotationBeanNameGenerator implements BeanNameGenerator {
protected String buildDefaultBeanName(BeanDefinition definition) {
String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
return Introspector.decapitalize(shortClassName);
}
}
public class Introspector {
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
}
这个时候可能有些细心的朋友就发现问题了,我们的dao层接口,在启动时生成了2个实现类装载到了spring的IOC容器中,一个为“DemoDaoImpl” ,另一个为mybatis生成了代理类,这两个实现的beanName分别为“demoDaoImpl”和“demoDao”,这时候是不是突然发现问题了??
1、使用@Resource注解通过“demoDao”找到了mybatis生成的代理类,这个代理类的sql命名空间为接口名和包路径。
2、使用@Autowired注解通过类型找,这是有两个实现类,我们自己的实现类中有@Primary这个优先级注解,因此没有问题。
3、这里spring还有一个小坑,如果接口名是”IDemoDao“,也就是前两个字母都是大写,那么beanName依然是”IDemoDao“。
我们来仔细刨析一下这两个注解的区别
@Resource
先举例:
@Resource(name="demoDao1")
private DemoDao demoDao
有些同学可能有疑惑,这样就找不到,我们详细说下@Resource的详细步骤
1、先通过name="demoDao1"去ioc容器找对应的bean,如果找不到,开始执行第二步。
2、以“DemoDao demoDao”的变量名作为beanName去ioc容器里寻找,如果这是还找不到,开始执行第三步
3、寻找DemoDao接口的实现类。
@Autowired
@Autowired
private DemoDao demoDao
详细步骤如下:
1、寻找DemoDao接口的实现类,这时如果有多个实现类,开始执行第二步。
2、查看实现类是有“@Primary”注解,或者在调用类中使用“@Qualifier("")”执行具体的实现类,如果都没有使用,开始执行第三步。
3、以“DemoDao demoDao”的变量名作为beanName,再次从ioc容器中查找,如果找不到抛出异常。
这个时候再回头看,是不是发现了端倪,给大家留一个思考题,我不用Zebra,就用spring和mybatis是否会出现这种情况?
备注:依然会出现这种情况,ZebraMapperScannerConfigurer这个类的实现,完全是copy的mybatis-spring.jar中实现。