- 在Spring配置文件中打开自动扫描功能,指定扫描的根目录(包)
- 为POJO配置各种注解,比如@Component是Bean,@Autowired用于注入等
- 通过容器获得组装好的Bean
注解Bean
注解Bean使用的注解是@Component,在之前的Java学习中,没有深入的学习注解,注解是给编译器看的,用于存放关于类的元信息,比如一些基础属性和配置等。注解的内容在今后也需要补上。 要让Spring开启自动扫描,必须在XML文件里配置context:component-scan标签,然后指定base-package属性为扫描的包,Spring会扫描这个包及其内部的所有子包中的注解,并且完成自动装配。 这里依然沿用原来已经编写好的几个Bean:Coach接口的BaseballCoach、CricketCoach、TrackCoach,FortuneService接口的HappyFortuneService,将它们复制到一个新包里,配置文件新建一个annoContext.xml,里边不用写任何bean标签,而是写这么一句:<context:component-scan base-package="annodemo"/>然后给BaseballCoach加上注解如下:
import org.springframework.stereotype.Component; @Component("myCoach") public class BaseballCoach implements Coach { private FortuneService fortuneService; public BaseballCoach() { } public BaseballCoach(FortuneService fortuneService) { this.fortuneService = fortuneService; } @Override public String getDailyWorkout() { return "I am BaseballCoach"; } @Override public String getDailyFortune() { return "I am BaseballCoach " + fortuneService.getFortune(); } }要注意的有,首先需要导入Component所在的包,其次@Component可以带参数也可以不带参数,如果带了字符串参数,就相当于XML中Bean的id属性,如果不带,则Bean的id会自动被设置为类名的第一个字母小写之后的名字。 这里因为还没有用到注入,所以我们要写一个空参的构造器,否则因为有了注入构造器,会报错。 然后新建一个annoApp.java作为应用,再其中创建容器和获取Bean:
import org.springframework.context.support.ClassPathXmlApplicationContext; public class annoApp { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annoContext.xml"); BaseballCoach baseballCoach = context.getBean("myCoach", BaseballCoach.class); System.out.println(baseballCoach.getDailyWorkout()); } }运行之后正常调用了方法,如果此时去掉注解中的参数,那么在getBean的时候使用"baseballCoach"作为第一个参数就能够获取到Bean
依赖注入--构造器注入
依赖注入需要两个注解,@Component和@Autowired: 像上一个例子一样,我们需要把实现了FortuneService接口的HappyFortuneService注入到这几个具体的Coach实现类中,Spring会先扫描所有的@Component做成Bean,然后找到@Autowired进行注入。 构造器注入的步骤和之前很类似:- 定义依赖关系,将所有需要的类做成Bean
- 编写能够接受依赖注入的构造器
- 使用@Autowired注解需要注入的方法
//把HappyFortuneService配置成Bean import org.springframework.stereotype.Component; @Component public class HappyFortuneService implements FortuneService { public String getFortune() { return "Today is your lucky day!"; } } import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class BaseballCoach implements Coach { private FortuneService fortuneService; public BaseballCoach() { } @Autowired public BaseballCoach(FortuneService fortuneService) { this.fortuneService = fortuneService; } @Override public String getDailyWorkout() { return "I am BaseballCoach"; } @Override public String getDailyFortune() { return "I am BaseballCoach " + fortuneService.getFortune(); } }这里的关键就是@Autowired,Spring会在这个被注解的构造器中,寻找符合实现了FortuneService接口类型要求的Bean来进行装配 如果有两个FortuneService实现类,那么会报错,提示有超过一个实现了FortuneService接口的Bean。这个问题会在后边通过Qualifier来解决。
依赖注入--Setter注入
这一次我们使用已经编写好了接受Setter注入的CricketCoach类来进行Setter注入,其实很简单,只要对setter方法进行注解即可:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CricketCoach implements Coach { private FortuneService fortuneService; // 定义好接受依赖对象的变量 private String emailAddress; private String team; @Override public String getDailyWorkout() { return "I am CricketCoach"; } @Override public String getDailyFortune() { return "I am CricketCoach " + fortuneService.getFortune(); } @Autowired public void setFortuneService(FortuneService fortuneService) { this.fortuneService = fortuneService; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public void setTeam(String team) { this.team = team; } public String getEmailAddress() { return emailAddress; } public String getTeam() { return team; } }获取Bean并且调用一下方法看看:
CricketCoach cricketCoach = context.getBean("cricketCoach", CricketCoach.class); System.out.println(cricketCoach.getDailyFortune());也完成了依赖注入。Spring这里是一样的,也是会去寻找实现了FortuneService接口的Bean然后注入。
依赖注入--方法注入
实际上,@Autowired可以用来将依赖对象注入任何方法,不光是setter方法,我们在CricketCoach中写另外一个任意命名的方法,其中和setter方法一样设置属性,然后注释掉刚才的setter方法:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CricketCoach implements Coach { private FortuneService fortuneService; // 定义好接受依赖对象的变量 private String emailAddress; private String team; @Override public String getDailyWorkout() { return "I am CricketCoach"; } @Override public String getDailyFortune() { return "I am CricketCoach " + fortuneService.getFortune(); } // @Autowired // public void setFortuneService(FortuneService fortuneService) { // this.fortuneService = fortuneService; // System.out.println(fortuneService); // } //新编写的自己命名的方法,该方法符合注入方式 @Autowired public void doSomething(FortuneService fortuneService) { this.fortuneService = fortuneService; System.out.println("A normal method use @Autowired"); System.out.println(fortuneService); } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public void setTeam(String team) { this.team = team; } public String getEmailAddress() { return emailAddress; } public String getTeam() { return team; } }不需要对应用的代码进行任何修改,直接再执行,可以发现结果是:
A normal method use @Autowired annodemo.HappyFortuneService@4b9e255 I am CricketCoach Today is your lucky day!可见Spring在装配Bean的时候先执行了该方法,然后才是获取Bean并调用Bean的方法。
依赖注入--Field注入
顾名思义,这次是注入域也就是成员变量的值了,这个技术是利用了Java的反射技术,不仅是普通变量,私有变量也可以进行注入。 与XML配置不同的是,使用@Autowired进行域注入,不需要配置setter方法,直接就能注入,只需要用@Autowired注解需要注入的成员变量声明即可。 将CricketCoach中的两个注入方法都注释掉,然后直接用@Autowired注解private FortuneService fortuneService;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CricketCoach implements Coach { @Autowired private FortuneService fortuneService; private String emailAddress; private String team; @Override public String getDailyWorkout() { return "I am CricketCoach"; } @Override public String getDailyFortune() { return "I am CricketCoach " + fortuneService.getFortune(); } // @Autowired // public void setFortuneService(FortuneService fortuneService) { // this.fortuneService = fortuneService; // System.out.println(fortuneService); // } //新编写的自己命名的方法,该方法符合注入方式 // @Autowired // public void doSomething(FortuneService fortuneService) { // this.fortuneService = fortuneService; // System.out.println("A normal method use @Autowired"); // System.out.println(fortuneService); // } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public void setTeam(String team) { this.team = team; } public String getEmailAddress() { return emailAddress; } public String getTeam() { return team; } }其他不用做任何修改,直接运行应用,发现一切正常。
从多个类中指定被注入的类
如果项目中除了HappyFortuneService,还有JoyFortuneService,RandomFortuneService等等实现类,如果直接去注入,Spring会报错如下(部分错误信息):nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'annodemo.FortuneService' available: expected single matching bean but found 2: happyFortuneService,joyFortuneService可以看到,错误是一个NoUniqueBeanDefinitionException,即没有特定Bean定义错误,之后说找到了2个实现了FortuneService接口的类。看上去我们只要通过某种方式指定一个具体的Bean就可以了。 指定的方式就是在@Autowired注解之下再增加一个@Qualifier注解,其中的参数指定特定的名称即可。 我们修改原来的HappyFortuneService的方法,加上打印自己的名称,再新增一个JoyFortuneService,然后为CricketCoach添加@Qualifier:
//HappyFortuneService.java import org.springframework.stereotype.Component; @Component public class JoyFortuneService implements FortuneService { public String getFortune() { return "Today is your lucky day for JoyFortuneService!"; } } //JoyFortuneService.java import org.springframework.stereotype.Component; @Component public class HappyFortuneService implements FortuneService { public String getFortune() { return "Today is your lucky day for HappyFortuneService!"; } } //CricketCoach.java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component public class CricketCoach implements Coach { @Autowired @Qualifier("happyFortuneService") private FortuneService fortuneService; private String emailAddress; private String team; @Override public String getDailyWorkout() { return "I am CricketCoach"; } @Override public String getDailyFortune() { return "I am CricketCoach " + fortuneService.getFortune(); } ...... }这个时候运行,会提示BaseballCoach创建失败,回头去看看,原来是因为我们刚才在BaseballCoach中配置的构造器注入也没有@Qualifier:
@Autowired public BaseballCoach(FortuneService fortuneService) { this.fortuneService = fortuneService; }这其中没有指定具体的Bean,如果直接写上一行,会提示不能这么写,实际上@Qualifier用于构造器注入,需要写成这样:
//BaseballCoach.java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @Component public class BaseballCoach implements Coach { private FortuneService fortuneService; public BaseballCoach() { } //@Qualifier用于构造器注入 @Autowired public BaseballCoach(@Qualifier("joyFortuneService") FortuneService fortuneService) { this.fortuneService = fortuneService; } @Override public String getDailyWorkout() { return "I am BaseballCoach"; } @Override public String getDailyFortune() { return "I am BaseballCoach " + fortuneService.getFortune(); } }之后在应用里执行一下下列代码:
System.out.println(context.getBean("cricketCoach", CricketCoach.class).getDailyFortune()); System.out.println(context.getBean("baseballCoach", BaseballCoach.class).getDailyFortune());可以发现,CricketCoach使用的是HappyFortuneService,而BaseballCoach使用的是JoyFortuneService,这样就解决了这个问题。 之后是豆知识里提到的关于Bean的Scope和自定义初始化和销毁方法的注解配置
@Scope注解
Scope属性的注解就在@Component之下使用@Scope("singleton")
或者其他参数来配置。
现在把BaseballCoach配置为单例,CricketCoach配置为prototype,再通过应用试验一下:
@Component @Scope("singleton") public class BaseballCoach implements Coach {......} @Component @Scope("prototype") public class CricketCoach implements Coach {......} //应用里的代码: ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annoContext.xml"); CricketCoach cricketCoach1 = context.getBean("cricketCoach", CricketCoach.class); CricketCoach cricketCoach2 = context.getBean("cricketCoach", CricketCoach.class); System.out.println(cricketCoach1==cricketCoach2); System.out.println(cricketCoach1 + " | " + cricketCoach2); BaseballCoach baseballCoach1 = context.getBean("baseballCoach", BaseballCoach.class); BaseballCoach baseballCoach2 = context.getBean("baseballCoach", BaseballCoach.class); System.out.println(baseballCoach1 == baseballCoach2); System.out.println(baseballCoach1 + " | " + baseballCoach2);运行之后可以看到成功的配置了单例或者prototype。
自定义初始方法和销毁方法的注解
初始方法的注解是@PostConstruct,而销毁前方法的注解是@PreDestroy。 就给BaseballCoach加上两个自定义方法,在Autowired里加一行显示,然后用这两个注解试验一下:import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Component @Scope("singleton") public class BaseballCoach implements Coach { private FortuneService fortuneService; public BaseballCoach() { } @Autowired public BaseballCoach(@Qualifier("joyFortuneService") FortuneService fortuneService) { System.out.println("This is auto-wired constructor."); this.fortuneService = fortuneService; } @Override public String getDailyWorkout() { return "I am BaseballCoach"; } @Override public String getDailyFortune() { return "I am BaseballCoach " + fortuneService.getFortune(); } @PostConstruct public void myInitMethod() { System.out.println("This is my init-method"); } @PreDestroy public void myDestroyMethod() { System.out.println("This is my destroy-method"); } }然后修改一下应用随便调用一下:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annoContext.xml"); System.out.println(context.getBean("baseballCoach", BaseballCoach.class).getDailyFortune()); context.close();可以看到结果是:
This is auto-wired constructor. This is my init-method I am BaseballCoach Today is your lucky day for JoyFortuneService! This is my destroy-method关于用这两个注释有些需要注意的地方:
- 方法的修饰符不受限制,public,private,protect均可
- 返回值一般为void,因为即使有返回值,也无法从这两个方法拿到返回值
- 方法名可以任意起
- 方法不能带有参数,必须是无参的
几个小要点
@Autowired不指定Bean名称的时候,会将类名的首字母自动小写来作为Bean的id,然而如果这个类的第一个和第二个字母都大写,则不会自动转换名称,会将类名作为Bean的id。@Autowired和@Qualifier用于构造器注入的时候写法比较特别,上边已经讲过了,需要注意。
与XML配置相比,这里没有讲注入字面量,注入字面量的方式依然需要用到XML配置,不能够直接注入,注入的方法是:
- XML里依然需要配置<context:property-placeholder location="classpath:sport.properties"/>
- 使用@Value配合Spring表达式来注解对应的域,比如:
@Value("${foo.email}") private String email; @Value("${foo.team}") private String team;