欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring之ApplicationContext的拓展功能

程序员文章站 2022-05-23 12:58:06
...

      相对于BeanFactory来说,ApplicationContext除了提供BeanFactory的所有功能外,还有一些其他的功能,主要包括国际化支持、资源访问、事件传递。下面将讲解ApplicationContext在BeanFactory的基本功能之外的功能。

1.国际化支持

简介

     国际化的英文为Internationalization,这个也太长了,所以它又称为I18n(英文单词 internationalization的首末字符i和n,18为中间的字符数)。 国际化的操作就是指一个程序可以同时适应多门语言,即:如果现在程序的使用者是中国人,则会以中文为显示文字,如果现在程序的使用者是美国人,则会以英语为显示的文字,也就是说可以通过国际化操作,让一个程序适应各个国家的语言要求。

原理

        程序根据不同的语言环境找到不同的资源文件,之后从资源文件中取出内容,资源文件中的内容是以key-àvalue的形式保存的,所以在读取的时候通过其key找到对应的value

Demo

1、i18n.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>message</value>
            </list>
        </property>
</bean>
</beans>

2、message_en_US.properties

k1=welcome\uFF1A {0}

3、message_zh_CN.properties

k1=\u6b22\u8fce\u4f60\uff0c{0}

      k1就是“你好”,上面使用的是unicode。可以使用Unicode的转换器来进行转换,也可以使用命令

4、测试

package com.yj.spring;

import java.util.Locale;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class I18NTest {

	@Test
	public void Test() {
		Locale defaultLocale = Locale.getDefault();
		//Locale defaultLocale = Locale.US;
		System.out.println("country="+ defaultLocale.getCountry());
	    System.out.println("language="+ defaultLocale.getLanguage());

		Object[] arg = new Object[] { "悟空"};
		ApplicationContext ctx = new ClassPathXmlApplicationContext("i18n.xml");
		String msg = ctx.getMessage("k1", arg, defaultLocale);

		System.out.println(msg);
	}
}

5、默认时,显示为

country=CN
language=zh
欢迎你,悟空

当为美国US时,显示为

country=US
language=en
welcome: 悟空

2.资源访问

很多时候应用程序都需要存取资源。Spring提供了对资源文件的存取。ApplicationContext继承了ReourceLoader接口,开发人员可以使用getResource()方法并指定资源文件的URL来存取。

ApplicationContext对资源文件的读取有如下3种方式:

1.虚拟路径来存取;

如果资源文件位于CLASSPATH下:可以通过这种方式来获取,代码如下

Resource resource=ctx.getResource("classpath:message.properties");

这里要说明的是"claspath:"是 spring约定的URL虚拟路径。

2.绝对路径来存取;

指定标准的URL,例如“file:”或“http:”,代码如下

Resource source=ctx.getResource("file:D:/eclipse/workspace/Spring/src/main/resources/message.properties");

3.相对路径来存取。

Resource source = actx.getResource("WEB-INF/message.properties");

当通过ApplicationContext取得一个Resource后,开发人员可以使用:

  • getFile() 来存取资源文件内容
  • exists()来检查资源文件是否存在
  • isOpen()检查资源文件是否被打开
  • getURL()返回资源文件的URL

完整test文件:

package com.yj.spring;

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;

public class PropTest {
	@Test
	public void ClassPathTest() throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
		Resource resource=ctx.getResource("classpath:message.properties");
		File file=resource.getFile();
		
		Properties prop=fileToProp(file);
		System.out.println("通过classptah路径获取:"+prop.getProperty("name"));
		
	}
	
	@Test
	public void RealPathTest() throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
		Resource resource=ctx.getResource("file:D:/eclipse/workspace/Spring/src/main/resources/message.properties");
		File file=resource.getFile();
		
		Properties prop=fileToProp(file);
		System.out.println("通过实际路径获取:"+prop.getProperty("name"));
		
	}
	
	@Test
	public void virtualPathTest() throws Exception {
		ApplicationContext ctx = new FileSystemXmlApplicationContext("D:/eclipse/workspace/Spring/src/main/resources/Beans.xml");
		Resource resource=ctx.getResource("WEB-INF/message.properties");
		File file=resource.getFile();
		
		Properties prop=fileToProp(file);
		System.out.println("通过虚拟路径获取:"+prop.getProperty("name"));
		
	}
	
	private Properties fileToProp(File file) throws Exception{
		Properties prop = new Properties();
		prop.load(new FileInputStream(file));
		return prop;
	}
}

3.事件传递

   ApplicationContext事件机制是观察者设计模式(订阅/发布模式)的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。

    Spring的事件框架有如下两个重要的成员:

  • ApplicationEvent:容器事件,必须由ApplicationContext发布

  • ApplicationListener:监听器,可由容器中的任何监听器Bean担任

    实际上,Spring的事件机制与所有时间机制都基本相似,它们都需要事件源、事件和事件监听器组成。只是此处的事件源是ApplicationContext,且事件必须由Java程序显式触发。下面的程序将演示Spring容器的事件机制。

Demo

1.项目整体结构

Spring之ApplicationContext的拓展功能

2.EmailController

package com.yj.event.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.yj.event.email.EmailEvent;
import com.yj.event.email.EmailService;

