天天看点

第3章:Spring高级话题3.1 Spring Aware3.2 多线程3.3 计划任务3.4 条件注解@Conditional3.5 组合注解与元注解3.6 @Enable*注解的工作原理3.7 测试

3.1 Spring Aware

3.1.1 点睛

Spring 的依赖注入最大的亮点就是你所有的Bean对Spring容器的存在是没有意识的.即你可以将你的容器替换成别的容器,如Google Guice,这时Bean之间的耦合度很低.

但是在实际项目中,你不可避免要用到Spring容器本身的功能资源,这时你的Bean必须意识到Spring容器的存在,才能调用Spring所提供的资源,这就是所谓的Spring Aware.其实Spring Aware本来就是Spring设计用来框架内部使用的,若使用了Spring Aware,你的Bean将会和Spring框架耦合.

Spring提供了Aware接口如图所示:

第3章:Spring高级话题3.1 Spring Aware3.2 多线程3.3 计划任务3.4 条件注解@Conditional3.5 组合注解与元注解3.6 @Enable*注解的工作原理3.7 测试

Spring Aware的目的是为了让Bean获得Spring容器的服务.因为ApplicationContext接口集成了MessageSource接口、ApplicationEventPublisher接口和ResourceLoader接口,所以Bean继承ApplicationContextAware可以获得Spring容器的所有服务,但是原则上我们用到什么接口,就实现什么接口.

3.1.2 示例

①:准备一个test.txt,内容随意,给下面的外部资源加载使用

②:Spring Aware演示Bean

package com.example.demo.aware;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;

//实现BeanNameAware、ResourceLoaderAware接口,获得Bean名称和资源加载服务
@Service
public class AwareService implements BeanNameAware, ResourceLoaderAware {
    private String beanName;
    private ResourceLoader loader;

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.loader = resourceLoader;
    }

    public void outputResult() {
        System.out.println("Bean的名称为:" + beanName);
        Resource resource = loader.getResource("classpath:test.txt");
        try {
            System.out.println("ResourceLoader加载的文件内容为:"+IOUtils.toString(resource.getInputStream()));
        }catch (Exception ex){
            System.out.println(ex.getMessage());
        }
    }



}
           

③:配置类

package com.example.demo.aware;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class AwareConfig {
}
           

④:运行

package com.example.demo.aware;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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();
    }
}
           

3.2 多线程

3.2.1 点睛

Spring通过任务执行器TaskExecutor来实现多线程和并发编程.使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.而实际开发中任务一般是非阻碍的,即异步的,所以我们在配置类中通过@EnableAsync开启对异步任务的支持,并通过在实际执行的Bean方法中使用@Async注解来声明其是一个异步任务.

3.2.2 示例

①:配置类

package com.example.demo.taskexecutor;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@ComponentScan
@EnableAsync//利用注解开启异步任务支持
public class TaskExecutorConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize();
        taskExecutor.setMaxPoolSize();
        //队列容量
        taskExecutor.setQueueCapacity();
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}
           

②:任务执行类

package com.example.demo.taskexecutor;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncTaskService {
    @Async
    public void executeAsyncTask(Integer i){
        System.out.println("执行异步任务:"+i);
    }

    @Async
    public void executeAsyncTaskPlus(Integer i){
        System.out.println("执行异步任务+1"+(i+));
    }
}
           

③:运行

package com.example.demo.taskexecutor;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskExecutorConfig.class);
        AsyncTaskService asyncTaskService = context.getBean(AsyncTaskService.class);
        for(int i=;i<;i++){
            asyncTaskService.executeAsyncTask(i);
            asyncTaskService.executeAsyncTaskPlus(i);
        }
        context.close();
    }
}
           

3.3 计划任务

3.3.1 点睛

从Spring3.1开始,计划任务在Spring中的实现变得异常的简单,首先通过配置类注解@EnableScheduling来开启对计划任务的支持,然后在要执行任务的方法上注解@Scheduled,声明这是一个计划任务.

Spring通过@Scheduled支持多种类型的计划任务,包含cron、fixDelay、fixRate等

3.3.2 示例

①:计划任务执行类

package com.example.demo.taskscheduler;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

@Service
public class ScheduledTaskService {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = )
    public void reportCurrentTime(){
        System.out.println("每隔5秒执行一次"+sdf.format(new Date()));
    }

    @Scheduled(cron="0 28 11 ? * *")
    public void fixTimeExecution(){
        System.out.println("在指定时间"+sdf.format(new Date())+"执行");
    }
}
           

②:配置类

package com.example.demo.taskscheduler;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@ComponentScan
@EnableScheduling//通过注解开启对计划任务的支持
public class TaskSchedulerConfig {

}
           

③:运行

package com.example.demo.taskscheduler;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TaskSchedulerConfig.class);

    }
}
           

3.4 条件注解@Conditional

3.4.1 点睛

在2.4节,通过活动的profile,我们可以获得不同的Bean.Spring4提供了一个更通用的基于条件的Bean的创建,即使用@Conditional注解

@Conditional根据满足某一个特定的条件创建一个特定的Bean.比如说,当某一个jar包在一个类路径下的时候,自动配置一个或多个Bean;或者只有某个Bean被创建才会创建另外一个Bean.总的来说,是根据特定条件来控制Bean的创建行为,这样我们可以利用这个特性来进行一些自动的配置.

下面展示不同的操作系统作为条件,我们通过实现Condition接口,并重写其matches方法来构造判断条件.若在windows系统下运行程序,则输出列表命令为dir;若在linux操作系统下,则输出列表命令ls.

3.4.1 示例

①:判断条件定义

(1)判定Windows的条件

package com.example.demo.conditional;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return conditionContext.getEnvironment().getProperty("os.name").contains("Windows");
    }
}
           

