spring之高级话题

1.spring Aware

spring的依赖注入中所有的bean对spring容器的存在是没有意识的,即我们可以将容器替换成别的容器,如Google Guice,在实际项目中,我们不可避免的要用到spring容器本身的功能资源,这个时候bean必须要意识到spring容器的存在,才能调用spring所提供的资源,这就是Spring Aware 。

spring提供的Aware接口

(1)BeanFactory容器的接口: 
A:org.springframework.beans.factory.BeanNameAware: 获取容器中bean的名称
B:org.springframework.beans.factory.BeanClassLoaderAware:加载当前对象的ClassLoader注入当前对象实例
C:org.springframework.beans.factory.BeanFactoryAware:获取当前BeanFactory,这样可以调用容器的服务

(2)ApplicationContext容器的Aware接口: 
A:org.springframework.context.ResourceLoaderAware:获取资源加载器,可以获得外部资源文件
B:org.springframework.context.ApplicationEventPublisherAware:应用时间发布器,可以发布事件(不注入ApplicationContext,注入这个也可以发布事件)
C: org.springframework.context.MessageSourceAware:MessageSource:获取message Resource,这样可以获取文本信息
D:org.springframework.context.ApplicationContextAware:同样的注入ApplicationContext。

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

演示BeanNameAware和ResouceLoaderAware的使用

第一步:编写service实现BeanNameAware和ResourceLoadAware

@Component
public class AwareService implements BeanNameAware,ResourceLoaderAware{
	
	private String name;
	
	private ResourceLoader loader;

	/* 
	 * bean名称服务
	 */
	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.loader = resourceLoader;
	}

	/* 
	 * 资源加载服务
	 */
	@Override
	public void setBeanName(String name) {
		this.name = name;
	}
	
	public void outputResult() throws IOException{
		System.out.println(name);
		Resource resource = loader.getResource("classpath:1.txt");
		InputStream inputStream = resource.getInputStream();
		byte[] bytes = new byte[0];
		bytes = new byte[inputStream.available()];
		inputStream.read(bytes);
		String str = new String(bytes);
		System.out.println(str);
		
	}

}

第二步:编写配置类和测试类

@Configuration
@ComponentScan("com.pingan.haofang.aware")
public class AwareConfig {

}

class Test{
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(AwareConfig.class);
		AwareService service = context.getBean(AwareService.class);
		try {
			service.outputResult();
		} catch (IOException e) {
		}
	}
}

spring 多线程

Spring通过任务执行器TaskExecutor来实现多线程和并发编程,使用ThreadPoolTaskExecutor 可实现一个基于线程池的TaskExecutor

在实际开发任务一般是非阻碍的,即异步的,所以我们要在配置类中通过@EnableAsync开启对异步任务的支持,并通过实践执行的Bean的方法中的@Async注解来声明其是一个异步任务

基于ThreadPoolTaskExecutor的任务执行器:

第一步:配置类实现AsyncConfigurer接口,重写getAsyncExecutor()方法,返回一个ThreadPoolTaskExecutor,这样我就就获取一个基于线程池的TaskExecutor

@Configuration
@ComponentScan("com.pingan.haofang.taskexecutor")
@EnableAsync     //开启异步任务支持
public class taskExecuterConfig implements AsyncConfigurer {

	/* 
	 * 配置并获取ThreadPoolTaskExecutor
	 */
	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor excutor = new ThreadPoolTaskExecutor();
		excutor.setCorePoolSize(10);
		excutor.setMaxPoolSize(20);
		excutor.setKeepAliveSeconds(0);
		excutor.setQueueCapacity(50);
		excutor.initialize();
		return excutor;
	}

	@Override
	public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		// TODO Auto-generated method stub
		return null;
	}

}

第二步:异步执行任务,在类或方法上使用@Async注解,被注解的方法会启用基于线程池的异步调用

@Service
public class AsyncService {
	
	@Async
	public void executeTask(Integer i) {
		System.out.println("异步执行任务"+i);
	}
	
	@Async
	public void executeTaskPlus(Integer i) {
		System.out.println("异步执行任务plus"+(i+1));
	}

}

