5.1 Spring容器技术内幕
Spring容器像一台构造精妙的机器,我们通过配置文件向机器传达控制信息,机器就能够按照设定的模式进行工作。如果我们将Spring容器比喻为一辆汽车,可以将BeanFactory看成汽车的发动机,而ApplicationContext则是整辆汽车。
5.1.1 内部工作机制
Spring组件按其所承担的角色可以划分为两类:
1)物料组件:Resource、BeanDefinition、PropertyEditor以及最终的Bean等。它们是加工流程中被加工、被消费的组件,就像流水线上被加工的物料;
2)加工设备组件:ResourceLoader、BeanDefinitionReader、BeanFactoryPostProcessor、InstantiationStrategy以及BeanWrap等组件。
5.1.2 BeanDefinition
RootBeanDefinition是最常见的实现类。将的配置信息,如class,scope等注册到BeanDefinitionResistry(就像Spring配置信息的内存数据库),后续操作直接从BeanDefinitionRegistry中读取配置信息。
利用BeanDefinitionReader对配置信息Resource进行读取,通过XML解析器解析配置信息的DOM对象。简单的为每个生成BeanDefinition对象。
利用容器中注册的BeanFactoryPostProcessor对半成品的BeanDefinition进行加工处理,形成最终的BeanDefinition。
5.1.3 InstantiationStrategy
负责根据BeanDefinition对象创建一个Bean实例。其中的实现类:CglibSubclassingInstantiationStrategy为Bean动态生成子类。属性填充留待BeanWrapper来完成。
5.1.4 BeanWrapper
BeanWrapper相当于一个代理,Spring通过BeanWrapper完成Bean属性的填充工作。其中的BeanWrapperImpl对BeanDefinition属性编辑。
5.2 属性编辑器
在Spring配置文件里,不管是double类型还是int类型,在配置文件中都对应字符串类型的字面值,这个转换器就是属性编辑器。
PropertyEditor就是属性编辑器,是JavaBean规范定义的接口。
5.2.1 JavaBean的编辑器
BeanInfo
描述了JavaBean哪些属性可以编辑以及对应的属性编辑器。
5.2.2 Spring默认属性编辑器
5.3 使用外部属性文件
Spring提供了一个PropertyPlaceholderConfigurer,它能够使Bean在配置时引用外部属性文件。这个类实现了BeanFactoryPostprocessorBean接口,因此也是衣蛾Bean工厂后处理器。
5.3.1 使用外部属性文件
使用属性文件
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql//localhost:3306/sampledb"
p:userName="root"
p:password="123456" />
抽取到一个配置文件中:jdbc.properties:
driverClassName = com.mysql.jdbc.Driver
url = jdbc:mysql//localhost:3306/sampledb
userName = root
password = 123456
引入jdbc.properties属性文件
<!-- 引入jdbc.properties -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:location="classpath:com/baobaotao/jdbc.properties"
p:fileEncoding="utf-8" />
<!-- 通过属性名引用属性值 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${driverClassName}"
p:url="${url}"
p:userName="${userName}"
p:password="${password}" />
PropertyPlaceholderConfigurer其他属性
locations:像set一样配置多个属性文件。
order对多个属性文件设置优先顺序。
placeholderPrefix前缀设置(默认为${)
placeholderSuffix后缀设置(默认为})
使用
配置utf-8需要额外声明字符串Bean
<context:property-placeholder
location="classpath:com/baobaotao/jdbc.properties"
file-encoding="utf8" />
<bean id="utf8" class="java.lang.String">
<constructor-arg value="utf-8"></constructor-arg>
</bean>
在基于注解及基于Java类配置中引用属性
@Component
public class MyDataSource{
@Value("${driverClassName}")
private String driverClassName;
}
5.3.2 使用加密的属性文件
PropertyPlaceholderConfigurer继承于PropertyResourceConfigurer类,用于对属性文件中的属性进行转换处理。
DES加密解密工具类
信息的解密分为对称和非对称。前者表示加密后的信息可以解密成原值,而后者则不能根据加密后的信息还原成原值。MD5属于非对称加密,而DES属于对称加密。
DES加密的工具类:
package com.baobaotao.app;
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class DESUtils {
//指定DES加密解密所用的密钥
private static Key key;
private static java.lang.String KEY_STR = "myKey";
static{
try {
KeyGenerator generator = KeyGenerator.getInstance("DES");
generator.init(new SecureRandom(KEY_STR.getBytes()));
key = generator.generateKey();
generator = null;
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
//对字符串进行DES加密,返回BASE64编码的加密字符串
public static String getEncryptString(String str){
BASE64Encoder base64en = new BASE64Encoder();
try {
byte[] strBytes = str.getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptStrBytes = cipher.doFinal(strBytes);
return base64en.encode(encryptStrBytes);
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException();
}
}
//对BASE64编码编码的加密字符串进行解密,返回解密后的字符串
public static String getDecryptString(String str){
BASE64Decoder base64De = new BASE64Decoder();
try {
byte[] strBytes = base64De.decodeBuffer(str);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptStrBytes = cipher.doFinal(strBytes);
return new String(decryptStrBytes, "UTF-8");
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
/*public static void main(String[] args){
String[] a = {"root", "123456"};
for (String string : a) {
System.out.println(getEncryptString(string)+",");
}
}*/
}
对属性进行解密的覆写:
package com.baobaotao.app;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
public class EncryptPropertyPlaceholderConfigurer extends
PropertyPlaceholderConfigurer {
private String[] encryptPropNames={"username","password"};
//对特定的属性的属性值进行转换
@Override
protected String convertProperty(String propertyName, String propertyValue) {
// TODO Auto-generated method stub
if (isEncryptProp(propertyName)) {
String decryptValue = DESUtils.getDecryptString(propertyValue);
System.out.println(decryptValue);
return decryptValue;
}else{
return propertyValue;
}
}
//判断是否是需要进行解密的属性
private boolean isEncryptProp(String propertyName){
for (String encryPropName : encryptPropNames) {
if (encryPropName.equals(propertyName)) {
return true;
}
}
return false;
}
}
......
<bean class="com.baobaotao.app.EncryptPropertyPlaceholderConfigurer"
p:location="classpath:com/baobaotao/app/jdbc.properties"
p:fileEncoding="utf-8" />
......
5.3.3 属性文件自身的引用
dbName =sampledb
url =jdbc:mysql://localhost:3306/${dbName}
5.4 引用Bean的属性值
public class SysConfig{
private int sessionTimeout;
private int maxTabPageNum;
private DataSource dataSource;
//模拟从数据库中获取配置值
public void initFromDB(){
this.sessionTimeout=30;
this.maxTabOPageNum=10;
}
//set\get方法
}
<bean id="sysConfig" class="com.baobaotao.beanprop.SysConfig"
init-method="inifFromDB"
p:dataSource-ref="dataSource" />
//引用Bean的属性值
<bean class="com.baobaotao.beanprop.AppManager"
p:maxTabPageNum="#{sysConfig.maxTabPageNum}"
p:sessionTimeout="#{sysConfig.sessionTimeout}" />
在类中使用即@Value(“#{maxTabPageNum}”)
5.5 国际化信息
5.6 容器事件
Spring的ApplicationContext能后发布事件并且允许注册相应的事件监听器,因此有一套完善的事件发布和监听机制。
事件源
事件监听器注册表:组件或框架的事件监听器保存在事件监听器注册表中
事件广播器:负责把事件通知给事件监听器。
事件体系体系是观察者模式的一种具体实现方式。
5.6.1 Spring事件类结构
事件类
ApplicationContextEvent:容器事件,拥有4个子类分别表示容器启动、刷新、停止及关闭的事件。
RequestHandleEvent:当一个HTTP请求被处理后,产生该事件。只有在web.xnl中定义了DispatcherServlet时才会产生该事件。有两个子类,分别代表Servlet及Portlet的请求事件。
事件监听器接口
事件广播器
5.6.2 解构Spring事件体系的具体实现
5.6.3 一个实例
事件源与这个事件。即事件源:
package com.baobaotao.event;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class MailSender implements ApplicationContextAware{
private ApplicationContext ctx;
//ApplicationContextAware的接口方法,以便容器启动时注入容器实例
@Override
public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
// TODO Auto-generated method stub
this.ctx = ctx;
}
public void sendMail(String to){
System.out.println("MailSender:模拟发送邮件...");
MailSendEvent mse = new MailSendEvent(this.ctx, to);
ctx.publishEvent(mse);
}
}
package com.baobaotao.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
public class MailSendEvent extends ApplicationContextEvent{
private String to;
public MailSendEvent(ApplicationContext source, String to){
super(source);
this.to = to;
}
public String getTo(){
return this.to;
}
}
事件监听器:
package com.baobaotao.event;
import org.springframework.context.ApplicationListener;
public class MailSendListener implements ApplicationListener<MailSendEvent>{
@Override
public void onApplicationEvent(MailSendEvent event) {
// TODO Auto-generated method stub
MailSendEvent msEvent = event;
System.out.println("MailSendListener:向" + msEvent.getTo() + "发送完一封邮件");
}
}
事件转化为一个Bean
<bean class="com.baobaotao.event.MailSendListener" />
<bean id="mailSender" class="com.baobaotao.event.MailSender" />
启动这个容器以及开启这个事件。
public class FactoryTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ClassPathXmlApplicationContext c = new ClassPathXmlApplicationContext("classpath:/com/baobaotao/event/beans.xml");
System.out.println("init BeanFactory.");
//事件源,即初始化了这个Bean
MailSender mailSender = c.getBean("mailSender", MailSender.class);
mailSender.sendMail("1254755805@qq.com");
//output
//init BeanFactory.
//MailSender:模拟发送邮件...
//MailSendListener:向1254755805@qq.com发送完一封邮件
}
}
5.7 小结
主要学习了容器事件体系的观察者模式。属性编辑器、外部属性的加密处理。