从Spring看Web项目开发
之前简单介绍过Spring框架,本文换个角度重新诠释Spring。使用Java语言开发的项目,几乎都绕不过Spring,那么Spring到底是啥,为何被如此广泛的应用,下面从以下两个问题出发来剖析Spring,本文所有讨论基于Spring 4。
-
Spring是啥
Spring 是一个分层的 JavaSE/EE一站式(full-stack)轻量级开源框架。
引入问题:
1.1 何为分层,为什么要分层?
分层即将一体化的软件系统按不同的功能特性拆分成多个独立的功能模块,分层的目的在于解耦,解耦的目的在于提升开发效率降低维护成本;层是拆分的依据。在Web项目中依据功能的不同拆分成Controller(视图层),Service(服务层),Dao(Data Access Object数据访问层)。
1.2 何为一站式?
Spring对不同的软件层提供了针对性的解决方案:
Dao:Spring极易整合MyBatis|Hibernate等持久层框架,以及dbcp|C3p0等数据库连接池;
Service:Spring Core通过IOC控制反转、DI依赖注入获取了对象的管理权,降低了用户编写程序的复杂度,同时其AOP面向切面的编程使服务功能的升级变得更加友好便捷。
Controller:可以使用SpringMvc或者通过Spring整合Struts2简化开发。
1.3 轻量级从何体现?
Spring的轻量级是相对于EJB而言,Spring针对不同的场景提供了不同的服务,用户在使用时可以选择性的集成需要的功能模块,开发简单;而EJB规范功能复杂,使用难度高。
1.4 开源=免费?
国内的软件生态中,得到最广泛的技术基本都是开源的,其中免费是很重要的因素,如Spring/Linux/MySQL等等。当然也正是由于这些项目遵从了开源协议,使更多的开发人员投入相关的优化升级工作,使得项目不断向前发展。
2. 为何Spring被如此广泛的应用
随着互联网及移动互联网的飞速发展,接入的用户数据暴增,业务复杂度亦不断提高,Web项目的开发维护难度不断加大。基于此,大型项目进行微服务拆分,软件服务进行分层开发是必然选择。由于Spring的上述优点,以及基于Spring良好的扩展性而形成的完善生态,Spring被广泛应用于Java项目之中。
2.1 Spring到底解决了哪些问题?
a. IOC控制反转----替用户管理对象
Java类中的非静态方法及属性只能通过实例对象进行调用,在开发过程中频繁创建对象会比较繁琐,同时也会增加JVM的负担。Spring通过Java的反射机制,利用工厂模式,配合配置文件(Spring2.5版本以后支持注解Annotation)将类实例化的过程放在程序启动时完成,并将创建的实例放在Spring容器(ApplicationContext)中,后续程序进行开发时只需要通过注解注入到相应的代码中即可。
所以Java反射是什么?
反射是框架设计的灵魂。Java的反射机制是在运行状态中,对于任何一个类,都能知道这个类的所有属性和方法;对于任何一个对象,都能调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象方法的功能称为Java语言的反射机制。
反射的实现原理
Java中所有的类都继承了Object,所有.java(Programmer.java)文件都会被编译成.class(Programmer.class)文件,JVM在加载.class文件时会为每一个类(Programmer)创建一个唯一的Class类的对象,该对象用于描述.class文件中类的相关信息。通过调用父类Object中的方法getClass(programmer.getClass)可以获取到内存中代表该类唯一元信息的Class对象。这些元信息描述了该类的所有方法和属性,获取到这些属性即可实现对类的操作。
Java的反射机制是一个打破封装的特性,该特性使开发Java框架成为了可能。
xml配置文件的进阶----Annotation注解(Java1.5版本引入)
xml可扩展标记语言由于其良好的结构被选中,成为Java项目的配置文件,但是随着项目的不断变大,xml需要配置的内容不断增加,终于爱偷懒的程序员决定开发一个更易用的配置方式,基于注解的配置方式。
xml的配置文件的解析原理与注解方式大为不同:xml配置文件的解析是通过文件IO操作,在指定路径读取配置文件,根据标签获取对应配置信息。注解的实现原理是通过反射机制获取被检查的方法或类上的注解信息,再根据注解的元素值进行相关处理。
JDK自带的注解(Java目前只内置了三种标准注解:@Override、@Deprecated、@SuppressWarnings,以及四种元注解:@Target、@Retention、@Documented、@Inherited)。我们接触最多和作用最大的一类注解是第三方的注解,如Spring中的@Sevice,Junit中的@Test等。
b. DI依赖注入----实现对象的创建过程(注入属性)
依赖注入,指容器复制创建和维护对象之间的依赖关系,在 spring 创建对象的过程中,对象所依赖的属性通过配置注入对象中。
Bean----Spring容器管理的Java对象
不是所有Java对象都称之为Bean,只有被Spring容器管理的Java对象称之为Bean。
Spring应用通过容器(Spring上下文Application Context)来管理Bean,负责Bean的创建和装配(常说的依赖注入DI)。
Spring常见的容器有两种类型,一种是Bean工厂,另一种是应用上下文,后者是在前者的基础上构建,比前者多了更多企业级的应用服务。所以后者也是用得最多的一种容器,我们在Spring应用中也经常能看到*application*.xml的配置文件,在SpringBoot应用中常看到@Bean注解。
Spring容器负责管理Bean的创建和装配,开发人员可以定义需要创建的Bean,配置Bean之间的依赖关系。
c. AOP面向切面的编程----在不需要侵入式修改源码的情况下实现方法功能的增强,常用于日志记录,性能统计,安全控制,事务处理,异常处理等等。
AOP 分为静态 AOP 和动态 AOP。静态 AOP 是指 AspectJ 实现的 AOP,他是将切面代码直接编译到 Java 类文件中。动态 AOP 是指将切面代码进行动态织入实现的 AOP。Spring 的 AOP 为动态 AOP,实现的技术为: JDK 提供的动态代理技术 和 CGLIB(动态字节码增强技术)。
问题:代理?静态代理?动态代理?
什么是代理?
Java中的代理类具有与原始Java相同的服务接口,但是在类中持有原始类的对象,利用原有的对象调用原Java类的方法实现功能;代理的好处是在调用原方法时可以在之前或之后进行包装增加额外的功能。如卖机票的代理去哪儿网,代理了各大航空公司的机票,为用户提供搜索比价等等服务,但是用户在去哪儿购票时,实际调用的仍然是各大航空公司自己的票务系统。
静态代理AspectJ
AspectJ 是静态代理的增强,所谓的静态代理就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强。
AspectJ 是 Java 语言的一个 AOP 实现,其主要包括两个部分:
-
第一个部分定义了如何表达、定义 AOP 编程中的语法规范,通过这套语言规范,我们可以方便地用 AOP 来解决 Java 语言中存在的交叉关注点问题;
-
另一个部分是工具部分,包括编译器、调试工具等。
AspectJ 是最早、功能比较强大的 AOP 实现之一,对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现,也借鉴或采纳了 AspectJ 中很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。
示例
一个普通的 Hello 类:
public class Hello {
public void sayHello() {
System.out.println("hello");
}
public static void main(String[] args) {
Hello h = new Hello();
h.sayHello();
}
}
使用 AspectJ 编写一个Aspect,命名为 TxAspect.aj:
public aspect TxAspect {
void around():call(void Hello.sayHello()){
System.out.println("开始事务 ...");
proceed();
System.out.println("事务结束 ...");
}
}
上面的 TxAspect 根本不是一个 Java 类,所以 aspect 也不是 Java 支持的关键字,它只是 AspectJ 才能识别的关键字。使用 AspectJ 的编译器编译:
ajc -d . Hello.java TxAspect.aj
查看一下编译后的 Hello.class:
public class Hello {
public Hello() {
}
public void sayHello() {
System.out.println("hello");
}
public static void main(String[] args) {
Hello h = new Hello();
sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null);
}
}
可以看到,这个类比原来的 Hello.java 多了一些代码,这就是 AspectJ 的静态代理,它会在编译阶段将Aspect 织入 Java 字节码中, 运行的时候就是经过增强之后的 AOP 对象。
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 上面静态代理的例子中,代理类在程序运行之前就已经编译完成。然而动态代理,代理类是在运行时动态生成的。
Java动态代理有两种----JDK动态代理和CGLIB动态代理
JDK的动态代理类java.lang.reflect.Proxy中newProxyInstance方法可以创建动态代理对象。
第一个参数:目标类的类加载器对象
第二个参数:目标类的实现接口的 Class[]
第三个参数:InvocationHandler 它是一个接口,它的作用是是代理实例的调用处理程序 实现的接口,接口中定义了一个方法
通过上述invoke方法调用原始类的方法实现功能。
CGLIB(Code Generation Library)是一个开源项目是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java接口。CGLIB 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类。
JDK动态代理只能针对接口操作,CGLIB动态代理还可以针对非接口操作。Spring动态代理针对接口时默认使用JDK。
Spring整合AspectJ实现AOP
在 spring2.0 以后它支持 jdk1.5 注解,而整合 aspectj 后可以使用 aspectj 语法,可以简化开发。
AspectJ 框架它定义的通知类型有6种:
-
前置通知 Before 相当于 BeforeAdvice
-
后置通知 AfterReturning 相当于 AfterReturningAdvice
-
环绕通知 Around 相当于 MethodInterceptor
-
抛出通知 AfterThrowing 相当于 ThrowAdvice
-
引介通知 DeclareParents 相当于 IntroductionInterceptor
-
最终通知 After 不管是否异常,该通知都会执行
基于注解方案的AOP开发是当前使用最广泛的开发方式,具体实现方式略过。
2.2 Spring强大的扩展能力----使用户可以快速集成各种框架实现特定功能
a. Spring整合持久层框架MyBatis|Hibernate;
b. Spring整合数据库连接池Dbutils|C3P0;
c. Spring整合视图层开发框架structs2;
d. Spring整合内存数据库Redis;
e. Spring整合搜索服务Solr;
......