谈谈Spring
来源:互联网 发布:java 挂起当前线程 编辑:程序博客网 时间:2024/05/20 20:57
Spring的基本理念:简化Java开发
基于POJO的轻量级和最小侵入性编程-POJO通过依赖注入和面向接口实现松耦合-DI基于切面进行声明式编程实现重用和分离关注点-AOP通过模板减少样板式代码,如JDBCTemplate-Template
Spring框架的核心组件只有三个:Core、Context和Bean。
Bean组件
org.springframework.beans包下,包括Bean的定义、Bean的创建及对Bean的解析。
Spring Bean的创建是典型的工厂模式,它的顶级接口是BeanFactory,最终的默认实现是DefaultListableBeanFactory。
Bean的定义主要由BeanDefinition描述。当Spring成功解析一个节点后,在Spring的内部它就被转换成BeanDefinition对象,以后所有的操作都是对这个对象进行。实现类是RootBeanDefinition
Bean的生命周期
如果Bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet()接口方法。如果Bean使用init-method声明了初始化方法,则该初始化方法会被调用。
如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法。如果Bean使用destory-method声明了销毁方法,则该销毁方法也会被调用。
Context组件
作为Spring IOC的容器,给Spring提供了一个运行时的环境。ApplicationContext继承了BeanFactory,另外ApplicationContext继承了ResourceLoader接口,使得ApplicationContext可以访问到任何外部资源。BeanFactory是最简单的容器,提供基本的DI支持。ApplicationContext基于BeanFactory之上构建,并提供面向应用的服务,例如从属性文件解析文本信息的能力等。
Spring常用的三个ApplicationContext:
ClassPathXmlApplicationContext-从classPath下的XML配置文件中加载上下文定义,把应用上下文定义文件当做类资源,即Resource
FileSystemXmlApplicationContext-读取文件系统下的XML配置文件并加载上下文定义
XmlWebApplicationContext-读取Web应用下的XML配置文件并装载上下文定义
Core组件
一个重要的组成部分就是定义了资源的访问方式,即把所有资源都抽象成一个接口Resource。Resource接口继承了InputStreamSource接口,在这个接口中有个getInputStream方法,返回的是InputStream类。这样所有的资源都可以通过InputStream类来获取,屏蔽了资源的提供者。Context把资源的加载、解析和描述工作委托给了ResourcePatternResolver类来完成。
有一个非常重要的Bean,FactoryBean,它是个工厂Bean,可以产生Bean的Bean。Spring获取FactoryBean本身的对象是通过在前面加上&来完成的。
如何精通Spring就看你有没有掌握好Spring有哪些扩展点,以及如何使用它们。
对Spring的IOC容器来说,主要有BeanFactoryPostProcessor和BeanPostProcessor,它们分别在构建BeanFactory和构建Bean对象时调用。还有就是InitializingBean和DisposableBean,它们分别在Bean实例创建和销毁时被调用。
BeanFactory的构建入口是AbstractApplicationContext类的refresh()方法:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex); // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } }}
Spring IOC - Spring的核心,AOP的切面,首先得是一个Bean
合作对象的引用或依赖关系的管理由具体对象来完成,会导致代码的高度耦合,紧密耦合的代码难以测试,难以复用。通过将对象的创建和对象间依赖关系交由IOC容器来管理,实现了对象间的解耦,提高了代码的可测试性。而且Bean成为了组件,促进了代码的复用。
如果对面向对象系统中的对象进行简单分类,会发现除了一部分是数据对象外,其他很大一部分对象是用来处理数据的。所以Spring Bean默认的作用域(scope)是单例(singleton)。通过scope属性来指定作用域:
<bean id="ticket" class="com.springinaction.springidol.Ticket" scope="prototype"/>
scope除了singleton、Prototype外还有request、session等
在具体的注入实现中,有接口注入,setter注入和构造器注入
setter注入:
<bean id="kenny" class="com.spring.Instrumentalist"> <property name="song" value="Jingle Bells"/> <property name="instrument" ref="saxophone"/></bean>
构造器注入:
<bean id="poeticDuck" class="com.springinaction.springidol.PoeticJuggler"> <constructor-arg value="15"/> <constructor-arg ref="sonnet29"/></bean>
通过工厂方法创建Bean
有时候静态工厂方法是实例化对象的唯一方法,比如当构造函数是私有时。Spring支持通过元素的factory-method属性指定一个静态方法,来创建Bean:
<bean id="theStage" class="com.springinaction.springidol.Stage" factory-method="getInstance"/>
Spring支持几种不同的用于自动装配的注解:
Spring自带的@Autowired注解(拥有required属性),先byType.(配合@Qualifier,byName).org.springframework.beans.factory.annotation.Autowired;
JSR-330(Java依赖注入规范)的@Inject注解(没有required属性),先byType.(配合@Named,byName).javax.inject.Inject;
JSR-250的@Resource注解,先byName.javax.annotation.Resource
自动检测Bean
使用可以消除Spring配置中的和元素,通过使用@Autowired等自动装配注解,但仍需要使用显示定义Bean.
而除了完成与一样的工作,还允许Spring自动检测Bean和定义Bean。base-package属性标识了元素所扫描的包。
默认情况下,查找使用构造型(steretype)注解所标注的类:(还有过滤器类型为assignable的,扫描派生于expression属性所指定类型的那些类)
@Component - 通用的构造型注解,标识该类为Spring组件
@Controller - 标注将该类定义为Spring MVC controller
@Repository - 标注将该类定义为数据仓库
@Service - 标识将该类定义为服务
自动注册所有的Instrument实现类,但除了使用自定义@SkipIt注解的类:
<context:component-scan base-package="com.spring"> <context:include-filter type="assignable" expression="com.spring.Instrument"/> <context:exclude-filter type="annotation" expression="com.spring.SkipIt"/></context:component-scan>
Spring AOP
1.可重用,AOP把遍布应用各处的功能分离出来形成可重用的组件
2.分离关注点,将日志、事务、安全等系统服务从核心业务逻辑中分离出去,使组件保持高内聚,只需关注自身业务。
JDK的动态代理是利用反射机制生成一个实现代理接口的匿名类(该类继承自类java.lang.reflect.Proxy,类中的所有方法都是final的),在调用具体方法前调用InvocationHandler来处理:
在JDK的java.lang.reflect包下有个Proxy类,它正是构造代理类的入口。有一个静态方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) public PersonBean getOwnerProxy(PersonBean person){ return (PersonBean)Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new OwnerInvocationHandler(person)); } OwnerInvocationHandler.java: public class OwnerInvocationHandler implements InvocationHandler { private PersonBean personBean; public OwnerInvocationHandler(PersonBean personBean){ this.personBean = personBean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try{ if(method.getName().startsWith("get")){ return method.invoke(personBean, args); }else if(method.getName().equals("setHotOrNotRating")){ throw new IllegalAccessException(); }else if(method.getName().startsWith("set")){ return method.invoke(personBean, args); } }catch(InvocationTargetException e){ e.printStackTrace(); } return null; }}
构造代理类是在ProxyGenerator的generateProxyClass方法中进行的,该类在sun.misc包下
说道反射,那就具体讲讲反射
简单说,就是在运行时可以动态的获取类信息以及调用对象的方法等
Java反射主要的类有: Class,Constructor,Field和Method。而Class是Reflection的起源。针对任何想勘探的class,唯有先为它产生一个Class object,接下来才能经由后者唤起数十多个Reflection APIs。
用户调用的目标方法都被代理到在InvocationHandler类中定义的唯一方法invoke()中
反射的最大应用就是框架
问题1:为什么说反射的效率差呢?
比如,a.getxxx属于直接调用,而反射要读类的元数据,也就是class文件,然后在转换成相应的对象,如Method,Field等
所以为了优化反射的性能,一般要在反射之上加缓存
问题2:反射和CGLIB调用ASM的区别?
1.反射是读取持久堆上存储的类信息。而ASM是直接处理.class字节码的小工具
2.反射只能读取类信息,而ASM除了读还能写
3.反射读取类信息时需要进行类加载处理,而ASM则不需要将类加载到内存
4.反射相对ASM来说使用方便,想直接操纵ASM需要有JVM指令基础
问题3:那么ASM又是什么?
在运行期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码。当前有很多开源框架可以完成这些功能,如ASM,Javassist。
ASM是一个Java字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为。(通过ClassWriter接口)
不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。
Javassist是一个开源的分析、编辑和创建Java字节码的类库。它已加入开发源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。其主要优点是简单、快速。直接使用Java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。(通过ClassPool接口)
问题4:为什么要使用动态代理而不是静态代理?
静态代理类的class在系统运行时,此class就已经存在了。所以当大量使用静态代理时,会使系统内的类的规模增大,并且不易维护。并且Proxy和RealSubject的功能本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。
动态代理解决了这个问题,在运行状态中,需要代理的地方,根据Subject和RealSubject,动态的创建一个Proxy,用完之后,就会销毁,避免了Proxy角色的class在系统中冗杂的问题。
在代理模式中,代理Proxy和RealSubject应该实现相同的功能,这一点非常重要。
而在面向对象编程中,如果我们想要约定Proxy和RealSubject可以实现相同的功能,有两种方式:
1,定义一个功能接口,让Proxy和RealSubject来实现这个接口
2,通过继承
其中,JDK创建动态代理的机制是以第一种思路设计的,而CGLIB则是以第二种思路设计的。
CGLIB,实现MethodInterceptor接口,来处理对代理类上所有方法的请求。这个接口和JDK动态代理InvocationHandler的功能和角色是一样的。
public class Programmer { public void code(){ System.out.println("I'm a Programmer"); }}import java.lang.reflect.Method;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class Hacker implements MethodInterceptor{ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("begin..."); proxy.invokeSuper(obj, args); System.out.println("end..."); return null; }}import net.sf.cglib.proxy.Enhancer;public class Test { public static void main(String[] args){ Programmer programmer = new Programmer(); Hacker hacker = new Hacker(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(programmer.getClass()); enhancer.setCallback(hacker); Programmer proxy = (Programmer)enhancer.create(); proxy.code(); }}output: begin... I'm a Programmer end...
Spring AOP是实现其自身的扩展点来完成这个特性的
Spring的代理方式有JDK动态代理和CGLIB代理,对这两种方式使用了策略模式。抽象策略是AopProxy接口,CglibAopProxy和JDKDynamicAopProxy分别代表两种策略的实现方式,ProxyFactoryBean代表Context的角色
package org.springframework.aop.framework;public interface AopProxy{ Object getProxy(); Object getProxy(ClassLoader classLoader);}package org.springframework.aop.framework;/***Spring AOP框架 基于JDK的动态代理类Proxy的AopProxy的实现*基于JDK的动态代理只能用来代理接口中声明的方法,在class中声明*的无法代理*/final class JdkDynamicAopProxy implements AopProxy,InvocationHandler,Serializable/***Spring AOP框架 基于CGLIB的AopProxy的实现*CGLIB(Code Generation Library),封装了ASM*通过类继承实现动态代理机制*/class CglibAopProxy implements AopProxy,Serializablepublic interface AopProxyFactory{ //使用给定的AOP配置,创建一个AopProxy AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;} /*** 默认AopProxyFactory的实现,创建一个CGLIB代理类,或者JDK动态代理类* 一般而言,通过指定proxyTargetClass来强制使用CGLIB代理;或者是指定* 一个或多个接口来使用JDK动态代理* 也就是说,在同等条件下,优先使用JDK动态代理*/public class DefaultAopProxyFactory implements AopProxyFactory,Serializable{ @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }}
AOP术语:
通知(advice)、切点(pointcut)和连接点(join point)
在一个或多个连接点上,将切面的功能(通知)织入到程序的执行过程中
Spring切面可以应用5种类型的通知:
Before - 在方法被调用之前调用通知
After - 在方法完成之后调用通知,无论方法执行是否成功
After-returning - 在方法成功执行之后调用通知
After-throwing - 在方法抛出异常后调用通知
Around - 通知包裹了被通知的方法,在被通知的方法之前和调用之后执行自定义的行为
引入(Introduction),允许我们向现有的类添加新方法或属性。
织入(Weaving),是将切面应用到目标对象来创建新的代理对象的过程。
在目标对象的生命周期里有多个点可以进行织入:
编译期 - 切面在目标类编译时被织入。这种方式需要特殊的编译期。AspectJ的织入编译期就是以这种方式织入切面的
类加载期 - 切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的LTW(load-time weaving)就支持以这种方式织入切面。
运行期 - 切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个代理对象。Spring AOP就是以这种方式织入切面的。
不同的AOP框架,在连接点模型上有强弱之分,有的允许对字段修饰符级别应用通知,而另一些只支持与方法调用相关的连接点。它们织入切面的方式和时机也不同。
现有的AOP框架有:AspectJ(Java语言扩展)、JBoss AOP、Spring AOP
Spring提供了4种AOP支持:
基于代理的经典AOP
@AspectJ注解驱动的切面
纯POJO切面
注入式AspectJ切面
Spring只支持方法连接点,局限于方法拦截。而AspectJ和JBoss,除了方法切点,还提供了字段和构造器接入点,可以拦截对字段的修改,或者在Bean创建时应用通知。
public class Performer { public void perform(){ System.out.println("The show time..."); }}public class Audience { public void takeSeats(){ System.out.println("The audience is taking their seats."); } public void turnOffCellPhones(){ System.out.println("The audience is turning off their cellphones"); } public void applaud(){ System.out.println("CLAP CLAP CLAP CLAP"); } public void demandRefund(){ System.out.println("Boo! We want our money back!"); }}applicationContext.xml:<bean id="audience" class="com.donson.aop.Audience"/><bean id="performer" class="com.donson.aop.Performer"/><aop:config> <aop:aspect ref="audience"> <aop:pointcut expression="execution(* com.donson.aop.Performer.perform(..))" id="performance"/> <aop:before method="takeSeats" pointcut-ref="performance"/> <aop:before method="turnOffCellPhones" pointcut-ref="performance"/> <aop:after-returning method="applaud" pointcut-ref="performance"/> <aop:after-throwing method="demandRefund" pointcut-ref="performance"/> </aop:aspect></aop:config>public class TestInjected { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Performer performer = (Performer)context.getBean("performer"); performer.perform(); }}output: The audience is taking their seats. The audience is turning off their cellphones The show time... CLAP CLAP CLAP CLAP
注:因为使用了AspectJ切点表达式,所以需要依赖包aspectjweaver.jar
环绕通知-可以在前置通知和后置通知之间共享信息:
import org.aspectj.lang.ProceedingJoinPoint;public class Audience {public void watchPerformance(ProceedingJoinPoint joinpoint){ try{ System.out.println("The audience is taking their seats."); System.out.println("The audience is turning off their cellphones"); long start = System.currentTimeMillis(); joinpoint.proceed();//执行被通知的方法 long end = System.currentTimeMillis(); System.out.println("CLAP CLAP CLAP CLAP"); System.out.println("The performance took " + (end-start) + " milliseconds."); }catch(Throwable t){ System.out.println("Boo! We want our money back!"); } }} applicationContext.xml: <aop:config> <aop:aspect ref="audience"> <aop:pointcut expression="execution(* com.donson.aop.Performer.perform(..))" id="performance"/> <aop:around method="watchPerformance" pointcut-ref="performance"/> </aop:aspect> </aop:config>
通知可以获取被通知方法的参数:
public interface MindReader { void interceptThoughts(String thoughts); String getThoughts();}public class Magician implements MindReader { private String thoughts; public void interceptThoughts(String thoughts) { System.out.println("Intercepting volunteer's thoughts"); this.thoughts = thoughts; System.out.println("thoughts:"+thoughts); } public String getThoughts() { return thoughts; }}public interface Thinker { void thinkOfSomething(String thoughts);}public class Volunteer implements Thinker { private String thoughts; public void thinkOfSomething(String thoughts) { this.thoughts = thoughts; } public String getThoughts(){ return thoughts; }}applicationContext.xml:<bean id="magician" class="com.donson.aop.Magician"/><bean id="volunteer" class="com.donson.aop.Volunteer"/><aop:config> <aop:aspect ref="magician"> <aop:pointcut expression="execution(* com.donson.aop.Thinker.thinkOfSomething(String)) and args(thoughts)" id="thinking"/> <aop:before method="interceptThoughts" arg-names="thoughts" pointcut-ref="thinking"/> </aop:aspect></aop:config>public class TestInjected { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Thinker thinker = (Thinker)context.getBean("volunteer"); thinker.thinkOfSomething("To do or not to do?"); }}outputs: Intercepting volunteer's thoughts thoughts:To do or not to do?
注解切面:@Aspect
import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;@Aspectpublic class Audience { @Pointcut("execution(* com.donson.aop.Performer.perform(..))") public void performance(){} @Before("performance()") public void takeSeats(){ System.out.println("The audience is taking their seats."); } @Before("performance()") public void turnOffCellPhones(){ System.out.println("The audience is turning off their cellphones"); } @AfterReturning("performance()") public void applaud(){ System.out.println("CLAP CLAP CLAP CLAP"); } @AfterThrowing("performance()") public void demandRefund(){ System.out.println("Boo! We want our money back!"); } @Around("performance()") public void watchPerformance(ProceedingJoinPoint joinpoint){ try{ System.out.println("The audience is taking their seats."); System.out.println("The audience is turning off their cellphones"); long start = System.currentTimeMillis(); joinpoint.proceed();//执行被通知的方法 long end = System.currentTimeMillis(); System.out.println("CLAP CLAP CLAP CLAP"); System.out.println("The performance took " + (end-start) + " milliseconds."); }catch(Throwable t){ System.out.println("Boo! We want our money back!"); } } }applicationContext.xml://将在Spring上下文中创建一个AnnotationAwareAspectJAutoProxyCreator类<aop:aspectj-autoproxy/>
注入@Aspect切面
public aspect JudgeAspect { private CriticismEngine criticismEngine; public void setCriticismEngine(CriticismEngine criticismEngine){ this.criticismEngine = criticismEngine; } public JudgeAspect(){} pointcut performance() : execution(* perform(..)); after() returning() : performance() { System.out.println(criticismEngine.getCriticism()); }}applicationContext.xml://JudgeAspect切面是由AspectJ在运行期创建的。等到Spring有机会为JudgeAspect注入CriticismEngine时,JudgeAspect已经被实例化了。所以,我们需要一种方式为Spring获得已经由AspectJ创建的JudgeAspect句柄,从而可以注入CriticismEngine。而所有的AspectJ切面都提供了一个静态的aspectOf()方法,该方法返回切面的一个单例。<bean class="com.donson.aop.JudgeAspect" factory-method="aspectOf"> <property name="criticismEngine" ref="criticismEngine"/></bean>
- 谈谈Spring
- 谈谈spring理解
- 谈谈spring的缓存
- 谈谈对Spring的理解
- 谈谈对spring的理解
- 谈谈对Spring的理解
- 谈谈对Spring的理解
- 谈谈Spring 中事务的隔离级别
- 谈谈对Spring IOC的理解
- 谈谈对Spring IOC的理解
- 谈谈对Spring IOC的理解
- 谈谈对Spring IOC的理解
- 谈谈对Spring IOC的理解
- 谈谈对Spring IOC的理解
- 谈谈spring中的拦截器interceptor
- 谈谈对Spring IOC的理解
- 谈谈对Spring IOC的理解
- 谈谈spring中bean的名字
- (八十八)VFL语言初步 - 实现布局
- Redis 基本类型
- HDU 1372 Knight Moves
- EventBus 源码解析
- [LeetCode]Construct Binary Tree from Inorder and Postorder Traversal
- 谈谈Spring
- linux+apache+mysql+php平台构建及环境配置
- TableView内存优化 ---- 建立缓存池
- JAVA 并发编程-读写锁之模拟缓存系统(十一)
- JAVA设计模式之策略模式
- 预处理指令
- [转]远程桌面连接原理
- ios把BOOL和OC对象互换
- 数组