一、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);
}
}
}