Spring其他功能
- Spring Aware
- 多執行緒
- Schedule
- Conditional
- 組合註解、元註解
- 開啟支持
- 測試
【Spring Aware】
- 需要讓Bean意識到Spring容器的存在,需要使用Aware
- 使用後程式會和Spring框架耦合
- Aware interface
BeanNameAware 獲得容器中Bean的名稱 BeanFactoryAware 獲得當前bean factory ApplicationContextAware 獲得當前application context MessageSourceAware 獲得message source,獲得文本信息 ApplicationEventPublisherAware 應用事件發布器,可以發布事件 ResourceLoaderAware 獲得資源加載器,可以獲得外部資源文件 ApplicationContextAware集成了其他interface,因此implement ApplicationContextAware,就可以獲得所有服務
【範例】
- Service
@Service
public class AwareService implements BeanNameAware,ResourceLoaderAware{ // 實作BeanNameAware, ResourceLoaderAware
private String beanName;
private ResourceLoader loader;
@Override // 實作ResourceLoaderAware要覆寫此方法
public void setResourceLoader(ResourceLoader resourceLoader) {
this.loader = resourceLoader;
}
@Override // 實作BeanNameAware要覆寫此方法
public void setBeanName(String name) {
this.beanName = name;
}
public void outputResult(){
System.out.println("Bean: " + beanName); // Bean: awareService
Resource resource =
loader.getResource("classpath:com/myPackage/test.txt");
try{
System.out.println("ResourceLoader: " + IOUtils.toString(resource.getInputStream())); // ResourceLoader: [test.txt內容]
}catch(IOException e){
e.printStackTrace();
}
}
}
- Configuration
@Configuration
@ComponentScan("com.myPackage.aware")
public class AwareConfig {
}
- Context
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AwareConfig.class);
AwareService awareService = context.getBean(AwareService.class);
awareService.outputResult();
context.close();
}
}
【多執行緒】
- Spring透過TaskExecutor實現多執行緒
- ThreadPoolTaskExecutor為implement TaskExecutor介面的class,且基於多執行緒池
- Configuration中使用@EnableAsync,開啟異步任務的支持
- Bean的方法中使用@Async,聲明為異步任務
【範例】
- Configuration
@Configuration
@ComponentScan("com.wisely.highlight_spring4.ch3.taskexecutor")
@EnableAsync // 開啟對異步任務的支持
public class TaskExecutorConfig implements AsyncConfigurer{// 實現AsyncConfigurer
@Override
public Executor getAsyncExecutor() {// 覆寫getAsyncExecutor方法,返回TaskExecutor
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(25);
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
- Service
@Service
public class AsyncTaskService {
@Async // 聲明此方法為異步方法,如註解在class,則表示整個class的方法都是異步方法
public void executeAsyncTask(Integer i){
System.out.println("執行任務A: "+i);
}
@Async
public void executeAsyncTaskPlus(Integer i){
System.out.println("執行任務B: "+(i+1));
}
}
- Context
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(TaskExecutorConfig.class);
AsyncTaskService asyncTaskService = context.getBean(AsyncTaskService.class);
for(int i =0 ;i<10;i++){
asyncTaskService.executeAsyncTask(i);
asyncTaskService.executeAsyncTaskPlus(i);
}
context.close();
}
}
【Schedule】
- Configuration中使用@EnableScheduling,開啟對Schedule的支持
- 執行計畫的方法上,使用@Scheduled
- Schedule有三種類型:cron、fixedRate、fixedDelay
fixedRate:前次任務「開始執行時」,計算間隔時間,並執行下一次任務
fixedDelay:前次任務「執行完畢時」,計算間隔時間,並執行下一次任務
fixedDelay:前次任務「執行完畢時」,計算間隔時間,並執行下一次任務
【範例】
- Service
@Service
public class ScheduledTaskService {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000) // 使用fixedRate,設定每五秒執行一次
public void reportCurrentTime() {
System.out.println("每五秒鐘執行 " + dateFormat.format(new Date()));
}
@Scheduled(cron = "0 28 11 ? * *" ) // 使用cron,指定每天11:28執行
public void fixTimeExecution(){
System.out.println("指定時間執行" + dateFormat.format(new Date())+"Ö´ÐÐ");
}
}
- Configuration
@Configuration
@ComponentScan("com.myPackage.taskscheduler")
@EnableScheduling // 開啟Spring對Schedule的支持
public class TaskSchedulerConfig {
}
- Context
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(TaskSchedulerConfig.class);
}
}
【Conditional】
- 根據滿足某個特定條件,創建特定的Bean
【範例】
- Condition1
public class WindowsCondition implements Condition { // 實作Condition介面
// 實作matches方法
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
}
- Condition2
public class LinuxCondition implements Condition {
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux");
}
}
- Service interface
public interface ListService {
public String showListCmd();
}
- Service1
public class WindowsListService implements ListService {
@Override
public String showListCmd() {
return "dir";
}
}
- Service2
public class LinuxListService implements ListService{
@Override
public String showListCmd() {
return "ls";
}
}
- Configuration
@Configuration
public class ConditionConifg {
@Bean
@Conditional(WindowsCondition.class) // 符合此Condition條件,會產生這個Bean
public ListService windowsListService() {
return new WindowsListService();
}
@Bean
@Conditional(LinuxCondition.class)
public ListService linuxListService() {
return new LinuxListService();
}
}
- Context
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ConditionConifg.class);
ListService listService = context.getBean(ListService.class);
System.out.println(context.getEnvironment().getProperty("os.name")
+ "系統下的列表命令為: "
+ listService.showListCmd());
context.close();
}
}
【組合註解、元註解】
- 組合註解:包含其他註解的功能(被元註解寄生的註解),因此也擁有所包含元註解的功能,如:@Configuration
- 元註解:可以註解其他註解的註解(註解的最小單位?),如:@Component
@Configuration包含了@Component的功能
組合註解與元註解為相對關係,組合註解在其他場合,也可以成為元註解
組合註解與元註解為相對關係,組合註解在其他場合,也可以成為元註解
【範例】將@Configuration、@ComponentScan結合
- 自訂組合註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration // 組合@Configuration元註解
@ComponentScan // 組合@ComponentScan元註解
public @interface WiselyConfiguration {
String[] value() default {}; // 覆蓋value參數
}
- Service
@Service
public class DemoService {
public void outputResult(){
System.out.println("透過自訂組合註解,所得到的Bean");
}
}
- Configuration
@WiselyConfiguration("com.myPackage.annotation") // 使用自訂組合註解
public class DemoConfig {
}
- Context
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(DemoConfig.class);
DemoService demoService = context.getBean(DemoService.class);
demoService.outputResult();
context.close();
}
}
【開啟支持】
- 透過@Enable*,可開啟Spring對各種服務的支持
@EnableAspectJAutoProxy AspectJ自動代理 @EnableAsync 異步方法 @EnableScheduling 計畫任務 @EnableWebNvc Web MVC @EnableConfigurationProperties @ConfigurationProperties註解配置Bean @EnableJpaRepositories Spring Data JPA Repository @EnableTransactionManagement 註解式事務 @EnableCaching 註解式緩存 - @Enable中,都有@Import註解,導入Configuration,代表這些註解其實是導入了一些自動配置的Bean
- 配置方式主要分成三種類型
- 直接導入配置:固定註冊一個Bean
- 依據條件選擇配置:根據條件,註冊不同的Bean
- 動態註冊:透過AnnotationMetadata,動態註冊Bean
【直接導入配置】
- 註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class) // 直接導入SchedulingConfiguration
@Documented
public @interface EnableScheduling {
}
- Configuration
@Configuration // 註冊ScheduledAnnotationBeanPostProcessor的Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
【依據條件選擇配置】
- 註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
// 條件都有預設值
Class<? extends Annotation> annotation() default Annotation.class;
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
- AsyncConfigurationSelector
// 最原始的interface為ImportSelector,需覆寫selectImports方法
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Override
public String[] selectImports(AdviceMode adviceMode) { // 根據adviceMode的不同,回傳不同的配置
switch (adviceMode) {
case PROXY:
return new String[] { ProxyAsyncConfiguration.class.getName() };
case ASPECTJ:
return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}
}
【動態註冊】
- 註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
- AspectJAutoProxyRegistrar
// 實現ImportBeanDefinitionRegistrar,並覆寫registerBeanDefinitions方法
// ImportBeanDefinitionRegistrar作用為在運行時,自動添加Bean到已有的配置中(不是選擇配置,而是選擇Bean)
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
// AnnotationMetadata:獲得當前配置上的註解
// BeanDefinitionRegistry:註冊Bean
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
【測試】
- Spring透過Spring TestContex Framework對集成測試提供支持,不依賴於特定的測試框架
- SpringJUnit4ClassRunner:此class提供Spring TestContex Framework的功能
- @ContextConfiguration:配置Application Context
- @ActiveProfiles:確定活動的profile
【範例】
- Bean
public class TestBean {
private String content;
public TestBean(String content) {
super();
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
- Configuration
@Configuration
public class TestConfig {
@Bean
@Profile("dev")
public TestBean devTestBean() {
return new TestBean("from development profile");
}
@Bean
@Profile("prod")
public TestBean prodTestBean() {
return new TestBean("from production profile");
}
}
- Test
// 此檔案路徑於src/test/java
@RunWith(SpringJUnit4ClassRunner.class) // 使用SpringJUnit4ClassRunner執行
@ContextConfiguration(classes = {TestConfig.class}) // 選擇要執行的Configuration
@ActiveProfiles("prod") // 選擇要使用的profile
public class DemoBeanIntegrationTests {
@Autowired
private TestBean testBean;
@Test // 聲明為測試代碼
public void prodBeanShouldInject(){
String expected = "from production profile";
String actual = testBean.getContent();
Assert.assertEquals(expected, actual); // 比對測試結果
}
}