470 likes | 651 Views
Spring 简入简出. 引子 : 在没有 Spring 的日子里. EJB 的例子. EJB 容器架构 : 容器提供服务 组件提供业务逻辑. 口号 : 应用开发者只需 关注业务逻辑. 引子 : 在没有 Spring 的日子里. 写一个 EJB 组件真的只需要关注业务逻辑吗 ?. 一个最基本的 SLSB. 在没有 Spring 的日子里. 淘宝系统早期的各种 Factory. 在没有 Spring 的日子里. 淘宝系统早期的各种 Factory. BO 、 VOF 的构造方式类似. <ao-factories>
E N D
引子:在没有Spring的日子里 • EJB的例子 • EJB容器架构: • 容器提供服务 • 组件提供业务逻辑 口号: 应用开发者只需 关注业务逻辑
引子:在没有Spring的日子里 • 写一个EJB组件真的只需要关注业务逻辑吗? 一个最基本的SLSB
在没有Spring的日子里 • 淘宝系统早期的各种Factory
在没有Spring的日子里 • 淘宝系统早期的各种Factory BO、VOF 的构造方式类似 <ao-factories> <ao-factory name="XXXAO" class="com....XXXAO" /> <ao-factory name="YYYAO" class="com....YYYAO"> <property name="pName" value="pValue"/> </ao-factory> </ao-factories>
Spring的理念:一个目标,三个原则 • 目标 • 简化J2EE应用开发 • 原则 • 易事简为 • 难事善为 • 重用轮子 易事简为 难事善为 重用轮子 简
Spring的方式:一个中心,三个基础 • 中心 • POJO编程模型 • 基础 • 反向控制 • 面向切面 • 服务抽象 反向控制 (IoC) 面向切面 (AOP) 服务抽象 (Service Abstraction) POJO
Spring的言行一致性 • 目标 • 简化J2EE应用开发 • 原则 • 易事简为 • 难事善为 • 重用轮子 • 中心 • POJO编程模型 • 基础 • 反向控制 • 面向切面 • 服务抽象
POJO编程模型 • 通过POJI松散耦合的POJO • 对象合作:IoC将POJO根据依赖关系组装成一个对象网络 • 对象配置:IoC为POJO提供了一致、灵活的配置机制 • 公共服务:AOP以声明式非侵入的方式将企业服务应用于POJO之上
Spring框架布局 消除 重复代码 基于POJO 简单开发 支持企业服务
Inverse of Control:反向控制 • 好莱坞原则Don’t call me, I’ll call you. • 反向控制原则由框架调用应用代码、控制全局流程;应用代码不调用框架。反向控制是一种原则 • 反向控制原则的普遍性EJB、Servlet、Webx Pipeline、业务应用框架
反向控制实例: Alipay交易框架 • 由容器,而不是组件控制全局规则 • 容器 -> 交易核心框架 • 组件 -> 交易Action • 全局规则 -> action清单和序列、事务边界、并发控制等等
Dependency Injection:依赖注入 • 依赖注入是Spring运用反向控制原则解决配置管理和对象关系管理的手段。 • 依赖注入的优势: • 代码简化 • 配置方式统一 • 不依赖特定框架或对象查找API • 自文档化,显式表达依赖关系
DI的类型:设值注入 • 设值注入(setter injection) public interface HollywoodService { public void callMe(); } public class HollywoodServiceImpl implements HollywoodService { private hollywoodManager; public void setHollywoodService (HollywoodManager hollywoodManager) { this. hollywoodManager = hollywoodManager; } public void callMe() { System.out.println(“Don’t call me, I’ll call you!”);} } <beans> <bean id=“hollywoodService” class=“HollywoodServiceImpl”> <property name=“hollywoodManager”> <ref bean=“hollywoodManager”/> </property> </bean> </beans>
DI的类型:构造器注入 • 构造器注入(constructor injection) public interface HollywoodService { public void callMe(); } public class HollywoodServiceImpl implements HollywoodService { private hollywoodManager; public HollywoodServiceImpl (HollywoodManager hollywoodManager) { this. hollywoodManager = hollywoodManager; } public void callMe() { System.out.println(“Don’t call me, I’ll call you!”);} } <beans> <bean id=“hollywoodService” class=“HollywoodServiceImpl”> <construtor-arg> <ref bean=“hollywoodManager”/> </ construtor-arg> </bean> </beans>
DI的类型:方法注入 • 方法注入(method injection) public interface HollywoodService { public void callMe(); } public abstract class HollywoodServiceImpl implements HollywoodService { protected abstract HollywoodManager getHollywoodManager(); public void callMe() { System.out.println(“Don’t call me, I’ll call you!”); getHollywoodManager().notify(); } }
DI的类型:方法注入(cont.) <beans> <bean id=“hollywoodService” class=“HollywoodServiceImpl”> <lookup-method name=“getHollywoodManager” bean=“hollywoodManager”/> </bean> <bean id=“hollywoodManager” singleton=“false” class=“HollywoodManagerImpl”/> </beans> • 方法注入(cont.) • 容器在运行时动态生成方法的实现 • 实现的方式是通过容器对象查找 • 典型应用场景是将一个非单例的bean注入给一个单例的bean,实现每次引用时可以使用一个新的实例,满足线程安全性要求。
DI实例:FactoryBean • FactoryBean是一个特别的接口,当它的实现类作为bean定义时,它代表的不是该类的实例,而是由它创建的对象。 • FactoryBean实例: • JNDIObjectFactoryBean: 查找JNDI获取对象;应用与JNDI API解耦。 <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>ZFBDataSource</value> </property> </bean>
DI实例:FactoryBean(cont.) • PropertyPathFactoryBean: 获取指定bean的属性值 <bean id=“person" class=“…“/> <bean id=“theCity” class=“org.springframework.beans.factory.config.PropertyPathFactoryBean ”> <property name=“targetObject"><ref local=“person”/></property> <property name=“propertyPath"><value>address.city</value></property> </bean> • MethodInvokingFactoryBean: 调用指定bean的方法,获取返回值
DI应用:基于DB的密钥管理器 • 场景: 应用中的加解密操作过程中需要使用密钥,出于安全起见,密钥不应该出现在配置文件或源代码中。在没有专用硬件支持的情况下,一个可行的选择是将密钥保存在DB中,并在系统启动时将密钥注入到加解密操作对象中,如何实现?
DI应用:基于DB的密钥管理器(cont.) • public interface KeyManager { • public String getKeyByName(String keyName); • } • public class KeyManagerImpl { • private KeyDAO keyDAO; • private Map keyMap; • public void init() { • keyMap = keyDAO.findAll(); • } • public String getKeyByName(String keyName) { • return (String) keyMap.get(keyName) • }; • }
DI应用:基于DB的密钥管理器(cont.) • public interface CryptoService { • public String encrypt(String clearText); • } • public class CryptoServiceImpl { • private String key; • private Encrypter encrypter; • public String encrypt(String clearText) { • encrypter.encrypt(key, clearText); • } • public void setKey(String key) { • this.key = key; • } • }
DI应用:基于DB的密钥管理器(cont.) • <bean id="keyManager" class="KeyManagerImpl“ init-method=“init”> • <property name="keyDAO"><ref bean="keyDAO"/></property> • </bean> • <bean id="cryptoService" class=cryptoServiceImpl"> • <property name="key"> • <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> • <property name="targetObject"><value>keyManager</value></property> • <property name="targetMethod"><value>getKeyByName</value></property> • <property name="arguments"> • <list><value>cryptoKey</value></list> • </property> • </bean> • </property> • </bean>
AOP:面向切面编程 • 理解AOP: 交叉关注点 交叉关注点 横切业务领域 与架构分层
AOP:面向切面编程 • 理解AOP: 关注点分解
AOP核心概念 • 切面(aspect)需要实现的交叉功能逻辑。 • 通知?(advice)切面的实现。 • 连接点(jointpoint)应用执行中需要插入切面的点。 • 切入点(pointcut)一组连接点的集合,切面将应用于这组连接点。
AOP核心概念 • 引入(introduction)为现存的类引入新的方法或属性。 • 目标对象(target)被通知的对象。 • 代理(proxy)将通知应用于目标对象之后得到的对象。 • 织入(weaving)将通知应用于目标对象的过程。
Spring的AOP支持 • Spring基于代理实现对AOP的支持 • 通过J2SE dynamic proxies,可以代理任意Java 接口。 • 通过CGLIB字节码生成,可以代理Java类。 • Spring的代理机制可以支持由Spring容器创建的bean,无法支持应用中自行new出来的bean。
创建AOP通知示例 • 环绕通知示例 public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; } public class PerformanceMonitorInterceptor implements MethodInterceptor { private int threshold = 1; .. public Object invoke(MethodInvocation invocation) throws Throwable { long startTime = System.currentTimeMillis(); try { return invocation.proceed(); } finally { long elapseTime = System.currentTimeMillis() - startTime; if (elapseTime > threshold) { logger.info("执行时间超过阈值,实际执行时间为" + elapseTime + "毫秒。"); } } } }
定义切入点 • Spring提供的静态切入点 • NameMatchMethodPointcutAdvisor: 匹配调用方法的名字 • RegexpMethodPointcutAdvisor: 使用正则表达式匹配调用的类名与方法 <bean id="samplePointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <property name="mappedName"> <value>order*</value> </property> <property name="advice"> <ref bean="sampleAdvice"/> </property> </bean>
用ProxyFactoryBean织入通知 <bean id="tradeDealer" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>com.beyond.biz.tradecore.TradeDealer</value> </property> <property name="interceptorNames"> <list> <value>tradeDealerPointcutAdvisor</value> </list> </property> <property name="target"><ref bean="tradeDealerTarget"/></property> </bean>
自动创建代理 • BeanNameAutoProxyCreator为符合相同命名规则的Bean应用一个或一组切面。 • DefaultAdvisorAutoProxyCreator实现了BeanPostProcessor接口。当ApplicationContext读入所有Bean的配置信息后,DefaultAdvisorAutoProxyCreator将扫描上下文,寻找所有的Advisor。它将这些Advisor应用到所有符合Advisor切入点的Bean中。
自动创建代理示例 <bean id="daoAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list><value>daoPerformanceMonitorInterceptor</value></list> </property> <property name="beanNames"> <value>*DAO</value> </property> </bean>
AOP应用示例:将消息发送统一移到事务外 • 应用场景: 在对象B中,发送邮件(通过异步消息实现)被明智地放在事务外,避免出现事务回滚(相当于业务操作未执行)而邮件发送出去的情况。但对象A的doA()方法在事务中调用对象B的doB()方法,并且假设事务传播属性为PROPAGATION_REQUIRED,造成发送邮件操作实际包含在事务中了,于是当doA()方法的事务回滚时,邮件已经发送出去,给用户带来困扰。
AOP应用示例:将消息发送统一移到事务外(cont.) • 需求:在不改变业务代码的前提下,确保系统中所有的异步消息都移到事务提交成功之后执行 • 这个需求(异步消息需要在事务提交之后再发送)是一个交叉关注点,横切所有的消息发送操作,因此,适于用AOP来实现。
AOP应用示例:将消息发送统一移到事务外(cont.) • 第1步,创建Advice,拦截消息发送操作 public class AfterCommitCommandInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { if (StringUtil.equals(invocation.getMethod().getName(), "execute")) { // 只对execute方法进行拦截 if (TransactionSynchronizationManager.isActualTransactionActive()) { // 由于当前处于活动事务中,因此向事务同步管理器注册同步对象,在事务提交后再执行真正的命令发送。 TransactionSynchronizationManager.registerSynchronization(new MethodInvocationTransactionSynchronization(invocation)); return new ResultSupport(true, ResultCode.SUCCESS); } else { return invocation.proceed(); } } else { return invocation.proceed(); } } }
AOP应用示例:将消息发送统一移到事务外(cont.) • 辅助类:MethodInvocationTransactionSynchronization的实现 public class MethodInvocationTransactionSynchronization implements TransactionSynchronization { private MethodInvocation methodInvocation; public void afterCompletion(int status) { if (status == STATUS_COMMITTED) { try { Object result = methodInvocation.proceed(); } catch (Exception e) { } } } }
AOP应用示例:将消息发送统一移到事务外(cont.) • 第2步,配置Spring自动创建代理,应用Advice <bean id="afterCommitCommandInterceptor" class="com.iwallet.biz.common.aop.AfterCommitCommandInterceptor"/> <bean id="afterCommitCommandAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="interceptorNames"> <list><value>afterCommitCommandInterceptor</value></list> </property> <property name="beanNames"> <!-- 这些都是发送异步消息的CommandDispatcher Bean --> <value>messageDispatcher,bankQueryDispatcher,externalNotifyDispatcher,eventMessageDispatcher,gotoneDispatcher</value> </property> </bean>