什么是注解
传统的 Spring 做法是使用 .xml 文件来对 bean 进行注入或者是配置 AOP、事务,这么做有两个缺点:
-
如果所有的内容都配置在 .xml 文件中,那么 .xml 文件将会十分庞大;如果按需求分开 .xml 文件,那么 .xml 文件又会非常多。总之这将导致配置文件的可读性与可维护性变得很低
-
在开发中在 .java 文件和 .xml 文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率
为了解决这两个问题,Spring 引入了注解,通过“@XXX”的方式,让注解与 Java Bean 紧密结合,既大大减少了配置文件的体积,又增加了 Java Bean 的可读性与内聚性。
本篇文章,讲讲最重要的三个 Spring 注解,也就是 @Autowired、@Resource 和 @Service。
不使用注解
先看一个不使用注解的 Spring 示例,在这个示例的基础上,改成注解版本的,这样也能看出使用与不使用注解之间的区别,先定义一个老虎:
public class Tiger
{
private String tigerName = "TigerKing";
public String toString()
{
return "TigerName:" + tigerName;
}
}
再定义一个猴子:
public class Monkey
{
private String monkeyName = "MonkeyKing";
public String toString()
{
return "MonkeyName:" + monkeyName;
}
}
定义一个动物园:
public class Zoo
{
private Tiger tiger;
private Monkey monkey;
public void setTiger(Tiger tiger)
{
this.tiger = tiger;
}
public void setMonkey(Monkey monkey)
{
this.monkey = monkey;
}
public Tiger getTiger()
{
return tiger;
}
public Monkey getMonkey()
{
return monkey;
}
public String toString()
{
return tiger + "/n" + monkey;
}
}
Spring 的配置文件这么写:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd"
default-autowire="byType">
<bean id="zoo" class="com.xrq.bean.Zoo" >
<property name="tiger" ref="tiger" />
<property name="monkey" ref="monkey" />
</bean>
<bean id="tiger" class="com.xrq.domain.Tiger" />
<bean id="monkey" class="com.xrq.domain.Monkey" />
</beans>
@Autowired
@Autowired 顾名思义,就是自动装配,其作用是为了消除 Java 代码里面的 getter/setter 与 xml bean 标签中的 property。当然,getter 看个人需求,如果私有属性需要对外提供的话,应当予以保留。
因此,引入 @Autowired 注解,先看一下 Spring 配置文件怎么写:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns="http://www.springframework.org/schema/beans"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
7 http://www.springframework.org/schema/context
8 http://www.springframework.org/schema/context/spring-context-4.2.xsd">
9
10 <context:component-scan base-package="com.xrq" />
11
12 <bean id="zoo" class="com.xrq.bean.Zoo" />
13 <bean id="tiger" class="com.xrq.domain.Tiger" />
14 <bean id="monkey" class="com.xrq.domain.Monkey" />
15
16 </beans>
注意第 10 行,使用必须告诉 Spring 一下我要使用注解了,告诉的方式有很多,<context:component-scan base-package="xxx" />
是一种最简单的,Spring 会自动扫描 xxx 路径下的注解。
看到第 12 行,原来 zoo 里面应当注入两个属性 tiger、monkey,现在不需要注入了。再看下,Zoo.java 也很方便,getter/setter 都可以去掉:
public class Zoo
{
@Autowired
private Tiger tiger;
@Autowired
private Monkey monkey;
public String toString()
{
return tiger + "/n" + monkey;
}
}
这里 @Autowired 注解的意思就是,当 Spring 发现 @Autowired 注解时,将自动在代码上下文中找到和其匹配(默认是类型匹配)的 Bean,并自动注入到相应的地方去。
有一个细节性的问题是,假如 xml bean 标签里面有两个 property,Zoo.java 里面又去掉了属性的 getter/setter 并使用 @Autowired 注解标注这两个属性那会怎么样?答案是 Spring 会按照 xml 优先的原则去 Zoo.java 中寻找这两个属性的 getter/setter,导致的结果就是初始化 bean 报错。
OK,假设此时我把 .xml 文件的 13 行、14 行两行给去掉,再运行,会抛出异常:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'Zoo': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.xrq.domain.Tiger com.xrq.bean.Zoo.ttiger; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xrq.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:835)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.xrq.test.MyTest.main(MyTest.java:13)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.xrq.domain.Tiger com.xrq.bean.Zoo.ttiger; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xrq.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:571)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
... 13 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xrq.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1373)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1119)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:543)
... 15 more
因为,@Autowired 注解要去寻找的是一个 Bean,Tiger 和 Monkey 的 Bean 定义都给去掉了,自然就不是一个 Bean 了,Spring 容器找不到也很好理解。那么,如果属性找不到我不想让 Spring 容器抛出异常,而就是显示 null,可以吗?可以的,其实异常信息里面也给出了提示了,就是将 @Autowired 注解的 required 属性设置为 false 即可:
public class Zoo
{
@Autowired(required = false)
private Tiger tiger;
@Autowired(required = false)
private Monkey monkey;
public String toString()
{
return tiger + "/n" + monkey;
}
}
此时,找不到 tiger、monkey 两个属性,Spring 容器不再抛出异而是认为这两个属性为 null。
@Autowired 接口注入
上面的比较简单,我们只是简单注入一个 Java 类,那么如果有一个接口,有多个实现,Bean 里引用的是接口名,又该怎么做呢?比如有一个 Car 接口:
public interface Car
{
public String carName();
}
两个实现类 Audi 和 Benz:
@Service
public class Audi implements Car
{
public String carName()
{
return "Audi car";
}
}
@Service
public class Benz implements Car
{
public String carName()
{
return "Benz car";
}
}
写一个 CarFactory,引用 Car:
@Service
public class CarFactory
{
@Autowired
private Car car;
public String toString()
{
return car.carName();
}
}
不用说,一定是报错的,Car 接口有两个实现类,Spring 并不知道应当引用哪个实现类。这种情况通常有两个解决办法:
-
删除其中一个实现类,Spring 会自动去 base-package 下寻找 Car 接口的实现类,发现 Car 接口只有一个实现类,便会直接引用这个实现类
-
实现类就是有多个该怎么办?此时可以使用 @Qualifier 注解:
@Service public class CarFactory { @Autowired @Qualifier("audi") private Car car; public String toString() { return car.carName(); } }
注意 @Qualifier 注解括号里面的是 Car 接口实现类的 bean id。
@Resource
把 @Resource 注解放在 @Autowired 下面说,是因为它们作用非常相似,这个就简单说了,例子过后点明一下 @Resource 和 @Autowired 的区别。先看一下 @Resource,直接写 Zoo.java 了:
@Service
public class Zoo
{
@Resource(name = "tiger")
private Tiger tiger;
@Resource(type = Monkey.class)
private Monkey monkey;
public String toString()
{
return tiger + "/n" + monkey;
}
}
这是详细一些的用法,说一下 @Resource 的装配顺序:
-
@Resource 后面没有任何内容,默认通过 name 属性去匹配 bean,找不到再按 type 去匹配
-
指定了 name 或者 type 则根据指定的类型去匹配 bean
-
指定了 name 和 type 则根据指定的 name 和 type 去匹配 bean,任何一个不匹配都将报错
然后,区分一下 @Autowired 和 @Resource 两个注解的区别:
-
@Autowired 默认按照 byType 方式进行 bean 匹配,@Resource 默认按照 byName 方式进行 bean 匹配
-
@Autowired 是 Spring 的注解,@Resource 是 J2EE 的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了
Spring 属于第三方的,J2EE 是 Java 自己的东西,因此,建议使用 @Resource 注解,以减少代码和 Spring 之间的耦合。
@Service
上面这个例子,还可以继续简化,因为 Spring 的配置文件里面还有 12 行~14 行三个 bean,下一步的简化是把这三个 bean 也给去掉,使得 Spring 配置文件里面只有一个自动扫描的标签,增强 Java 代码的内聚性并进一步减少配置文件。
要继续简化,可以使用 @Service。先看一下配置文件,当然是全部删除了:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="com.xrq" />
</beans>
是不是感觉很爽?起码我觉得是的。OK,下面以 Zoo.java 为例,其余的 Monkey.java 和 Tiger.java 都一样:
@Service
public class Zoo
{
@Autowired
private Tiger ttiger;
@Autowired
private Monkey mmonkey;
public String toString()
{
return ttiger + "/n" + mmonkey;
}
}
这样,Zoo.java 在 Spring 容器中存在的形式就是“zoo”,即可以通过 ApplicationContext 的 getBean(“zoo”) 方法来得到 Zoo.java。@Service 注解,其实做了两件事情:
-
声明 Zoo.java 是一个 bean,这点很重要,因为 Zoo.java 是一个 bean,其他的类才可以使用 @Autowired 将 Zoo 作为一个成员变量自动注入
-
Zoo.java 在 bean 中的 id 是“zoo”,即类名且首字母小写
如果,我不想用这种形式怎么办,就想让 Zoo.java 在 Spring 容器中的名字(id)叫做“Zoo”,可以的:
@Service("Zoo")
@Scope("prototype")
public class Zoo
{
@Autowired
private Monkey monkey;
@Autowired
private Tiger tiger;
public String toString()
{
return "MonkeyName:" + monkey + "/nTigerName:" + tiger;
}
}
这样,就可以通过 ApplicationContext 的 getBean(“Zoo”) 方法来得到 Zoo.java 了。
这里我还多加了一个 @Scope 注解,应该很好理解。因为 Spring 默认产生的 bean 是单例的,假如我不想使用单例怎么办,xml 文件里面可以在 bean 里面配置 scope 属性。注解也是一样,配置 @Scope 即可,默认是“singleton”即单例,“prototype”表示原型即每次都会 new 一个新的出来。
补充细节
最后再补充一个我发现的细节。假如 animal 包下有 Tiger、domain 包下也有 Tiger,它们二者都加了 @Service 注解,那么在 Zoo.java 中即使明确表示我要引用的是 domain 包下的 Tiger,程序运行的时候依然会报错。
细想,其实这很好理解,两个 Tiger 都使用 @Service 注解标注,意味着两个 Bean 的名字都是“tiger”,那么我在 Zoo.java 中自动装配的是哪个 Tiger 呢?不明确,因此,Spring 容器会抛出 BeanDefinitionStoreException 异常,Caused by:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'monkey' for bean class [com.xrq.domain.Monkey]
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/279489.html