@RestController
public class EmailController {
	@Autowired
	private EmailService emailService;

	@RequestMapping("/sendEmail")
	public void sendEmail() {
		EmailEvent emailEvent = new EmailEvent("source");
		emailEvent.setAddress("my address");
		emailEvent.setText("hello world");
		emailService.sendEmail(emailEvent);
	}
}

3.EmailEvent

程序先定义了一个EmailEvent类,其对象就是一个Spring容器事件。该类继承了ApplicationEvent类,除此之外,它就是一个普通的Java类。代码如下:

package com.yj.event.email;

import org.springframework.context.ApplicationEvent;

public class EmailEvent extends ApplicationEvent {

	private static final long serialVersionUID = 8890656093518139995L;
	private String address;
	private String text;

	public EmailEvent(Object source) {
		super(source);
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}
}

 

4.EmailListener

两种实现方式,

  • @EventListener注解
  • 一种实现ApplicationListener接口,重写onApplicationEvent方法

  基于注解的方式

package com.yj.event.email;

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class EmailAnnoListener {

    @EventListener
    //@Async
    public void EmailEventListener(EmailEvent event) {
        try {
			System.out.println("开始休眠...");
			Thread.sleep(5000L);
			System.out.println("休眠结束...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("EmailEventListener:"+event.getClass());
		System.out.println("注解监听到发送邮件的事件");
		System.out.println("注解需要发送的邮件地址: " + event.getAddress());
		System.out.println("注解邮件正文: " + event.getText());
    }
}

 实现接口的方式

package com.yj.event.email;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;


//@Component
//@Async
public class EmailListener implements ApplicationListener<ApplicationEvent> {

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		System.out.println("EmailListener:" + event.getClass());
		if (event instanceof EmailEvent) {
			EmailEvent emailEvent = (EmailEvent) event;
			System.out.println("监听到发送邮件的事件");
			System.out.println("需要发送的邮件地址: " + emailEvent.getAddress());
			System.out.println("邮件正文: " + emailEvent.getText());
			try {
				Thread.sleep(5000L);
				System.out.println("休眠...");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

       EmailEventListener方法或者onApplicationEvent方法,可以传入ApplicationEvent参数,监听所有的事件,本例只传入EmailEvent参数,只监听EmailEvent事件。然后将监听器配置在Spring的容器中。需注意的是,此时事件的发布,与事件的监听处理,默认是同步阻塞的,下文会开启异步的方式

5.EmailService

      当系统创建Spring容器、加载Spring容器时会自动触发容器事件,容器事件监听器可以监听到这些事件。除此之外,程序也可以调用ApplicationContext的publishEvent()方法来主动触发一个容器事件

package com.yj.event.email;

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import com.yj.event.util.ApplicationContextUtil;

@Component
public class EmailService {
	public void sendEmail(EmailEvent event){
		ApplicationContext ctx=ApplicationContextUtil.getApplicationContext();
		ctx.publishEvent(event);
	}
}

6.ApplicationContextUtil

如果Bean想发布事件,则Bean必须获得其容器的引用。如果程序中没有直接获取容器的引用,则应该让Bean实现ApplicationContextAware或者BeanFactoryAware接口,从而可以获得容器的引用

package com.yj.event.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (ApplicationContextUtil.applicationContext == null) {
        	ApplicationContextUtil.applicationContext = applicationContext;
        }
    }
}

7.pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.yj</groupId>
	<artifactId>Event</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>Event</name>
	<url>http://maven.apache.org</url>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath />
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	</dependencies>
</project>

8.app

package com.yj.event;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//@EnableAsync
public class App {
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

验证

访问路径

http://127.0.0.1:8080/sendEmail

默认同步的情况下,显示结果

开始休眠...
休眠结束...
EmailEventListener:class com.yj.event.email.EmailEvent
注解监听到发送邮件的事件
注解需要发送的邮件地址: my address
注解邮件正文: hello world
结束

开启异步的情况(app.java类添加@EnableAsync注解,EmailAnnoListener类的EmailEventListener方法上添加@Async注解),有点MQ的效果,只是只能在一个ApplicationContext范围内才能捕获到事件的发布,才能起作用。显示结果

结束
开始休眠...
休眠结束...
EmailEventListener:class com.yj.event.email.EmailEvent
注解监听到发送邮件的事件
注解需要发送的邮件地址: my address
注解邮件正文: hello world

Spring提供如下几个内置事件:

  • ContextRefreshedEvent:ApplicationContext容器初始化或刷新时触发该事件。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并**,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用

  • ContextStartedEvent:当使用ConfigurableApplicationContext(ApplicationContext的子接口)接口的start()方法启动ApplicationContext容器时触发该事件。容器管理声明周期的Bean实例将获得一个指定的启动信号,这在经常需要停止后重新启动的场合比较常见

  • ContextClosedEvent:当使用ConfigurableApplicationContext接口的close()方法关闭ApplicationContext时触发该事件

  • ContextStoppedEvent:当使用ConfigurableApplicationContext接口的stop()方法使ApplicationContext容器停止时触发该事件。此处的停止,意味着容器管理生命周期的Bean实例将获得一个指定的停止信号,被停止的Spring容器可再次调用start()方法重新启动

  • RequestHandledEvent:Web相关事件,只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。