今日分享开始啦,请大家多多指教~
Spring概述
概念
Spring是一个轻量级Java开发框架,最早有RodJohnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEEfull-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。
Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependencyinjection,DI)和面向切面编程(aspect-orientedprogramming,AOP)。
为了降低Java开发的复杂性,Spring采取了以下4种关键策略。
基于POJO的轻量级和最小侵入性编程通过依赖注入和面向接口实现松耦合基于切面和惯例进行声明式编程通过切面和模板减少样板式代码优缺点
优点
方便解耦,简化开发:Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。AOP编程的支持:Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序。方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。降低JavaEEAPI的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。缺点
Spring依赖反射,反射影响性能
使用门槛升高,入门Spring需要较长时间
Spring的模块
springcore:提供了框架的基本组成部分,包括控制反转(InversionofControl,IOC)和依赖注入(DependencyInjection,DI)功能。springbeans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。springcontext:构建于core封装包基础上的context封装包,提供了一种框架式的对象访问方法。springjdbc:提供了一个JDBC的抽象层,消除了繁琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC。springaop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。springWeb:提供了针对Web开发的集成特性,例如文件上传,利用servletlisteners进行ioc容器初始化和针对Web的ApplicationContext。springtest:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。Spring框架中都用到了那些设计模式
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例。单例模式:Bean默认为单例模式。代理模式:SpringAOP功能用到了JDK的动态代理和CGLIB字节码生成技术。模板方法:用来解决代码重复的问题,比如:RestTemplate,JmsTemplate,JpaTemplate、refresh。观察者模式:定义对象键是一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到被制动更新,如Spring中listener的实现–ApplicationListener。SpringIOC
IOC是什么?
IOC(InversionofControl)即控制反转,简单说就是把原来代码需要实现的对象创建、依赖反转给容器来帮忙实现,需要创建一个容器并且需要一种描述让容器指导要创建对象之间的关系,在Spring中管理对象及其依赖关系是通过Spring的IOC容器实现的。
IOC的实现方式有依赖注入和依赖查找是,由于依赖查找用的很少,因此IOC也叫做依赖注入。依赖注入指对象被动地接受依赖类而不用自己主动去找,对象不是从容器中查找它依赖的类,而是在容器实例化对象时,主动将它所依赖的类注入给它。
依赖注入更加准确地描述了IOC的设计理念,所谓依赖注入(DependencyInjection),即组件之间的依赖关系有容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用程序中的各个关联的组件之中,组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
举例:一个Car类需要一个Engine的对象,那么一般需要手动new一个Engine,利用IOC就只需要定义一个私有的Engine类型的成员变量,容器会在运行时创建一个Engine的实例对象并将引用自动注入给成员变量。
控制反转是:关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。
IOC容器初始化的过程
基于xml的容器初始化
当创建一个ClassPathXmlApplicationContext时,构造方法做了两件事
调用父容器的构造方法为容器设置好Bean资源资源加载器调用父类的setConfigLocations方法设置Bean信息的定位路径ClassPathXmlApplicationContext通过调用父类AbstractApplicationContext的refresh方法启动整个IOC容器对Bean定义的载入过程,refresh是一个模板方法,规定了IOC容器的启动流程,在创建IOC容器前如果已有容器存在,需要把已有的容器销毁,保证在refresh方法后使用的是新创建的IOC容器
容器创建后通过loadBeanDefinitions方法加载Bean配置资源,该方法做两件事:
调用资源加载器的方法获取需要加载的资源;
真正执行加载功能,由子类XmlBeanDefinitionReader实现。加载资源时:
首先解析配置文件路径,读取配置文件内容,然后通过xml解析器将Bean配置信息转换成文档对象;之后按照SpringBean定义规则对文档对象进行解析。SpringIOC容器中注解解析的Bean信息放在一个HashMap集合中,key是字符串,值是BeanDefinition,注册过程中需要synchronized保证线程安全,当配置文件的Bean被解析且被注册到IOC容器中后,初始化就算真正完成了,Bean定义信息已经可以使用且可被检索。SpringIoC容器的作用就是对这些注册的Bean定义信息进行处理和维护,注册的Bean定义信息是控制反转和依赖注入的基础。
测试类
基于注解的容器初始化
分为两种:
直接将注解Bean注解到容器中,可以在初始化容器时注册,也可以在容器创建之后手动注册,然后刷新容器使其对注册的注解Bean进行处理。通过扫描指定包及其子包的所有类处理,在初始化注解容器时指定要自动扫描的路径。
或者
在xml中开启注解装配
context:annotation-config/context:annotation-config
然后使用ClassPathXmlApplicationContext加载xml配置文件
依赖注入的实现方法
构造方法注入
IOCServiceProvider会检查被注入对象的构造方法取得它所需要的依赖对象列表,进而为其注入相应的对象。这种方法的优点是在对象构造完成后就处于就绪状态,可以马上使用。缺点是当依赖对象较多时,构造方法的参数列表会比较长,构造方法无法被继承,无法设置默认值。对于非必需的依赖处理可能需要引入多个构造方法,参数数量的变动可能会造成维护的困难。
setter方法注入
当前对象要只需要为其依赖对象对应的属性添加setter方法,就可以通过setter方法将依赖对象注入中。setter方法注入在描述性上要比构造方法注入强,并且可以被继承,允许设置默认值。缺点是无法在对象构造完成后马上进入就绪状态。
构造器依赖注入和Setter方法注入的区别
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
接口注入
必须实现某个接口,接口提供方法来为其注入依赖对象。使用少,因为它强制要求被注入对象实现不必要接口,侵入性强,已在Spring4中废除。
依赖注入的过程?
getBean方法获取Bean实例,该方法调用doGetBean,doGetBean真正实现从IOC容器获取Bean的功能,也是触发依赖注入的地方。
具体创建Bean对象的过程是由ObjectFactory的createBean完成的,该方法主要通过createBeanInstance方法生成Bean包含的Java对象实例和populateBean方法对Bean属性的依赖注入进行处理。
在populateBean方法中,注入过程主要分为两种情况:
1.属性值类型不需要强制转换时,不需要强制类型转换时,不需要解析属性值,直接进行依赖注入。
2.属性类型需要强制转换时。
首先解析属性值;然后对解析后的属性值进行依赖注入。依赖注入的过程就是将Bean对象实例设置到它所依赖的Bean对象的属性上。真正的依赖注入是通过setPropertyValues方法实现的,该方法使用了委派模式。BeanWrapperImpl类负责对完成初始化的Bean对象进行依赖注入
对于非集合类型属性,使用JDK反射,通过属性的setter方法为属性设置注入后的值;对于集合类型的属性,将属性值解析为目标类型的集合直接赋值给属性。当容器对Bean的定位、载入、解析和依赖注入完成后就不再需要手动创建对象,IOC容器会自动为我们创建对象并且依赖注入。
Bean的生命周期
Spring容器读取XML配置文件中bean定义并实例化Bean;Spring根据Bean的定义设置属性值;如果该Bean实现了BeanNameAware接口,Spring将Bean的id传递给setBeanName()方法;如果该Bean实现了BeanFactoryAware接口,Spring将BeanFactory传递给setBeanFactory()方法;如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用。此时Bean就已经准备就绪了,可以被应用程序使用了,他们一直驻留在容器中,直到容器被销毁。
果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。自己定制初始化和注销方法
XML方式通过配置bean标签中的init-Method和destory-Method指定自定义初始化和销毁方法。
注解方式通过
PreConstruct和PostConstruct注解指定自定义初始化和销毁方法。Bean的作用范围
通过scope属性指定bean的作用范围,包括:
singleton:单例模式,是默认作用域,不管收到多少Bean请求每个容器中只有一个唯一的Bean实例prototype:原型模式,和singleton相反,每次Bean请求都会创建一个新的实例request:每次HTTP请求都会创建一个新的Bean并把它放到request域中,在请求完成之后Bean会失效并被垃圾收集器回收session:和request类似,确保每个session中有一个Bean实例,session过期后Bean会随之失效
globalsession:当应用部署在Portlet容器时,如果想让Portlet公用全局存储变量,那么该变量需要存储在globalsession中Bean线程安全性
Spring的单例Bean不是线程安全的
Spring中的Bean默认是单例模式,Spring框架中没有对单例Bean进行多线程的封装处理。
实际上大部分时候springbean无状态的(比如dao类),所有某种程度上来说bean也是安全的,但如果bean有状态的话(比如viewmodel对象),那就要开发者自己去保证线程安全了,最简单的就是改变bean的作用域,把“singleton”变更为“prototype”,这样请求bean相当于newBean()了,所以就可以保证线程安全了。
有状态:有数据存储功能无状态:不会保存数据Spring如何处理线程并发问题
只有无状态的Bean才会在多线程下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据访问的冲突,因为每一个线程都拥有自己的变量副本,从而就没有必要对该变量进行同步,ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
扩展问题
创建Bean的方式
xml
默认无参构造方法,只需要指明bean标签中的id和class属性,如果没有无参构造方***报错。静态工厂方法,通过bean标签中的class属性指明静态工厂,factory-method属性指明静态工厂方法。实例工厂方法,通过ean标签中的factory-bean属性指明实例工厂,factory-method属性指明实例工厂方法。注解
Component把当前类对象存入Spring容器中,相当于在xml中配置一个bean标签。value属性指定bean的id,默认使用当前类的首字母小写的类名。Controller,Service,Repository三个注解都是Component的衍生注解,作用及属性都是一模一样的。只是提供了更加明确语义,Controller用于表现层,Service用于业务层,Repository用于持久层。如果注解中有且只有一个value属性要赋值时可以省略value。如果想将第三方的类变成组件又没有源代码,也就没办法使用
Component进行自动配置,这种时候就要使用Bean注解。被Bean注解的方法返回值是一个对象,将会实例化,配置和初始化一个新对象并返回,这个对象由Spring的IoC容器管理。name属性用于给当前
Bean注解方法创建的对象指定一个名称,即bean的id。当使用注解配置方法时,如果方法有参数,Spring会去容器查找是否有可用bean对象,查找方式和Autowired一样。BeanFactory、FactoryBean和ApplicationContext的区别?
BeanFactory(”低级容器)
BeanFactory是一个Bean工厂,使用简单工厂模式,是SpringIOC容器的顶级接口,可以理解为含有Bean集合的工厂,作用是管理Bean,包括实例化、定位、配置对象及建立这些对象间的依赖。BeanFactory实例化后并不会自动实例化Bean,只有当Bean被使用时才实例化与装配依赖关系,属于延迟加载,适合多例模式。
FactoryBean
FactoryBean是一个工厂Bean,使用了工厂方法模式,作用是生产其他Bean实例,可以通过实现该接口,提供一个工厂方法来自定义实例化Bean的逻辑,FactoryBean接口有BeanFactory中的配置实现,,这些对象本身就是用于创建对象的工厂,如果一个Bean实现了这个接口,那么它就是创建对象的工厂Bean,而不是Bean实例本身。
ApplicationContext(”高级容器“)
ApplicationContext是BeanFactory的子接口,扩展了BeanFactory的功能,提供了支持国际化的文本消息,统一的资源文件读取方式,事件传播以及应用层的特别配置等。该接口定义了一个refresh方法,用于刷新整个容器,即重新加载/刷新所有的bean。容器会在初始化时对配置的Bean进行预实例化,Bean的依赖注入在容器初始化时就已经完成,属于立即加载,适合单例模式,一般推荐使用。
实现类:
ClassPathApplicationContext:只能读取路径下的配置文件(推荐使用)。FileSystemXmlApplicationContext:可以读取任意硬盘路径的配置文件,你可以点击根目录下的文件的属性查看他在本地硬盘的位置。AnnotationConfigApplicationContext:读取注解配置类。SpringAOP
AOP是什么?
AOP(Aspect-OrientedProgramming)即面向切面编程,简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强,减少了系统的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
常见的场景包括:权限认证、自动缓存、错误处理、日志、调试和事务。
动态代理
JDK
Spring根据类是否实现接口来判断动态代理的方式,如果实现接口会使用JDK动态代理,核心是InvocationHandler接口和Proxy类,JDK动态代理主要通过重组字节码实现,首先获得被代理对象的引用和所有接口,生成新的类必须实现代理类的所有接口,动态生成Java代码后编译生成新的.class文件并重新加载到JVM运行。JDK代理直接写Class字节码。
InvocationHandler的invoke(Objectproxy,Methodmethod,Object[]args):proxy是最终生成的代理实例;method是被代理目标实例的某个具体方法;args是被代理目标实例某个方法的具体入口,在方法反射调用时使用。
CGLIB
如果代理类没有实现InvocationHandler接口,那么SpringAOP会选择使用CGLIB来动态代理目标类。CGLIB(CodeGenerationLibrary),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中的特定方法并添加增强代码。采用ASM框架写字节码,生成代理类的效率低,但是CGLIB调用方法的效率高,因为JDK使用反射调用方法。CGLIB使用FastClass机制为代理类和被代理类各生成一个子类,这个类会为代理类或者被代理类的方法生成一个index,这个index可以作为参数直接定位要调用的方法。如果没有实现接口会使用CGLIB是运行时动态生成某个类的子类,如果某个类被标记为final,不能使用CGLIB。AOP相关术语
Aspect:切面是通知和切点的结合。通知和切点共同定义了切点的全部内容,在SpringAOP中,切面可以使用通用类(基于模式的风格)或者在普通类中以
AspectJ注解来实现。Joinpoint:指连接点,在SpringAOP中,一个连接点总是代表一个方法的执行。应用可能有数以千计应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
Advice:通知,指切面对于某个连接点所产生的动作,包括:
前置通知(Before)后置通知(After)返回后通知(AfterReturning)异常通知(AfterThrowing)环绕通知(Around)Pointcut:切入点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
Proxy:代理,SpringAOP中有JDK动态代理和CGLib代理,目标对象实现了接口时采用JDK动态代理,反之采用CGLib代理。
Target:代理的目标对象,指一个或多切面所通知的对象。
Weaving:织入,指把增强应用到目标对象来创建代理对象的过程。
编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。AOP的过程
SpringAOP由BeanPostProcessor后置处理器开始,这个后置处理器是一个监听器,可以监听容器触发的Bean生命周期事件,向容器注册后置处理器以后,容器中管理的Bean就具备了接收IOC容器回调事件的能力。BeanPostProcessor的调用发生在SpringIoC容器完成Bean实例对象的创建和属性的依赖注入后,为Bean对象添加后置处理器的入口是initializeBean方法。
AOP工作中心在于如何将增强编织到目标对象的连接点上,这里包含两个动作
如何通过pointcut和advice定位到特定的joinpoint上如何在advice中编写代码
扩展问题
AOP有哪些实现方式
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表是AspectJ,动态代理则以SpringAOP为代表。
SpringAOP和AspectJAOP有什么区别
AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强的AOP对象。SpringAOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时,在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理。Spring注解
基于Spring注解配置
基于Java的配置,允许在少量Java注解的帮助下,进行大部分Spring的配置而非通过xml文件。
Configuration:用于指定当前类是一个Spring配置类,标记类可以当做一个Bean的定义,被IOC容器使用。value属性用于指定配置类的字节码文件。Bean:表示此方法要返回一个对象,作为一个Bean注册到Spring应用上下文中。ComponentScan:用于指定Spring在初始化容器时要扫描的包,basePaceages属性用于指定要扫描的包。PropertySource:用于加载.properties文件中的配置。value属性用于指定文件位置,如果是在类路径下需要加上classpath。Import用于导入其他配置类,在引入其他配置类时可以不用再写Configuration注解。对象注解Component:将java标记为Bean,它是任何Spring管理组件的通用构造型,Spring的组件扫描机制可以将其拾取并拉入容器中。Controller:这将一个类标记为SpringWebMVC控制器。标有它的Bean会自动导入到IOC容器中。Service:此注解是组件注解的特化。它不会对Component注解提供任何其他行为。您可以在服务层类中使用Service而不是Component,因为它以更好的方式指定了意图。Repository:这个注解是具有类似用途和功能的Component注解的特化。它为DAO提供了额外的好处。它将DAO导入IoC容器,并使未经检查的异常有资格转换为SpringDataAccessException。依赖注入(DI)的相