第三步:测试及结果

	public static void main(String[] args) {
		AnnotationConfigApplicationContext acac= new AnnotationConfigApplicationContext(taskExecuterConfig.class);
		AsyncService asyncService = acac.getBean(AsyncService.class);
		for (int j = 0; j < 20; j++) {
			asyncService.executeTask(j);
			asyncService.executeTaskPlus(j);
		}
		acac.close();
	}

结果:
异步执行任务plus1
异步执行任务5
异步执行任务plus6
异步执行任务6
异步执行任务plus7
异步执行任务7
异步执行任务plus8
异步执行任务8
异步执行任务plus9
异步执行任务9
异步执行任务plus10
异步执行任务10
异步执行任务plus11
异步执行任务11
异步执行任务plus12
异步执行任务12
异步执行任务plus13
异步执行任务13
异步执行任务plus14
异步执行任务14
异步执行任务plus15
异步执行任务15
异步执行任务plus16
异步执行任务16
异步执行任务plus17
异步执行任务17
异步执行任务plus18
异步执行任务18
异步执行任务plus19
异步执行任务19
异步执行任务plus20
异步执行任务plus3
异步执行任务plus5
异步执行任务1
异步执行任务3
异步执行任务0
异步执行任务2
异步执行任务4
异步执行任务plus2
异步执行任务plus4

spring 的定时任务

从spring 3.1开始,定时任务在spring中的实现变得异常简单,首先通过在配置类注解@EnableScheduling来开启对定时任务的支持,然后在执行定时任务的方法上添加注解@Scheduled,用来声明这个一个定时任务。

spring通过@Scheduled支持多种类型的定时任务,包括cron,fixDelay,fixRate

//定时任务service
@Service
public class SchedulerService {
	
	@Scheduled(cron="0 0/1  * * * ?") //每分钟执行一次
	public void schedulerFirst() {
		System.out.println("Hi,now is :"+new Date());
	}
	
	@Scheduled(fixedRate = 5000) //每分钟执行一次
	public void schedulerSecond() {
		System.out.println("Hi,I am kakaPo");
	}

}

//定时任务配置类
@Configuration
@ComponentScan("com.pingan.haofang.scheduler")
@EnableScheduling //开启计划任务的支持
public class MySchedulerConfig { 
	
}

测试:
容器启动时候,会自动执行定时任务

Hi,I am kakaPo
Hi,I am kakaPo
Hi,I am kakaPo
Hi,I am kakaPo
Hi,I am kakaPo
Hi,I am kakaPo
Hi,now is :Thu Aug 16 14:22:00 CST 2018
Hi,I am kakaPo
Hi,I am kakaPo
Hi,I am kakaPo
Hi,I am kakaPo

当然除了使用以上注解,如果使用xml,也是可以的,具体使用如下:

  首先:xml文件引入命名空间
xmlns:task="http://www.springframework.org/schema/task" 
http://www.springframework.org/schema/task                        http://www.springframework.org/schema/task/spring-task-4.1.xsd

    <!-- proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。首先说明下proxy-target-class="true"和proxy-target-class="false"的区别,
    为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用。-->
    
    <!--1.启用注解驱动的定时任务  -->
    <task:annotation-driven scheduler="Scheduler" proxy-target-class="true"/>
     <!--2.配置定时任务的线程池,如果不配置,多任务下会有问题  -->
    <task:scheduler id="Scheduler" pool-size="10"/> 
    
    <!-- 3.配置任务bean -->
    <bean id="schedulerService" class="com.pingan.haofang.scheduler.SchedulerService"></bean>
    <!-- 4.配置任务执行的规则 
   		ref是工作类 
		method是工作类中要执行的方法 
		initial-delay是任务第一次被调用前的延时,单位毫秒 
		fixed-delay是上一个调用完成后再次调用的延时 
		fixed-rate是上一个调用开始后再次调用的延时(不用等待上一次调用完成) 
		cron是表达式,表示在什么时候进行任务调度。
    -->
    <task:scheduled-tasks>
        <task:scheduled ref="schedulerService" method="schedulerFirst" cron="0/2 * * * * ?"/>
    	<task:scheduled ref="schedulerService" method="schedulerSecond" fixed-delay="5000"/>  
    </task:scheduled-tasks>

