Spring高级话题

一、Spring Aware

Spirng 依赖注入最大亮点就是你所有的Bean对Spring容器的存在是没有意识的,即可以将容器替换成别的容器,这时Bean之间的耦合度很低。
但是在实际项目中,不可避免要用到Spring容器本身的功能资源,这时Bean必须要意识到Spring容器的存在,才能调用Spring所提供的资源,这就是Spring Aware。若使用了Spring Aware,你的Bean将会和Spring 框架耦合。

Spring 提供的Aware接口如下表所示:

Aware接口备注你
BeanNameAware获得容器中Bean的名称
BeanFactoryAware获得当前 bean factory,这样可以调用容器服务
ApplicationContextAware*当前的application context,这样可以容器的服务
MessageSourceAware获得message source,这样可以获得文本信息
ApplicationEventPublisherAware应用事件发布器,可以发布事件
ResourceLoaderAware获得资源加载器,可以获得外部资源文件

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

示例
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;

import java.io.IOException;

@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("test.txt");
        try{
            System.out.println("ResourceLoader加载的文件内容: "+ IOUtils.toString(resource.getInputStream()));
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

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

@Configuration
@ComponentScan("com.example.aware")
public class AwareConfig {
}
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();
    }
}
`22:08:27.253 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'awareConfig'
22:08:27.259 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'awareService'
Bean的名称: awareService
ResourceLoader加载的文件内容: Aware 接口测试
22:08:27.286 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1175e2db, started on Tue Nov 12 22:08:27 GMT+08:00 2019

二、多线程

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

示例
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("com.example.taskexecutor")
@EnableAsync
public class TaskExecutorConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor(){
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.initialize();
        return taskExecutor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
        return null;
    }
}

利用@EnableAsync注解开启异步任务支持

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

@Service
public class AsyncTaskService {

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


}

通过@Async注解表明该方法是个异步方法,如果注解在类级别,则表明该类所有的方法都是异步方法,而这里的方法自动被注入使用ThreadPoolTaskExecutor作为TaskExecutor。
输出
22:27:25.072 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@76ccd017, started on Tue Nov 12 22:27:24 GMT+08:00 2019
执行异步任务: 0
执行异步任务+1: 01
执行异步任务: 3
执行异步任务+1: 31
执行异步任务: 4
执行异步任务+1: 41
执行异步任务: 5
执行异步任务+1: 51
执行异步任务: 6
执行异步任务+1: 61
执行异步任务: 7
执行异步任务+1: 71
执行异步任务: 8
执行异步任务+1: 81
执行异步任务: 9
执行异步任务+1: 91
执行异步任务: 2
执行异步任务+1: 11
执行异步任务: 1
执行异步任务+1: 21

三、计划任务

从Spring3.1开始,计划任务在spring中实现异常的简单,首先通过在配置类注解@EnableScheduling来开启对计划任务的支持,然后再要执行计划任务的方法上注解@Scheduled,声明这是一个计划任务。
Spring通过@Scheduled支持多种类型的计划任务,包含cron、fixDelay、fixRate等。

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

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

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

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

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

@Configuration
@ComponentScan("com.example.taskscheduler")
@EnableScheduling
public class TaskSchedulerConfig {
}

@EnableScheduling 开启对计划任务的支持

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

22:52:32.129 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘scheduledTaskService’
22:52:32.152 [main] INFO org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor - No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
每隔五秒执行一次 22:52:32
每隔五秒执行一次 22:52:37
每隔五秒执行一次 22:52:42
每隔五秒执行一次 22:52:47
每隔五秒执行一次 22:52:52
每隔五秒执行一次 22:52:57
每隔五秒执行一次 22:53:02
每隔五秒执行一次 22:53:07
每隔五秒执行一次 22:53:12
每隔五秒执行一次 22:53:17
每隔五秒执行一次 22:53:22
每隔五秒执行一次 22:53:27
每隔五秒执行一次 22:53:32
每隔五秒执行一次 22:53:37
每隔五秒执行一次 22:53:42

四、条件注解@Conditional

通过活动的Profile可以获得不同的Bean,Spring 4提供一个更通用的基于条件的Bean的创建,即使用@Conditional注解。@Conditional根据满足某一个特定条件创建一个特定的Bean

示例
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");
    }
}
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");
    }
}
public interface ListService {
    public String showListCmd();
}

public class WindowsListService implements ListService {
    @Override
    public String showListCmd() {
        return "dir";
    }
}
public class LinuxListService implements ListService {
    @Override
    public String showListCmd() {
        return "ls";
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConditionConfig {

    @Bean
    @Conditional(WindowsCondition.class)
    public ListService windowsListService(){
        return new WindowsListService();
    }

    @Bean
    @Conditional(LinuxCondition.class)
    public ListService linuxListService(){
        return new LinuxListService();
    }
}
public class Main {
    public static void main(String[] args){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class);
        ListService listService = context.getBean(ListService.class);
        System.out.println(context.getEnvironment().getProperty("os.name")+"系统下的列表命令为: "+listService.showListCmd());
    }
}

23:14:56.548 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘windowsListService’
23:14:56.595 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key ‘os.name’ in PropertySource ‘systemProperties’ with value of type String
Windows 10系统下的列表命令为: dir

五、组合注解与元注解

Spring注解主要用来配置和注入Bean以及AOP相关配置(@Transactional)。随着注解的大量使用,尤其相同的多个注解用到各个类或方法中,会相当繁琐,这就是所谓的样板代码,是spring设计原则中要消除的代码。
所谓元注解就是可以注解到别的注解上的注解,被注解的注解称之为组合注解,组合注解具备注解其上的元注解的功能。

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

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@ComponentScan
public @interface WiselyConfiguration {
      String[] value() default {};
}

组合@Configuration元注解
组合@ComponentScan元注解
覆盖value参数

import org.springframework.stereotype.Service;

@Service
public class DemoService {
    public void outputResult(){
        System.out.println("从组合注解配置照样获得Bean");
    }
}
@WiselyConfiguration("com.example.annotation")
public class DemoConfig {
}

使用@WiselyConfiguration组合注解替代@Configuration和@ComponentScan

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

23:29:44.152 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘demoConfig’
23:29:44.159 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘demoService’
从组合注解配置照样获得Bean

六、@Enable*注解的工作原理

@EnableAspectJAutoProxy开启对AspectJ自动代理的支持
@EnableAsync开启异步方法的支持
@EnableScheduling开启计划任务的支持
@EnableWebMvc开启对WebMVC的配置支持
@EnableConfigurationProperties开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories开启对Spring Data JPA Repository的支持
@EnableTransactionManagement开启注解式事务的支持
@EnableCaching开启注解式的缓存支持

通过观察这些@Enable*注解的源码,发现所有注解都有一个@Import注解,@Import是用来导入配置类的,这也就意味着这些自动开启的实现其实是导入了一些自动配置的Bean。这些导入的配置方式主要分为以下三种类型。
第一类:直接导入配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling{
}
直接导入配置类SchedulingConfiguration,这个类注解了@Configuration,且注解了一个scheduledAnnotationProcessor的Bean,源码如下:
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration{
@Bean(name=TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESS_BEAN_NAME)
@Role(BeanDefinition.ROLE.INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor(){
return new ScheduledAnnotationBeanPostProcessor();
}
}
第二类:依据条件选择配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AsyncConfigurationSelector.class)
@Documented
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根据条件来选择需要导入的配置类,
AsyncConfigurationSelector根接口为ImportSelector,这个接口需重写selectImports方法,在此方法内进行事先条件判断。
public class AsyncConfigurationSelector extends AdviceModeImportSelector{
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME=“org.springframework.scheduling.aspectj.AspectJAsyncConfiguration”;

@Override
public String[] selectImports(AdviceMode adviceMode){
switch(adviceMode){
case PROXY:
return new String[] {ProxyAsyncConfiguration.class.getName()};
case ASPECTJ:
return new String[]{ASYNC_EXECUTION_AS[ECT_CONFIGURATION_CLASS_NAME};
default:
return null;
}
}
}
第三类: 动态注册

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AspectJAutoProxyRegistrar.class)
@Documented
public @interface EnableAspectJAutoProxy{
    boolean proxyTargetClass() default false;
}

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,AspectJAutoProxyRegistrar的作用是在运行时自动添加Bean到已有的配置类,通过重写方法:
registerBeanDefinitions(AnnotationMetadata importingClassMetadata),BeanDefinitonRegistry registry)
其中,AnnotationMetadata参数用来获得当前配置类上的注解,BeanDefinitonRegistry参数用来注册Bean,源码如下:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata),BeanDefinitonRegistry registry){
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAJutoProxy = AnnotationConfigUtils,attributesFor(importingClassMetadata,EnableAspectJAutoProxy.class);
if(enableAJAutoProxy.getBoolean(“proxyTargetClass”)){
AopConfigUtils.foreAutoProxyCreatorToUseClassProxying(registry);
}
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值