谈谈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>
0 0
原创粉丝点击