然而,上面的任务没有配置线程池,默认只有一个线程执行定时任务,这样在多任务下或有问题,以下面的列子说明:

是推荐配置线程池,若不配置多任务下会有问题。后面会详细说明单线程的问题

@Service
public class SchedulerService {
	
	@Scheduled(cron="0/10 * *  * * ? ")   //每10秒执行一次   
	public void schedulerA() {
		   try {
               TimeUnit.SECONDS.sleep(20);//任务执行需要20S
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
           System.out.println(sdf.format(new Date())+"*********A任务每10秒执行一次进入测试");  
	}
		
	@Scheduled(cron="0/5 * *  * * ? ")   //每5秒执行一次  
	public void schedulerB() {
		  DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
	         System.out.println(sdf.format(new Date())+"*********B任务每5秒执行一次进入测试"); 
	}

}

单线程多任务的执行结果(B任务会因为A任务执行起来需要20S而被延后20S执行)

2018-08-16 16:15:00*********B任务每5秒执行一次进入测试
2018-08-16 16:15:20*********A任务每10秒执行一次进入测试
2018-08-16 16:15:20*********B任务每5秒执行一次进入测试
2018-08-16 16:15:25*********B任务每5秒执行一次进入测试
2018-08-16 16:15:30*********B任务每5秒执行一次进入测试
2018-08-16 16:15:50*********A任务每10秒执行一次进入测试
2018-08-16 16:15:50*********B任务每5秒执行一次进入测试
2018-08-16 16:15:55*********B任务每5秒执行一次进入测试
2018-08-16 16:16:20*********A任务每10秒执行一次进入测试
2018-08-16 16:16:20*********B任务每5秒执行一次进入测试
2018-08-16 16:16:25*********B任务每5秒执行一次进入测试
2018-08-16 16:16:30*********B任务每5秒执行一次进入测试
2018-08-16 16:16:50*********A任务每10秒执行一次进入测试
2018-08-16 16:16:50*********B任务每5秒执行一次进入测试
2018-08-16 16:16:55*********B任务每5秒执行一次进入测试
2018-08-16 16:17:20*********A任务每10秒执行一次进入测试
2018-08-16 16:17:20*********B任务每5秒执行一次进入测试
2018-08-16 16:17:25*********B任务每5秒执行一次进入测试
2018-08-16 16:17:30*********B任务每5秒执行一次进入测试
2018-08-16 16:17:50*********A任务每10秒执行一次进入测试
2018-08-16 16:17:50*********B任务每5秒执行一次进入测试
2018-08-16 16:17:55*********B任务每5秒执行一次进入测试
2018-08-16 16:18:20*********A任务每10秒执行一次进入测试
2018-08-16 16:18:20*********B任务每5秒执行一次进入测试
2018-08-16 16:18:25*********B任务每5秒执行一次进入测试
2018-08-16 16:18:30*********B任务每5秒执行一次进入测试

所以我们需要配置为定时任务线程池(在多线程环境下不会任务冲突,或者让任务分别运行在不同的scheduler里就好了,相当于一个定时任务分配了一个线程)

方法1:在配置类中添加线程池,给定时任务添加@Async注解,让其多线程异步执行

@Service
public class SchedulerService {
	
	@Async //基于TaskExecutor的异步执行
	@Scheduled(cron="0/10 * *  * * ? ")   //每10秒执行一次   
	public void schedulerFirst() {
		   try {
               TimeUnit.SECONDS.sleep(20);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
           System.out.println(sdf.format(new Date())+"*********A任务每10秒执行一次进入测试");  
	}
	
	@Async //基于TaskExecutor的异步执行
	@Scheduled(cron="0/5 * *  * * ? ")   //每5秒执行一次  
	public void schedulerSecond() {
		  DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
	         System.out.println(sdf.format(new Date())+"*********B任务每5秒执行一次进入测试"); 
	}

}

配置类添加线程池:
@Configuration
@ComponentScan("com.pingan.haofang.scheduler")
@EnableScheduling //开启计划任务的支持
@EnableAsync//开启异步任务支持
public class MySchedulerConfig implements AsyncConfigurer {

