个人说明:
下面有一部分引用该链接:https://www.tutorialspoint.com/spring/spring_architecture.htm 另外一部分加上我个人的使用经验和体会
之所以参考,第一,相关概念知识回顾;第二,系统化和条理化。
我想做到的是,给初学者一个全局的概况和相关应用的说明,给中级开发者们,一个知识回顾和灵感的体会。
至于高级开发者,即架构师或者专家,我就不再这班门弄斧了,不过也十分希望他们的意见和看法,以此来灵感碰撞产生知识火花。
该篇博文,主要是回顾我的使用Spring的经历历程和一些深刻体会,另外我希望加上一些源码方面的理解。
对于初学者而言,知道怎么用就行了,但对于非初学者而言,不仅仅要知道怎么用,还要知道如何用的更好(这就需要你知道它的底层原理和实现机制)。
一、简述
Spring是企业Java最流行的应用程序开发框架。全球数百万开发人员使用Spring Framework创建高性能,易于测试和可重用的代码。
Spring框架是一个开源Java平台。它最初由Rod Johnson编写,并于2003年6月首次在Apache 2.0许可下发布。
弹簧在尺寸和透明度方面很轻巧。Spring框架的基本版本大约为2MB。
Spring Framework的核心功能可用于开发任何Java应用程序,但有一些扩展用于在Java EE平台之上构建Web应用程序。Spring框架的目标是通过启用基于POJO的编程模型,使J2EE开发更易于使用并促进良好的编程实践。
二、使用Spring的好处
(1)Spring使开发人员能够使用POJO开发企业级应用程序。仅使用POJO的好处是您不需要EJB容器产品(如应用程序服务器),但您可以选择仅使用强大的servlet容器(如Tomcat)或某些商业产品。
对于初学者而言或许会疑惑,什么是POJO?
简单的说,POJO相当于JavaBean,又称实体类。
这里还要补充说下,应用服务器和web服务器的区别?常见的应用服务器和web服务器有哪些?
常见的应用服务器,比如我比较常用的就是tomcat,当然在我的朋友中,他们有些人使用Jboss或者resin或weblogic等等。
常见的web服务器:比如apache、nginx等
相同点(共性):遵守HTTP协议
不同点:(应用服务器即可处理静态请求(图片、js、css、html等),又可以处理动态请求(可嵌入服务端脚本,例如:jsp,freemarker,volocity等),而web服务器只能处理静态请求,不能处理静态请求,当然了,web服务器处理静态资源的效率自然比应用服务器处理静态资源的效率要高的多。
(2)Spring采用模块化方式组织。即使包和类的数量很大,你也只需要担心你需要的那些而忽略其余的。
(3)Spring并没有重新发明轮子,而是真正利用了一些现有技术,如几个ORM框架,日志框架,JEE,Quartz和JDK计时器以及其他视图技术。
常用的ORM框架:Spring的JDBC,MyBatis,Hibernate等;
常见的日志框架:log4j或slif4j等;
视图技术:JSP+Volocity+Freemarker等;
定时任务:相比JDK定时任务更通俗易懂的Quartz;
(4)测试用Spring编写的应用程序很简单,因为依赖于环境的代码被移动到这个框架中。此外,通过使用JavaBeanstyle POJO,使用依赖注入来注入测试数据变得更加容易。
例如Spring的Junit4或者Juni5
(5)Spring的Web框架是一个设计良好的Web MVC框架,它提供了一个很好的替代Web框架,如Struts或借着Spring名气流行+自身相比Struts2更轻量级的实现方式的SpringMVC。
(6)Spring提供了一个方便的API,用于将特定于技术的异常(例如,JDBC,Hibernate或JDO抛出)转换为一致的,未经检查的异常。
Spring提供的API非常易懂,Spring对于有一定Java基础的人而言,很容易上手
(7)轻量级IoC容器往往是轻量级的,尤其是与EJB容器相比时。这有利于在具有有限内存和CPU资源的计算机上开发和部署应用程序。
说到轻量级,什么是轻量级?为什么要轻量级?轻量级给实际项目开发会带来哪些好处?
轻量级,简单说就是低耦合,代码相互依存度不强,遵守软件开中的编码"高内聚,低耦合"原则。
解释了什么是轻量级,为什么要轻量级就更好说明了,谁都不希望在需求变更的时候,改了这段代码,那段代码就出问题了,换言之,好不容易解决了一个bug,新的bug又出现了,代码耦合度越强,内聚性低,那么bug只会越改越多,最终除了拼命的加班加班改bug改bug,这也就是程序员为什么会猝死的一个重要原因之一以及为什么找不到对象,处对象也是需要时间的,天天对着电脑改bug,公司加班通宵达旦的改,回来还得改,这样导致个人提高时间少了(个人提高,主要在于平时看书,总结加上实践练习,再加上开源项目学习,借鉴前辈们的解决方案,虽说要有创新精神,但是,就拿中国的改革开放而言,现在已经是中国有特色的的社会主义道路,但是在此新中国(1978年之前)我们模仿苏联,走苏联的社会主义道路,满清时,我们学习西方,掀起了洋务运动,维新变法,辛亥革命等等,但最后都没有达到应有的效果,都相继失败了,这是为什么呢?因为我们忽略的自身的实际情况。有一句话叫做量力而行,还有一句话叫做循环渐进,还有最后一句话叫日积月累)。
最后轻量级会给实际项目开发带来哪些好处?简单的说,
第一,代码可扩展性强;
第二,代码可维护性好;
第三,bug机率少,加班就少,加班少有利于生命健康,而且还有利于代码功力的提升。
补充说明:我想谁都不愿意,天天改着因为自己平时不认真或者是编码时,不好好想怎样做会更好,更有利于扩展和维护,反而将其当为体力劳动,那么即便做了四年五年,那与一年两年又有何区别呢?人要有梦想,没有梦想和咸鱼就没有区别了。
(8)Spring提供了一致的事务管理接口,可以缩小到本地事务(例如,使用单个数据库)并扩展到全局事务(例如,使用JTA或者使用Spring的xml事务或注解事务等)。
事务,为什么需要事务?银行去取钱的例子可以很好说明这一点,这里不再赘述,网上一大把例子和博文说明。
三、依赖注入
Spring最常见的技术是Inversion of Control 的依赖注入(DI)风格。该控制反转(IOC)是一个笼统的概念,它可以在许多不同的方式来表达。依赖注入只是控制反转的一个具体例子。
在编写复杂的Java应用程序时,应用程序类应尽可能独立于其他Java类,以增加重用这些类的可能性,并在单元测试时独立于其他类测试它们。依赖注入有助于将这些类粘合在一起,同时保持它们的独立性。
究竟什么是依赖注入?我们分别看看这两个词。这里依赖部分转换为两个类之间的关联。例如,A类依赖于B类。现在,让我们看第二部分,注入。所有这些意味着,B类将由IoC注入A类。
依赖注入可以通过将参数传递给构造函数或使用setter方法进行后构建来实现。
以下面一段代码说明:
PostMapper.java
package cn.blog.mapper; import java.util.HashMap; import java.util.List; import java.util.Map; import cn.blog.entity.Post; /* * 文章接口 */ public interface PostMapper { /** * 根据文章ID显示文章 */ public Post selectByPostId(Integer postId); /** * 显示文章总数 */ public int selectPostCount(); /** *分页显示 */ public List<Post> selectPostWherePage(HashMap<String,Object> map); /** * 根据用户ID显示该用户的所有文章 */ public List<Post> selectUserIdWherePage(Map<String,Object> map); /** *显示该用户ID下所有文章总数 */ public int selectUserIdWherePageCount(Integer userId); }
PostService.java
package cn.blog.service; import org.apache.ibatis.annotations.Param; import cn.blog.entity.Post; import cn.blog.utils.PageBean; public interface PostService { /** * 根据文章ID显示文章 */ public Post selectByPostId(Integer postId); /** * 显示文章总数 */ public int selectPostCount(); /** *分页显示 */ public PageBean<Post> selectPostWherePage(int currentPage); /** * 根据用户ID显示该用户的所有文章 */ public PageBean<Post> selectUserIdWherePage(int currentPate,int userId); /** *显示该用户ID下所有文章总数 */ public int selectUserIdWherePageCount(Integer userId); }
PostServiceImpl.java
package cn.blog.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.ibatis.annotations.Param; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.blog.entity.Post; import cn.blog.mapper.PostMapper; import cn.blog.service.PostService; import cn.blog.utils.PageBean; @Service public class PostServiceImpl implements PostService{ @Autowired private PostMapper postMapper; @Override public Post selectByPostId(Integer postId) { // TODO Auto-generated method stub return postMapper.selectByPostId(postId); } @Override public int selectPostCount() { // TODO Auto-generated method stub return postMapper.selectPostCount(); } @Override public PageBean<Post> selectPostWherePage(int currentPage) { HashMap<String,Object> map = new HashMap<String,Object>(); PageBean<Post> pageBean = new PageBean<Post>(); //封装当前页数 pageBean.setCurrPage(currentPage); //每页显示的数据 int pageSize=5; pageBean.setPageSize(pageSize); //封装总记录数 int totalCount = postMapper.selectPostCount(); pageBean.setTotalCount(totalCount); //封装总页数 double tc = totalCount; Double num =Math.ceil(tc/pageSize);//向上取整 pageBean.setTotalPage(num.intValue()); map.put("start",(currentPage-1)*pageSize); map.put("size", pageSize); //封装每页显示的数据 List<Post> lists = postMapper.selectPostWherePage(map); pageBean.setLists(lists); return pageBean; } @Override public int selectUserIdWherePageCount(Integer userId) { // TODO Auto-generated method stub return postMapper.selectUserIdWherePageCount(userId); } @Override public PageBean<Post> selectUserIdWherePage(int currentPage,int userId) { PageBean<Post> pageBean = new PageBean<Post>(); Map<String,Object> paramMap = new HashMap<String,Object>(); //封装当前页数 pageBean.setCurrPage(currentPage); //每页显示的数据 int pageSize=5; pageBean.setPageSize(pageSize); //封装总记录数 int totalCount = postMapper.selectUserIdWherePageCount(userId); pageBean.setTotalCount(totalCount); //封装总页数 double tc = totalCount; Double num =Math.ceil(tc/pageSize);//向上取整 pageBean.setTotalPage(num.intValue()); paramMap.put("start", (currentPage-1)*pageSize); paramMap.put("size", pageBean.getPageSize()); paramMap.put("userId", userId); //封装每页显示的数据 List<Post> lists = postMapper.selectUserIdWherePage(paramMap); pageBean.setLists(lists); return pageBean; } }
对应的xml配置文件为:
<bean id="postMapper" class="com.blog.mapper.PostMapper"> </bean>
<bean id="postService" class="cn.blog.service.impl.PostServiceImpl">
<property name="postMapper" ref="postMapper"/>
</bean>
通过这段例子,我想大家应该明白了什么是依赖注入,如果不这样写的话,你前台要想获得对应的数据访问层(DAO层)数据,必须得实例化,十几二十个还好管理,成千上万个呢?那估计你肯定会有骂娘的冲动了。同时也明白了Spring为你管理对象的好处。如果没有Spring为你管理对象,一个一个实例化,那将是一件非常恐惧的事情。
四、面向切面编程
Spring的一个关键组件是面向切面编程(AOP)框架。跨越应用程序多个点的功能称为跨领域问题,这些跨领域问题在概念上与应用程序的业务逻辑分开。各方面有各种常见的良好示例,包括日志记录,声明式事务,安全性,缓存等。
OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。DI可帮助您将应用程序对象彼此分离,而AOP可帮助您将交叉问题与它们所影响的对象分离。
Spring Framework的AOP模块提供了面向方面的编程实现,允许您定义方法拦截器和切入点,以便干净地解耦实现应该分离的功能的代码。
就拿Spring的xml注解配置讲解:
<!-- 配置事务管理 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务管理 属性 --> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="append*" propagation="REQUIRED"/> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="modify*" propagation="REQUIRED"/> <tx:method name="edit*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="remove*" propagation="REQUIRED"/> <tx:method name="repair" propagation="REQUIRED"/> <tx:method name="get*" propagation="REQUIRED" read-only="true"/> <tx:method name="find*" propagation="REQUIRED" read-only="true"/> <tx:method name="load*" propagation="REQUIRED" read-only="true"/> <tx:method name="search*" propagation="REQUIRED" read-only="true"/> <tx:method name="datagrid*" propagation="REQUIRED" read-only="true"/> <tx:method name="*" propagation="REQUIRED" read-only="true"/> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.rms.service..*.*(..))"/> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/> </aop:config>
上述例子,比如配置切面,这个切面相当于不管怎么样你必定会经过的,比如电视中山大王经常去某个必经之路抢经过路人的钱财。
当然了,上面的配置,主要是关于事务的。熟悉xml事务配置的,对于上面xml代码再熟悉不过了。
不熟悉的也没有关系,用多了自然明白。另外补充一点,上面同时也代表了一种规则,比如我的添加方法叫userAdd,但是我的事务配置了必须要以add或者append或者其他read-only为false开头的才能在数据库对应的表新增数据,现在我的添加方法叫userAdd,它就会触发该规则中包含的另外一种规则就是:<tx:method name="*" propagation="REQUIRED" read-only="true"/>,这个规则将非上述列出的均判断为查询,而我的userAdd是新增,但是不符合上述配置的事务规则,就会导致报错,从而插入数据失败。
这样一来,大家或许就知道它的应用了,如果是传统的那种Java编程(非Spring),我们将要写很多判断。
五、Spring的架构图
经常用Spring相关的组件开发项目,对于上述架构图,我想一定不陌生了。
简要概述说明:
核心容器
核心容器由核心,Bean,上下文和表达式语言模块组成,其详细信息如下 -
-
所述Core 模块提供了框架的基本部分,包括IOC和依赖注入特征。
-
该Bean模块提供的BeanFactory,这是一个复杂的实现工厂模式。
-
的Context模块建立由核心和豆类模块提供的固体基体上,它是访问定义和配置的任何对象的介质。ApplicationContext接口是Context模块的焦点。
-
在使用SpEL模块提供用于查询并在运行时操作对象图的强大的表达式语言。
数据访问/集成
数据访问/集成层由JDBC,ORM,OXM,JMS和Transaction模块组成,其详细信息如下 -
-
该JDBC模块提供了一个JDBC的抽象层,消除了冗长的JDBC相关编码的需要。
-
的ORM模块提供的集成层为流行的对象关系映射API,包括JPA,JDO,休眠,和iBatis。
-
的OXM模块提供了支持用于JAXB,蓖麻,XMLBeans的,JiBX的及XStream对象/ XML映射实现的抽象层。
-
Java Messaging Service JMS模块包含用于生成和使用消息的功能。
-
该Transaction模块支持编程和声明式事务管理实现特殊接口的类,并为所有的POJO。
Web
Web层由Web,Web-MVC,Web-Socket和Web-Portlet模块组成,其详细信息如下 -
-
该Web模块提供了基本的面向Web的集成功能,如多文件上传功能,并使用servlet的听众和一个面向Web的应用程序上下文IoC容器的初始化。
-
该Web-MVC模块包含Web应用程序的Spring的模型-视图-控制器(MVC)的实现。
-
该Web-Socket模块提供客户端和Web应用程序服务器之间基于WebSocket的-,双向通信支持。
-
该Web-Portlet模块提供了MVC实现在门户环境中使用,并反映了网络服务程序模块的功能。
Miscellaneous
其他重要的模块很少,如AOP,Aspects,Instrumentation,Web和Test模块,其详细信息如下 -
-
在AOP模块提供了一个面向方面的编程实现,允许你定义方法拦截器和切入点干净地分离实现的功能,应该被分离的代码。
-
该Aspects模块提供了与AspectJ的,这又是一个强大而成熟的AOP框架集成。
-
该Instrumentation 模块提供类工具的支持和类加载器实现在某些应用服务器使用。
-
该Messaging模块提供STOMP作为WebSocket的子协议在应用程序中使用的支持。它还支持用于从WebSocket客户端路由和处理STOMP消息的注释编程模型。
-
该Test模块支持Spring组件的使用JUnit或TestNG的框架中进行测试。