Spring 概述
Spring 是一个开源的轻量级Java SE(Java 标准版本)/Java EE(Java企业版本) 开发应用框架,集成了很多设计模式(工厂设计模式、代理设计模式等),是一个轻量级框架(低污染)。降低了类和类之间的耦合,具有解耦和的作用,可以对对象、框架进行管理,其目的是用于简化企业级应用程序开发。
在传统应用程序开发中,一个完整的应用是由一组相互协作的对象组成。所以开发一个应用除了要开发业务逻辑之外,最多的是关注如何使这些对象协作来完成所需功能,而且要低耦合、高内聚。业务逻辑开发是不可避免的,这就需要有个框架出来帮我们来创建对象及管理这些对象之间的依赖关系。
可能有人说了,比如“抽象工厂、工厂方法设计模式”不也可以帮我们创建对象,“生成器模式”帮我们处理对象间的依赖关系,不也能完成这些功能吗?可是这些又需要我们创建另一些工厂类、生成器类,又要额外的管理这些类,增加了我们的负担,如果能有种通过配置方式来创建对象,管理对象之间依赖关系,我们不需要通过工厂和生成器来创建及管理对象之间的依赖关系,这样我们是不是减少了许多工作,加速了开发,能节省出很多时间来干其他事。
Spring 框架主要就是来完成这个功能,Spring框架除了帮我们管理对象及其依赖关系,还提供像通用日志记录、性能统计、安全控制、异常处理等面向切面的能力,还能管理最头疼的数据库事务,本身提供了一套简单的 JDBC 访问实现,提供与第三方数据访问框架集成(如 Hibernate、JPA),与各种 Java EE 技术整合(如 Java Mail、任务调度等等),提供一套自己的 web 层框架 Spring MVC、而且还能非常简单的与第三方 web 框架集成。
从这里我们可以认为 Spring 是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力,从而使我们可以更自由的选择到底使用什么技术进行开发。而且不管是 JAVA SE(C/S 架构)应用程序还是 JAVA EE(B/S架构)应用程序都可以使用这个平台进行开发。
Spring核心功能模块
- Core Container(核心容器):Beans,Core,Context,Expression Language
- Spring的另一个核心:AOP,Aspects,Instrumentation
- Date Access/Integration(Spring对DAO层的支持):JDBC,ORM,OXM,JMS,Transactions——spring对jdbc做了封装;事务处理
- Web(Spring对MVC的支持):Web,Servlet,Portlet,Struts
- Test(Spring对测试的支持)
Spring 为简化开发采取的策略
Spring 是为解决企业级应用开发的复杂性而设计的,她可以做很多事。但归根到底支撑Spring的仅仅是少许的基本理念,而所有地这些的基本理念都能可以追溯到一个最根本的使命: 简化开发。这是一个郑重的承诺,其实许多框架都声称在某些方面做了简化。而Spring则立志于全方面的简化Java开发。对此,她主要采取了 4 个关键策略:
- 基于 POJO 的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口松耦合;
- 基于切面进行声明式编程;
- 通过切面和模板减少样板式代码;
而他主要是通过:面向 Bean、依赖注入以及面向切面这三种方式来达成的。
一、面向 Bean
Spring 是面向 Bean 的编程(Bean Oriented Programming, BOP),Bean在Spring中才是真正的主角。Bean在Spring中作用就像Object对OOP的意义一样,Spring 中没有Bean也就没有Spring存在的意义。Spring提供了 IOC 容器通过配置文件或者注解的方式来管理对象之间的依赖关系。
控制反转(Inverse of Control,IOC)——其中最常见的方式叫做依赖注入(Dependency Injection,DI),还有一种方式叫依赖查找(Dependency Lookup,DL),她在 C++、Java、PHP 以及 .net 中都有运用。在最早的 Spring 中是包含有依赖注入方法和依赖查询的,但因为依赖查询使用频率过低,不久就被 Spring 移除了,所以在Spring 中控制反转也被称作依赖注入。
她的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器 (在 Spring框架中是IOC容器)负责将这些联系在一起。 在典型的IOC场景中,容器创建了所有对象,并设置必要的属性将它们连接在一起,决定什么时间调用方法。
在Spring中,通过IOC可以将实现类、参数信息等配置在其对应的配置文件中,那么当需要更改实现类或参数信息时,只需要修改配置文件即可,这种方法降低了类与类之间的耦合。 我们还可以对某对象所需要的其它对象进行注入,这种注入都是在配置文件中做的,Spring的IOC的实现原理利用的就是Java的反射机制,Spring还充当了工厂的角色,我们不需要自己建立工厂类。Spring的工厂类会帮我们完成配置文件的读取、利用反射机制注入对象等工作,我们可以通过bean的名称获取对应的对象。
对于属性赋值的控制权,从原来的在类中完成赋值,转移到在Spring配置文件中完成赋值。实现原理是通过工厂设计模式以及代理模式机制来实现,有利于解耦和。
二、依赖注入
类中所依赖的成员变量的值,通过Spring的注入机制完成属性的赋值。Spring设计的核心是org.springframework.beans包(架构核心是org.springframework.core包),它的设计目标是与JavaBean组件一起使用。这个包通常不是由用户直接使用,而是由服务器将其用作其他多数功能的底层中介。下一个最高级抽象是BeanFactory接口,它是工厂设计模式的实现,允许通过名称创建和检索对象。BeanFactory 也可以管理对象之间的关系。
BeanFactory 支持两个对象模型
- 单例:模型提供了具有特定名称的对象的共享实例,可以在查询时对其进行检索。Singleton 是默认的也是最常用的对象模型。对于无状态服务对象很理想。
- 原型:模型确保每次检索都会创建单独的对象。在每个用户都需要自己的对象时,原型模型最适合。bean 工厂的概念是 Spring 作为 IOC 容器的基础。IOC 则将处理事情的责任从应用程序代码转移到框架。
Spring注入方式
set注入:通过set方法实现给属性赋值(设值注入)
- 基本数据类型、String(通过value属性或者value子标签完成)
- 数组/集合(list、set、map):数组/list(通过list标签)、set(通过set标签)、map(通过map标签以及entry标签)
- 自定义类型(类类型)(通过ref子标签bean属性或者ref属性完成)
- Properties类型
<bean id=”user2” class=”com.entity.User”>
<property name=”properties”>
<props>
<prop key=”a”>aaaaaaa</prop>
<prop key=”b”>bbbbbb</prop>
</props>
</property>
</bean>
- 给String类型的变量赋值空字符串
- 给String类型的属性赋null值(通过null子标签完成)
- 给日期类型的属性赋值
<bean id=”date” class=”java.util.Date”>
<property name=”year” value=”1995”></property>
<property name=”month” value=”10”></property>
<property name=”date” value=”01”></property>
</bean>
<bean id=”user” class=”xxx.xxx.spring.entity.User”>
<property name=”birthday” ref=”date”></property>
</bean>
自动装配:通过set注入实现,在bean标签中写autowire属性,通过autowire实现
- byName:类中的属性名跟spring工厂创建的对象名字(bean标签的id)一样;
- byType:spring工厂中所创建的对象,跟属性的类型一致,实现自动装配;如果有多个类型一致的对象,报异常。
构造注入:构造注入就是通过一个类的有参构造方法传参对类的属性进行赋值,所以说想要通过构造注入赋值就必须在类中有对应的构造方法,否则就无法通过类的构造方法进行参数传递。
- 构造注入的标签为,在标签中写index属性,指定参数的索引位置,索引从0开始。
<constructor-arg index=”0” value=”xxx”>
<constructor-arg index="2" ref="dept"/>
- 在标签中写type属性,参数的类型为基本类型和字符串用value,对象类型用ref。一个类里可以有好几个相同基本类型的变量,很容易就混淆值传给哪一个参数了,所以不推荐使用这种方法。
<constructor-arg type="Java.lang.String" value="xxx"
<constructor-arg type="com.entity.dept" ref="dept"/>
- 直接传值。直接给参数赋值,这种方法也是根据顺序排的,所以一旦调换位置的话,就会出现bug。
<constructor-arg value="xxx"/>
<constructor-arg ref="dept"/>
<constructor-arg value="男"/>
- 根据参数的名字传值。根据name来传值的,只要名字对了,这个值就可以获取到。
<constructor-arg name="name value="孙卓然" />
<constructor-arg name="dept" ref="dept"/>
<constructor-arg name="sex" value="男"/>
设值注入与构造注入的区别
- 设值注入是通过类中的setter方法对属性进行赋值,而构造注入通过类的构造方法对属性进行赋值。
- 设值注入灵活性好,但setter方法数量较多;构造注入灵活性差,仅靠重载限制太多。
三、面向切面
面向切面编程(Aspect Oriented Programing,AOP)是一种编程思想,可以说是面向对象编程(Object Oriented Programming,OOP)的补充和完善。OOP 引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能,日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系,对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此,这种散布在各处的无关的代码被称之为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为“横切”的技术,封装的对象内部进行剖解,并将哪些影响了多个类的公共行为封装到一个可重用模块,并将其命名为“Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑,封装起来便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。它允许程序员对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化。AOP的核心构造是切面,它将那些影响多个类的行为封装到可重用的模块中。
AOP和IOC是补充性的技术,它们都运用模块化方式解决企业应用程序开发中的复杂问题。在典型的面向对象开发方式中,可能要将日志记录语句放在所有方法和Java类中才能实现日志功能。在AOP方式中,可以反过来将日志服务模块化,并以声明的方式将它们应用到需要日志的组件上。当然,优势就是 Java 类不需要知道日志服务的存在,也不需要考虑相关的代码。所以,用Spring AOP编写的应用程序代码是松散耦合的。
Spring AOP的实现原理
通过动态字节码技术,动态将目标类和额外功能组合生成目标类的代理类,形成代理对象,也就是动态代理;Spring工厂把创建出来对象交给BeanPostProcessor处理,处理之后的对象名字不变,再交给Spring工厂。SpringAOP底层采用JDK动态代理和cglib动态代理两种方式实现。
详见下方“Spring中用到的设计模式–代理模式”。
AOP应用
AOP 的功能完全集成到了 Spring 事务管理、日志和其他各种特性的上下文中。
- Authentication 权限认证
- Logging 日志监听
- Transctions Manager 事务代理(声明式事务,哪个方法需要加事务,哪个方法不需要加事务)
- Lazy Loading 懒加载
- Context Process 上下文处理
- Error Handler 错误跟踪(异常捕获机制)
- Cache 缓存
Spring 思想 | 应用场景(特点) | 一句话归纳 |
---|---|---|
AOP | 找出多个类中有一定规律的代码,开发时拆开,运行时再合并。面向切面编程,即面向规则编程。 | 解耦,专人做专事 |
OOP | 归纳总结生活中一切事物。 | 封装、继承、多态 |
BOP | 面向 Bean(普通的 java 类)设计程序。 | 一切从 Bean 开始 |
IOC | 将 new 对象的动作交给 Spring 管理,并由 Spring 保存已创建的对象。 | 转交控制权 |
DI/DL | 依赖注入、依赖查找,Spring不仅保存自己创建的对象,而且保存对象与对象之间的关系。注入即赋值,主要三种方式构造方法、set 方法、直接赋值。 | 先理清关系再赋值 |
Spring中用到的设计模式
解决一些具有代表性的问题,提升代码的可读性、可扩展性、维护成本、解决复杂的业务问题。
一、代理模式
代理设计模式是将核心代码与公共功能代码分别写在不同的类中减少代码冗余。或者在原有核心类(核心类就是目标类,也就是原始类、被代理类,目标类负责的是核心功能的实现,不去关心其他功能的实现,不负责额外功能)的基础上增加额外的功能,形成一个代理类,使被代理的类变得更强大。公共部分又被叫做公共功能、增强处理、额外功能。通俗的说:就是分为执行者与被代理人;对于被代理人来说,这件事是必做但又不想自己做的事,所以交给代理执行者;而代理人需要获取到被代理人的个人资料。
代理类作用
就是将目标类和需要增加的额外功能放在一起形成新的代理类,代理类的功能较目标类更加强大,使得程序员只需要关心核心代码,而不需要关注其他的公共功能,同时减少了代码冗余。可以符合开闭原则,在目标类的基础上添加新的功能而不修改原有的目标类代码。
Spring动态代理的开发步骤
- 写目标类,即被代理类;
- 写公共功能;根据要放在目标类前后的位置,实现Spring已经设定好的接口来实现不同的增强方式:
- MethodBeforeAdvice(前置增强,实现before()方法)
- AfterReturingAdvice(后置增强,实现afterReturning()方法)
- ThrowsAdvice(异常增强,实现afterThrowing()方法)
- MethodInterceptor(环绕增强,实现invoke()方法)
- 在Spring配置文件中配置公共功能对象。
例: - 编织(织入);通过AOP标签,定义切入点和额外功能,把目标对象和增强处理编织在一起。
<aop:config>
<aop:pointcut expression="execution(* com.demo.service.UserService.reg())" id="userPointCut"/>
<aop:advisor advice-ref="log4jAdvice" pointcut-ref="userPointCut"/>
</aop:config>
Spring AOP的详细配置
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
<aop:config>
<aop:pointcut expression=”execution(返回值 全限定名.方法名())
/args(参数类型)
/@annotation(自定义注解全限定名)” id=”xxxRef”
/within(包的全限定名)>
<aop:advisor advice-ref=”xxxAdvice” pointcut-ref=”xxxRef”>
</aop:config>
注:
- args():用来匹配方法中的参数作为切入点,基本类型参数直接写参数类型,对象类型写全限定名,多个参数用逗号隔开。
- within():用来匹配包作为切入点,包中的所有内容都作为切入点。
- @annotation():
- 自己定义一个注解,通过@interface定义注解。
- 在需要做增强处理的方法上使用自己定义的注解:@注解名。
- 在Spring配置文件中的aop:pointcut标签上,通过@annotation(自定义注解的全限定名)加载注解。
JDK动态代理原理(基于接口)
在程序运行的过程中,根据被代理的接口来动态生成代理类的class文件,Jdk动态生成的字节码数据,通过类加载器加载处理,形成代理类,加载运行的过程。
- 拿到被代理对象的引用,然后获取它的接口
- JDK代理重新生成一个类,同时实现代理对象所实现的接口
- 重新动态生成一个class字节码
- 进行编译
cglib动态代理(基于父类)
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑;也就是说,对于没有接口的类,要实现动态代理,就需要用cglib动态代理。cglib.jar (全称 Code Generation Library 代码生成库);asm.jar(全称 assembly,装配)
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之使用JDK方式要更为合适一些。同时,由于CGLib是采用动态创建子类的方法,对于final方法,无法进行代理。Cglib动态代理是基于继承的。
二、工厂模式
隐藏复杂的逻辑处理过程,只关心执行结果。具体实现分为简单工厂、工厂方法、抽象工厂
Spring中的工厂模式:Bean、BeanFactory(生成Bean)、单例的Bean、被代理过的Bean、最原始的Bean(原型)、List类型的Bean、作用域不同的Bean。
工厂设计模式开发步骤
由工厂创建对象——>配置文件+工厂类+反射+面向接口编程——>目的:解耦合
- 写配置文件bean.properties,key(接口)=value(实现类全限定名)
- 写工厂类
//读配置文件。以流的方式读取配置文件,并且加载到Properties对象中
private static Properties properties = new Properties();
InputStream stream = BeanFactory.class.getResourceAsStream(“xxx.properties”);
properties.load(stream);
//根据key得到对应的value
String value = properties.getProperty(key);
//通过获取到的value进行反射创建对象
Class clazz = Class.forName(value);
Object object = clazz.newInstance();
三、单例模式
保证从系统启动到系统终止,全过程只会产生一个实例。当我们在应用中遇到功能性冲突的时候,需要使用单例模式。可解决并发访问时线程安全问题。
- 饿汉式:在实例使用之前,不论是否调用,都先将对象new出来,避免线程安全问题。
- 懒汉式:默认在类加载时不进行实例化,在需要用到这个实例的时候才进行实例化。延时加载。
- 注册登记式:每使用一次,都往一个固定的容器中注册,并且将已使用过的对象进行缓存,下次去取对象的时候就直接从缓存中取值,以保证每次取到的都是同一个对象。IOC中的单例模式,就是典型的注册登记式单例。
- 序列化与反序列化保证单例:重写readResolve()。
场景:配置文件、监控程序、IOC容器、日历
四、委派模式
在常用的23种设计模式中其实面没有委派模式(delegate)的影子,但是在Spring中委派模式确实用的比较多的一种模式,Spring MVC框架中的DispatcherServlet其实就用到了委派模式,要和代理模式区分开来。相当于是静态代理一种非常特殊的情况,全权代理。以Delegate结尾、Dispatcher结尾的类名。
特点:
- 类似于中介的功能(委托机制);
- 持有被委托人的引用;
- 不关心过程,只关心结果。
一个比较实际的例子,就是老板将任务委派给项目经理,项目经理将任务细化,根据每个人擅长的某一方面将细化后的任务分给指定的员工,权衡的方式(策略)有多种,而这个任务项目经理不想干,就将其代理给了各个员工,从这个层面来看委派模式就是策略模式和代理模式的组合。
//Boss,老板指派任务给项目经理
public class Boss {
public static void main(String[] args) {
new Leader().dispatch("加密");
new Leader().dispatch("销售");
}
}
//项目经理(委派者)
public class Leader {
private Map<String ,ITarget> targets = new HashMap<String ,ITarget>();
public Leader(){
targets.put("加密",new TargetA());
targets.put("销售",new TargetB());
}
public void dispatch(String command){
targets.get(command).doing(command);
}
}
//普通职员接口 ITarget
public interface ITarget {
public void doing(String command);
}
//两个普通职员类
public class TargetA implements ITarget{
public void doing(String command) {
System.out.println("开始进行加密算法的实现");
}
}
public class TargetB implements ITarget{
public void doing(String command) {
System.out.println("开始开发销售代码");
}
}
主要目的就是对外隐藏具体实现逻辑,仅将委派者角色暴露给外部, 易于扩展,简化调用。
在IOC 容器中,有一个 Register 的东西(为了告诉我们的容器,在这个类被初始化的过程中,需要做很多
不同的逻辑处理,需要实现多个任务执行者,分别实现各自的功能)
保证结果的多样性,对于用户来说是只有一种方法。
五、策略模式
过程不同,但结果一样(我行我素,达到目的就行)。
应用场景:根据用户的需求处理数据时需要对算法做出选择,旅行线路的选择,出行方式的选择。
六、原型模式
数据内容完全一样,但实例不同(拔一根猴毛,吹出千万个,即复制、克隆)。Spring中的对象原型,主要是为了配置信息能够被重复使用,而且互不干扰。
技术手段:实现cloneable重写clone方法;字节码操作来实现;通过反射机制来实现(Spring中常用)
七、模板模式
通常又叫做模板方法模式(Template Method)。执行流程固定,但中间有些步骤有细微差别(流程标准化,原料自己加)。
应用场景:JdbcTemplage、工作流
对比策略模式:策略模式只有选择权(由用户自己选择已有的固定算法),而模板模式侧重的不是选择,没有选择必须要做,但可以参与某一部分内容自定义。