	@Override
	public Executor getAsyncExecutor() {
			ThreadPoolTaskExecutor excutor = new ThreadPoolTaskExecutor();
			excutor.setCorePoolSize(10);
			excutor.setMaxPoolSize(20);
			excutor.setKeepAliveSeconds(0);
			excutor.setQueueCapacity(50);
			excutor.initialize();
			return excutor;
	}

	@Override
	public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		// TODO Auto-generated method stub
		return null;
	} 
	
}

基于线程池+异步任务,执行的结果如下:

2018-08-16 16:40:00*********A任务每10秒执行一次进入测试
2018-08-16 16:40:00*********B任务每5秒执行一次进入测试
2018-08-16 16:40:05*********B任务每5秒执行一次进入测试
2018-08-16 16:40:10*********A任务每10秒执行一次进入测试
2018-08-16 16:40:10*********B任务每5秒执行一次进入测试
2018-08-16 16:40:15*********B任务每5秒执行一次进入测试
2018-08-16 16:40:20*********A任务每10秒执行一次进入测试
2018-08-16 16:40:20*********B任务每5秒执行一次进入测试
2018-08-16 16:40:25*********B任务每5秒执行一次进入测试
2018-08-16 16:40:30*********A任务每10秒执行一次进入测试
2018-08-16 16:40:30*********B任务每5秒执行一次进入测试
2018-08-16 16:40:35*********B任务每5秒执行一次进入测试
2018-08-16 16:40:40*********A任务每10秒执行一次进入测试
2018-08-16 16:40:40*********B任务每5秒执行一次进入测试
2018-08-16 16:40:45*********B任务每5秒执行一次进入测试
2018-08-16 16:40:50*********A任务每10秒执行一次进入测试
2018-08-16 16:40:50*********B任务每5秒执行一次进入测试
2018-08-16 16:40:55*********B任务每5秒执行一次进入测试
2018-08-16 16:41:00*********A任务每10秒执行一次进入测试
2018-08-16 16:41:00*********B任务每5秒执行一次进入测试
2018-08-16 16:41:05*********B任务每5秒执行一次进入测试
2018-08-16 16:41:10*********A任务每10秒执行一次进入测试
2018-08-16 16:41:10*********B任务每5秒执行一次进入测试
2018-08-16 16:41:15*********B任务每5秒执行一次进入测试
2018-08-16 16:41:20*********B任务每5秒执行一次进入测试
2018-08-16 16:41:20*********A任务每10秒执行一次进入测试

方法2:xml添加线程池配置同样可以实现上面的效果

<task:scheduler id="myScheduler" pool-size="5"/>

 

Spring条件注解@Conditional

有提到过配置不同的profile获取不同的Bean,Spring 4提供了一个更通用的基于条件的Bean的穿件,即@Conditonal注解

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

在SpringBoot大量使用到这个注解

查看Conditional注解的源码,需要我们传递一个实现Condition条件接口的Class对象

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

所以我们自定义一个类去实现Condition接口,重写matches方法作为判断条件,用这个类作为@Conditional注解的参数

public class WindowCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// TODO Auto-generated method stub
		return context.getEnvironment().getProperty("os.name").contains("windows");
	}

}


public class LinuxCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		
		return context.getEnvironment().getProperty("os.name").contains("linux");
	}

}

然后在配置中使用@Conditional注解传入Condition子类的class对象

@Configuration
public class ConditionConfig {
	
	@Bean
	@Conditional(WindowCondition.class)
	//如果WindowCondition中的match方法返回结果true,就创建该Bean,否则不创建
	public IService windowService() {
		WindowService windowService = new WindowService();
		return windowService;
	}
	
	@Bean
	@Conditional(LinuxCondition.class)
	public IService linuxService() {
		LinuxService linuxService = new LinuxService();
		return linuxService;
	}
}

Enable*注解的工作原理

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值