(2)判定Linux的条件

package com.example.demo.conditional;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return conditionContext.getEnvironment().getProperty("os.name").contains("Linux");
    }
}
           

②:不同系统下的Bean的类

(1)接口

package com.example.demo.conditional;
public interface ListServcie {
     String showListCmd();
}
           

(2)Windows下所要创建的Bean的类

package com.example.demo.conditional;

public class WindowsListService implements ListServcie{
    @Override
    public String showListCmd() {
        return "dir";
    }
}
           

(3)Linux下所要创建的Bean的类

package com.example.demo.conditional;

public class LinuxListService implements ListServcie {

    @Override
    public String showListCmd() {
        return "ls";
    }
}
           

③:配置类

@Configuration
public class ConditionConfig {

    @Bean
    @Conditional(WindowsCondition.class)//通过注解,符合Windows条件实例化WindowsListService
    public ListServcie windowsListService(){
        return new WindowsListService();
    }

    @Bean
    @Conditional(LinuxCondition.class)//通过注解,符合Linux条件实例化LinuxListService
    public ListServcie linuxListService(){
        return new LinuxListService();
    }
}
           

④:运行

package com.example.demo.conditional;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class);
        ListServcie listServcie = context.getBean(ListServcie.class);
        System.out.println(context.getEnvironment().getProperty("os.name")+"系统下的列表命令为:"+listServcie.showListCmd());
    }
}
           

3.5 组合注解与元注解

3.5.1 点睛

从Spring2开始,为了响应JDK1.5推出的注解功能,Spring开始大量加入注解来替代xml配置.Spring的注解主要用来配置注入Bean,面向相关配置(@Transactional).随着注解的大量使用,尤其相同的注解用到各个类中,会相当啰嗦.这就是所谓的模板代码,是Spring设计原则要消除的代码.

所谓元注解其实就是可以注解到别的注解上的注解,被注解的注解称之为组合注解,组合注解具备元注解的功能.Spring的很多注解都可以作为元注解,而且Spring本身已经有很多组合注解,例如@Configuration就是一个组合@Component注解,表明这个类其实也是一个Bean

3.5.2 示例

①:示例组合注解

package com.example.demo.annotation;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration//组合@Configuration 和@ComponentScan 元注解
@ComponentScan
public @interface WiselyConfiguration {
    String[] value() default {};//覆盖value参数
}
           

②:演示服务Bean

package com.example.demo.annotation;

import org.springframework.stereotype.Service;

@Service
public class DemoService {
    public void outputResult(){
        System.out.println("从组合注解配置照样获得到bean");
    }
}
           

③:新的配置类

package com.example.demo.annotation;

@WiselyConfiguration//使用此注解替代@Configuration和@ComponetScan
public class DemoConfig {
}
           

④:运行

package com.example.demo.annotation;


import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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();
    }
}
           

3.6 @Enable*注解的工作原理

观察发现@Enable注解的源码,都有一个@Import注解,@Import是用来导入配置类的,这也意味着这些自动开启的实现其实是导入了一些自动配置的Bean.这些配置方式主要分为以下三种类型.

3.6.1 第一类:直接导入配置类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}
           

直接导入配置类SchedulingConfiguration,这个类注解了@Configuration,且注册了一个scheduledAnnotationProcessor的Bean,源码如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.scheduling.annotation;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;

@Configuration
@Role()
public class SchedulingConfiguration {
    public SchedulingConfiguration() {
    }

    @Bean(
        name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
    )
    @Role()
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }
}
           

3.6.2 第二类:根据条件选择配置类(@EnableAsync)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
}
           

AsyncConfigurationSelector通过条件来选择需要导入的配置类,AsyncConfigurationSelector的根接口为ImportSelector,这个几块需要重写selectImports方法,在此方法内进行事先条件判断,此例中adviceMode为不同的值返回不同的配置类

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

    public AsyncConfigurationSelector() {
    }

    @Nullable
    public String[] selectImports(AdviceMode adviceMode) {
        switch(adviceMode) {
        case PROXY:
            return new String[]{ProxyAsyncConfiguration.class.getName()};
        case ASPECTJ:
            return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
        default:
            return null;
        }
    }
}
           

3.6.3 第三类:动态注册Bean(@EnableAspectJAutoProxy)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
}
           

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,ImportBeanDefinitionRegistrar的作用是在运行时自动添加Bean到已有的配置类,通过重写方法

public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}
           

其中AnnotationMetadata 参数用来获得当前配置类上的注解;BeanDefinitionRegistry 参数用来注册Bean

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }
}
           

3.7 测试

3.7.1 点睛

Spring通过Spring TestContext Framework对集成测试提供顶级支持.它不依赖于特定的测试框架,即可使用Junit也可使用TestNG.

Spring提供了一个SpringJUnit4ClassRunner类,它提供了Spring TestContext Framework的功能,通过@ContextConfiguration来配置Application Context,通过@ActiveProfiles确定激活的profile

①:准备 增加Spring测试的依赖包到Maven

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
</dependency>
           

②:业务代码

package com.example.demo.fortest;

public class TestBean {
    private String content;

    public TestBean(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
           

③:配置类

package com.example.demo.fortest;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@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");
    }
}
           

④:测试

package com.example.demo;

import com.example.demo.fortest.TestBean;
import com.example.demo.fortest.TestConfig;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfig.class})
@ActiveProfiles("prod")
public class DemoBeanIntegrationTests {
    @Autowired
    private TestBean testBean;

    @Test
    public void prodBeanShouldInject(){
        String expected ="from production profile";
        String actual = testBean.getContent();
        Assert.assertEquals(expected,actual);
    }
}
           

继续阅读