譯文鏈接:http://websystique.com/spring/spring-auto-detection-autowire-component-scanning-example-with-annotations/
在本篇文章我們會看到Spring是如何通過component-scanning配置
,在沒有使用@Bean和
自動檢測到程序中配置的bean,並且自動裝配這些bean。@Configuration聲明bean,也沒有使用XML配置聲明bean的情況下,
對於component-scanning的配置,本文將使用
,當然,我們也會提供一份對應的XML配置來作為比較。@ComponentScan
注解
我們將創建一個典型的企業級應用示例,涉及不同的層(Service、DAO)。
如下是本工程的目錄結構
接下來開始往上面添加具體內容。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.spring</groupId> <artifactId>Spring4AutoScanning</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>Spring4AutoScanning</name> <properties> <springframework.version>4.0.6.RELEASE</springframework.version> <joda-time.version>2.3</joda-time.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${springframework.version}</version> </dependency> <!-- Joda-Time --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${joda-time.version}</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
這個示例,我們使用了Spring-core和Spring-context依賴,另外,還使用了JodaTime的LocalDate類來做一些日期計算,所以引入了joda-time依賴。
Spring配置類是用@Configuration
注解標注的,這些類包含了用@Bean
注解標注的方法,這些方法生成bean會交給Spring容器來管理。
package com.websystique.spring.configuration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "com.websystique.spring") public class AppConfig { }
你可能注意到上面的類是空的,沒有使用@Bean標注的方法,那麼bean從哪裡產生呢?
事實上,我們使用了@ComponentScan
注解,來幫助我們自動檢測bean
@ComponentScan(basePackages = "com.websystique.spring")
@ComponentScan注解的basePackages
屬性是一個包名,配置好後,將會在該包下查找所有使用特定注解標注的類,作為bean。
如下是一些常見的注解,被這些注解標注的類是一個bean,將會被自動檢測
@Repository
- 作為持久層的DAO組件.
@Service
- 作為業務層的Service組件.
@Controller
- 作為展現層的Controller組件.
@Configuration
- Configuration組件.
@Component
- 通用注解, 可以作為以上注解的替代.
注意上面的注解內部都是用@Component標注的,所以實際上你可以在任何地方使用@Component, 但是為了表達更加清晰的設計意圖,強烈建議根據不同情況使用不同的注解。
注意:在我們這裡例子,你甚至可以直接刪除配置類因為它並沒有包含任何@Bean注解標注的方法,在後面的main方法裡我們將會看到在這種情況下是如何掃描這些Bean。
另外,看下使用XML配置的情況,結果如下(命名為app-config.xml)
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.websystique.spring" /> </beans>
package com.websystique.spring.dao; import com.websystique.spring.model.Employee; public interface EmployeeDao { void saveInDatabase(Employee employee); }
package com.websystique.spring.dao; import org.springframework.stereotype.Repository; import com.websystique.spring.model.Employee; @Repository("employeeDao") public class EmployeeDaoImpl implements EmployeeDao{ public void saveInDatabase(Employee employee) { /* * Logic to save in DB goes here */ System.out.println("Employee "+employee.getName()+" is registered for assessment on "+ employee.getAssessmentDate()); } }
@Repository
注解標注該類作為一個持久層自動檢測的bean,參數employeeDao
為bean提供了一個名字,我們將會在主服務Bean裡注入該bean。
package com.websystique.spring.service; import org.joda.time.LocalDate; public interface DateService { LocalDate getNextAssessmentDate(); }
package com.websystique.spring.service; import org.joda.time.LocalDate; import org.springframework.stereotype.Service; @Service("dateService") public class DateServiceImpl implements DateService{ public LocalDate getNextAssessmentDate() { return new LocalDate(2015,10,10); } }
@Service
注解標注這個類為業務層自動檢測的bean,後續我們會將其注入到主服務bean中。
package com.websystique.spring.service; import com.websystique.spring.model.Employee; public interface EmployeeService { void registerEmployee(Employee employee); }
package com.websystique.spring.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.websystique.spring.dao.EmployeeDao; import com.websystique.spring.model.Employee; @Service("employeeService") public class EmployeeServiceImpl implements EmployeeService{ @Autowired private DateService dateService; @Autowired private EmployeeDao employeeDao; public void registerEmployee(Employee employee) { employee.setAssessmentDate(dateService.getNextAssessmentDate()); employeeDao.saveInDatabase(employee); } }
EmployeeService是我們的主服務類,可以看到,我們往這個類注入了DateService和EmployeeDao。被@Autowired
注解標注的dateService屬性,會被Spring的依賴注入自動裝配合適的Bean,由於我們已經使用@Service聲明了一個DateService Bean,所以該Bean將會被注入到這裡。類似的,被@Repository標注的EmployeeDao也會被注入到employeeDao屬性中。
如下是我們的實體類Employee
package com.websystique.spring.model; import org.joda.time.LocalDate; public class Employee { private int id; private String name; private LocalDate assessmentDate; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public LocalDate getAssessmentDate() { return assessmentDate; } public void setAssessmentDate(LocalDate assessmentDate) { this.assessmentDate = assessmentDate; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", assessmentDate=" + assessmentDate + "]"; } }
package com.websystique.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import com.websystique.spring.configuration.AppConfig; import com.websystique.spring.model.Employee; import com.websystique.spring.service.EmployeeService; public class AppMain { public static void main(String args[]){ AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); EmployeeService service = (EmployeeService) context.getBean("employeeService"); /* * Register employee using service */ Employee employee = new Employee(); employee.setName("Danny Theys"); service.registerEmployee(employee); context.close(); } }
運行上面的程序,會看到如下結果:
Employee Danny Theys is registered for assessment on 2016-12-22
另外,假如你想不使用配置類AppConfig
,那麼還可以這樣做:
package com.websystique.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.websystique.spring.model.Employee; import com.websystique.spring.service.EmployeeService; public class AppMain { public static void main(String args[]){ //AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan("com.websystique.spring"); context.refresh(); EmployeeService service = (EmployeeService) context.getBean("employeeService"); /* * Register employee using service */ Employee employee = new Employee(); employee.setName("Danny Theys"); service.registerEmployee(employee); context.close(); } }
AnnotationConfigApplicationContext.scan
方法會掃描指定包下的所有類,注冊所有被@Component
標注的bean(實際上@configuration本身內部也是使用@component注解)到應用的上下文環境中;
另外要注意,在完成掃描操作後,refresh方法必須被調用,能保證完整的處理這些注冊類。
運行以上程序,你會看到同樣的輸出。
最後,如果使用XML配置的話,在main方法裡替換
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
為
AbstractApplicationContext context = new ClassPathXmlApplicationContext("app-config.xml");
會看到同樣的輸出。
http://websystique.com/?smd_process_download=1&download_id=793