Spring5:@Autowired注解、@Resource注解和@Service注解什么是注解
传统的Spring做法是使⽤.xml⽂件来对bean进⾏注⼊或者是配置aop、事物,这么做有两个缺点:
1、如果所有的内容都配置在.xml⽂件中,那么.xml⽂件将会⼗分庞⼤;如果按需求分开.xml⽂件,那么.xml⽂件⼜会⾮常多。总之这将导致配置⽂件的可读性与可维护性变得很低
2、在开发中在.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)
{
}
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="/2001/XMLSchema-instance"
xmlns="/schema/beans"
xmlns:context="/schema/context"
xsi:schemaLocation="/schema/beans
/schema/beans/spring-beans-4.2.xsd
/schema/context
/schema/context/spring-context-4.2.xsd"
default-autowire="byType">
<bean id="zoo" class="q.bean.Zoo">
<property name="tiger" ref="tiger"/>
<property name="monkey" ref="monkey"/>
</bean>
<bean id="tiger" class="q.domain.Tiger"/>
<bean id="monkey" class="q.domain.Monkey"/>
</beans>
都很熟悉,权当复习⼀遍了。
@Autowired
@Autowired顾名思义,就是⾃动装配,其作⽤是为了消除代码Java代码⾥⾯的getter/setter与bean属性中的property。当然,getter看个⼈需求,如果私有属性需要对外提供的话,应当予以保留。因此,引⼊@Autowired注解,先看⼀下spring配置⽂件怎么写:
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns:xsi="/2001/XMLSchema-instance"
3 xmlns="/schema/beans"
4 xmlns:context="/schema/context"
5 xsi:schemaLocation="/schema/beans
6 /schema/beans/spring-beans-4.2.xsd
7 /schema/context
8 /schema/context/spring-context-4.2.xsd">
9
10<context:component-scan base-package="q"/>
11
12<bean id="zoo" class="q.bean.Zoo"/>
13<bean id="tiger" class="q.domain.Tiger"/>
14<bean id="monkey" class="q.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,并⾃动注⼊到相应的地⽅去。
有⼀个细节性的问题是,假如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.BeanCreationExceptio 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.ateBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.Object(AbstractBeanFactory.java:305)
at org.springframework.beans.factory.Singleton(DefaultSin
gletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301)
at org.springframework.beans.factory.Bean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at t.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:835)
at t.fresh(AbstractApplicationContext.java:537)
at t.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
resource和autowired注解的区别at t.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
q.test.MyTest.main(MyTest.java:13)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: q.domain.q.iger; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No 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 [q.domain.Tiger] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Depe at org.springframework.beans.factory.support.Defaul
tListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1373)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1119)
at org.springframework.beans.factory.solveDependency(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();
}
两个实现类BMW和Benz:
@Service
public class BMW implements Car
{
public String carName()
{
return "BMW 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并不知道应当引⽤哪个实现类。这种情况通常有两个解决办法:
1、删除其中⼀个实现类,Spring会⾃动去base-package下寻Car接⼝的实现类,发现Car接⼝只有⼀个实现类,便会直接引⽤这个实现类
2、实现类就是有多个该怎么办?此时可以使⽤@Qualifier注解:
@Service
public class CarFactory
{
@Autowired
@Qualifier("BMW")
private Car car;
public String toString()
{
return car.carName();
}
}
注意@Qualifier注解括号⾥⾯的应当是Car接⼝实现类的类名,我之前试的时候⼀直以为是bean的名字,所以写了"bMW",结果⼀直报错。
@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的装配顺序:
1、@Resource后⾯没有任何内容,默认通过name属性去匹配bean,不到再按type去匹配
2、指定了name或者type则根据指定的类型去匹配bean
3、指定了name和type则根据指定的name和type去匹配bean,任何⼀个不匹配都将报错
然后,区分⼀下@Autowired和@Resource两个注解的区别:
1、@Autowired默认按照byType⽅式进⾏bean匹配,@Resource默认按照byName⽅式进⾏bean匹配
2、@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="/2001/XMLSchema-instance"
xmlns="/schema/beans"
xmlns:context="/schema/context"
xsi:schemaLocation="/schema/beans
/schema/beans/spring-beans-4.2.xsd
/schema/context
/schema/context/spring-context-4.2.xsd">
<context:component-scan base-package="q"/>
</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注解,其实做了两件事情:
1、声明Zoo.java是⼀个bean,这点很重要,因为Zoo.java是⼀个bean,其他的类才可以使⽤@Autowired将Zoo作为⼀个成员变量⾃动注⼊
2、Zoo.java在bean中的id是"zoo",即类名且⾸字母⼩写
如果,我不想⽤这种形式怎么办,就想让Zoo.java在Spring容器中的名字叫做"Zoo",可以的:
@Service
@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: t.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'monkey' for bean class [q.domain.Monkey] conflicts with existing, non-compatible bean definition of same name and
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论