Spring的历史
Spring 的发展
在JavaEE诞生的很长一段时间里,EJB技术被认为是J2EE的核心。但是不管从开发阶段,还是运行时看,EJB 都太过
臃肿。
Rod Johnson与2002年写了两本引发J2EE地震的图书:《Expert One-on-One J2EE Design andDevelopment》和
《Expert One-on-One J2EE Developmentwithout EJB》。
在《Expert One-on-One J2EE Design andDevelopment》中Rod Johnson总结J2EE各种技术的优缺点以及目前流行
框架的优缺点。然后在这本书中对于这些有“问题”给出了自己的的解决方案。这些解决方案被整理后就是Spring框架
最初的原型,当初它的名子不叫Spring而是叫interface21。
在《Expert One-on-One J2EE Developmentwithout EJB》中Rod Johnson总结了EJB所存在的问题,以及如何使用
Spring框架构建没有EJB的J2EE企业级系统。在这本书中Rod Johnson展示了Spring 框架给J2EE技术带来了前所未有
的进步。
2003 年,Rod Johnson 和同伴创建了 Spring公司 ,在interface21的基础上开发了一个全新的框架命名为 Spring, 据 Rod Johnson 介绍 Spring 是传统 J2EE 新的开始,随后 Spring 发展进入快车道。
2004 年 03 月,1.0 版发布。
2006 年 10 月,2.0 版发布。
2007 年 11 月,公司更名为 SpringSource,同时发布了 Spring 2.5。
2009 年 12 月,Spring 3.0 发布。
2013 年 12 月,Pivotal 宣布发布 Spring 框架 4.0。
2017 年 09 月,Spring 5.0 发布。
Spring 所属公司
1989 年,Rob Mee 创立咨询公司 Pivotal Labs,专注于快速的互联网式软件开发,即敏捷编程。创立 Pivotal Labs
的时候,它还是一家非常小的软件顾问公司,它的主营业务就是与客户合作,帮助客户开发软件。
2012 年,EMC 以现金方式收购了 Pivotal 并照单全收了它的 200 名员工。刚开始的时候,公司并没有发生太大的变
化,只是作为新部门成为了 EMC 的一部分,Pivotal Labs 仍然继续像以前样与客户合作。
2013 年,EMC 和 VMware 分拆出其 Cloud Foundry、Pivotal Labs、Greenplum 等云计算、大数据资源,GE(通
用电气) 投资 1.05 亿美元,成立新公司 Pivotal。
Pivotal 公司成立之后,于 2014 年发布了 Spring Boot,2015 年发布了 Spring Cloud,2018 年 Pivotal 公司在纽约
上市。
Pivotal 公司可谓是大牛云集,公司的常见开源产品(https://pivotal.io/cn/open-source)有:Spring 以及 Spring
衍生产品、缓存中间件 Redis、消息中间件 RabbitMQ、机器学习库MADlib等。
1989 年,Rob Mee 创立的咨询公司 Pivotal Labs;
2003 年,Rod Johnson 和同伴创建了 Spring;
2003 年,EMC 收购了 VMware 公司;
2009 年,VMware 以 4.2 亿美元收购 Spring Source (3.6亿现金外加5800万股份) 。 ;
2012 年,EMC 又收购了 Pivotal Labs 公司;
2013 年,EMC 、 VMware 和收购来的 Pivotal Labs 公司重新组建了新的公司 Pivotal;
2015 年,戴尔又并购了 EMC;
2018 年,戴尔独立上市。
Spring介绍
Spring框架是一个轻量级的解决方案,可以一站式地构建企业级应用。Spring是模块化的,所以可以只使用其中需要
的部分。
核心容器(Core Container)
核心容器包括spring-core,spring-beans,spring-context,spring-context-support和springexpression(SpEL)等模块。
spring-core和spring-beans模块是Spring框架的基础,包括控制反转和依赖注入等功能。BeanFactory是工厂模式
的复杂实现。可以把配置和依赖从实际编码逻辑中解耦。
spring-context模块是在Core和Bean模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。
Context模块继承自Bean模块,并且添加了国际化、事件传播、资源加载等功能。Context模块也支持Java EE的功
能,比如EJB、JMX和远程调用等。spring-context-support提供了对第三方库集成到Spring上下文的支持,比如缓存
(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker,
JasperReports, Velocity)等。
spring-expression模块提供了强大的表达式语言用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表
达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变
量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。
AOP 及 Instrumentation
spring-aop模块提供了面向切面编程(AOP)的实现,可以定义诸如方法拦截器和切入点等,从而使横切的代码彻
底的解耦出来。
spring-aspects模块提供了对AspectJ的集成。
spring-instrument模块提供了对检测类的支持和用于特定的应用服务器的类加载器的实现。
spring-instrument-tomcat模块包含了用于tomcat的Spring检测代理。
消息
Spring 4 包含的spring-messaging模块是从Spring集成项目的关键抽象中提取出来的,这些项目包括Message、
MessageChannel、MessageHandler和其它服务于消息处理的项目。这个模块也包含一系列的注解用于映射消息
到方法,这类似于Spring MVC基于编码模型的注解。
数据访问/集成
数据访问与集成层包含JDBC、ORM、OXM(Object XML Mapping)、JMS和事务模块。
spring-jdbc模块提供了JDBC抽象层,它消除了冗长的JDBC编码和对数据库特定错误代码的解析。
spring-tx模块支持编程式事务和声明式事务,可用于实现了特定接口的类和所有的POJO对象。 (编程式事务需要自
己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由spring自动处理,
编程式事务粒度更细)
spring-orm模块提供了对流行的ORM框架的集成,包括jpa、hibernate等。通过此模块可以让这些ORM框架和
spring的其它功能整合,比如前面提及的事务管理。
spring-oxm模块提供了对OXM实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。
spring-jms模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了springmessaging模块。
Web
Web层包括spring-web、spring-webmvc、spring-websocket、spring-webmvc-portlet等模块。
spring-web模块提供面向web的基本功能和面向web的应用上下文,比如多部分(multipart)文件上传功能、使用
Servlet监听器初始化IoC容器等。它还包括HTTP客户端以及Spring远程调用中与web相关的部分。
spring-webmvc模块(即Web-Servlet模块)为web应用提供了模型视图控制MVC和REST Web服务的实现。Spring
的MVC框架可以使领域模型代码和web表单完全地分离,且可以与Spring框架的其它所有功能进行集成。
spring-webmvc-portlet模块(即Web-Portlet模块)提供了用于Portlet环境的MVC实现,并反映了springwebmvc模块的功能。
Test
spring-test模块通过JUnit和TestNG组件支持单元测试和集成测试。它提供了一致性地加载和缓存Spring上下文,也
提供了用于单独测试代码的模拟对象(mock object)。
Spring常用模块
IOC
spring-beans 和 spring-context 是 Spring Framework 的 IoC 容器的根本。 BeanFactory 接口提供了一种能够管理
任何类型对象的高级配置机制。ApplicationContext 是 BeanFactory 的一个子接口。ApplicationContext 使得与
Spring 的AOP集成变得更简单,添加了资源处理(国际化中使用)、事件发布,以及特定于应用程序层的上下文,
例如 WebApplicationContext
简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext 添加了更多企业应用功能。
ApplicationContext完整扩展了BeanFactory。
基于XML配置的IOC
获取上下文:
applicationContext.xml: ApplicationContext applicationContext = new ClassPathXmlApplicationContext( new String[] { “com/demo/spring/sample/step01/applicationContext.xml” }); //使用构造函数创建的bean User user = (User) applicationContext.getBean(“user”); <?xml version="1.0" encoding="UTF-8"?> <beans … >
详细代码参阅:com.demo.spring.sample.step01
基于注解配置的IOC
为了缩减XML的体积,以及把配置元数据和代码放在一起,Spring2.5开始支持基于注解的配置,总体的思路是让
Spring扫描类路径下的Class,通过反射检查是否有特定注解,如果类上存在@Service一类的注解则实例化该类为
bean,依赖注入由自动注册的相关BeanPostProcessor根据注解来完成。
Step03.java:
applicationContext_scan.xml:
UserService.java:
。。。。。。 /*** 基于BeanPostProcessor与注解进行依赖注入。 * 通过扫描classpath并匹配过滤器来隐式地检测相关组件。 / public class Step03 { private static Logger logger = LogManager.getLogger(Step03.class); public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( “com/demo/spring/sample/step03/applicationContext_scan.xml”); IUserService userService = (IUserService) context.getBean(“userService”); userService.create(); userService.create(10); } }<?xml version="1.0" encoding="UTF-8"?> <beans …> <context:component-scan base-package=“com.demo.spring.sample.step03” /> /*
bean的生命周期
Spring自带了两种不同的类型的容器。两种容器的的bean生命周期略有不同 1:bean工厂(由
org.springframework.beans.factory.BeanFactory接口定义)是最简单的容器,提供基本的DI 支持。 2:应用上下文
(由org.springframework.context.ApplicationContext接口定义),基于BeanFactory构建, 并提供应用框架级别的
服务,例如 从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。
ApplicationContext Bean生命周期: 1.Spring对bean进行实例化; 2.Spring将值和bean的引用注入到bean对
应的属性中; 3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法; 4.如
果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入; 5.
如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用
上下文的引用传入进来; 6.如果上下文中存在实现了BeanPostProcessor接口的bean,Spring将调用它们的
postProcessBeforeInitialization()方法; 7.如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。 8.如果bean使用init-method声明了初始化方法,该方法被调用; 9.如果上下文中存在实
现了BeanPostProcessor接口的bean,Spring将调用它们的postProcessAfterInitialization()方法; 经过以上过程之
后,就可以正式使用该Bean了,对于scope为singleton的Bean,它们将一直驻留在应用上下文中,直到该应用上下文
被销毁;而对于scope为prototype的Bean,每次被调用都回new一个对象,而且生命周期也交给调用方管理了,不再是
Spring容器进行管理了。
10. 容器关闭后,如果Bean实现了DisposableBean接口,则会调用该接口的destroy()方法。
11. 如果Bean配置了destroy-method方法,则会执行destroy-method配置的方法,至此,整个Bean生命周期结
束。
BeanFactory Bean生命周期: 与ApplicationContext相比,有如下几点不同:
- BeanFactory容器中,不会调用ApplicationContextAware接口的setApplicationContext()方法。
- @Autowired:按照类型自动装配,可以放置在构造方法、属性、参数等地方,由 * AutowiredAnnotationBeanPostProcessor后置处理器进行解析,然后装配 ** @author 王庆丰 * @version 2017年10月12日 下午5:31:20 */ @Service(“userService”) public class UserService implements IUserService { private static Logger logger = LogManager.getLogger(UserService.class); @Autowired private IUserDAO userDAO = null; public void create() { logger.debug(“create() 方法”); userDAO.create(); }public void create(int id) { logger.debug(“create(id) 方法”); userDAO.create(id); } }
- BeanPostProcessor接口的postProcessBeforeInitialization方法和postProcessAfterInitialization方法不会自
动调用,必须自己通过代码手动注册 - BeanFactory容器启动的时候,不会去实例化所有bean,包括所有scope为singleton且非延迟加载的bean也是
一样,而是在调用的时候去实例化。
详细代码参阅:com.demo.spring.sample.step06
AOP
面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足。在OOP中模块
化的关键单元是类(classes),而在AOP中模块化的单元则是切面。切面能对关注点进行模块化,例如横切多个类
型和对象的事务管理。(在AOP术语中通常称作横切(crosscutting)关注点。)
AOP的基本概念
切面(Aspect)
一个关注点的模块化,是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完
成其功能。
连接点(Joinpoint)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。
切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。因为Spring基于代理实现AOP,所以只支持
方法连接点。
通知(Advice)
在切面的某个特定的连接点上执行的动作。Spring切面可以应用5种类型的通知: - 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。\ 6. 引入通知(Introduction):用来给一个类型声明额外的方法或属性。Spring允许引入新的接口(以及一个对应
的实现)到任何被代理的对象。可以在无需修改现有的类的情况下,让它们具有新的行为和状态。
切入点(Pointcut)
切入点是匹配连接点的表达式。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行。一个切面并不
需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。
目标对象(Target Object)
被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,
这个对象永远是一个被代理(proxied)对象。
AOP代理(AOP Proxy) AOP框架创建的对象,用来实现切面(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者
CGLIB代理。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的
生命周期里有多个点可以进行织入: - 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入
切面的。 - 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标
类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,LTW)就支持以这种
方式织入切面。 - 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建
一个代理对象。Spring AOP就是以这种方式织入切面的。
Spring对AOP的支持
Spring提供了4种类型的AOP支持:
基于代理的经典Spring AOP
基于XML配置当然,曾经它的确非常棒。但是现在Spring提供了更简洁和干净的面向切面编程方式。引入了简单的声
明式AOP和基于注解的AOP之后,Spring经典的AOP看起来就显得非常笨重和过于复杂。
纯POJO切面
借助Spring的aop命名空间,我们可以将纯POJO转换为切面。实际上,这些POJO只是提供了满足切点条件时所要调
用的方法。遗憾的是,这种技术需要XML配置,但这的确是声明式地将对象转换为切面的简便方式。
@AspectJ注解驱动的切面
Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。本质上,它依然是Spring基于代理的AOP,但是编程模型几
乎与编写成熟的AspectJ注解切面完全一致。这种AOP风格的好处在于能够不使用XML来完成功能。
需要导入aspectjweaver.jar,4.3.8依赖1.8.9
注入式AspectJ切面
如果AOP的切点要求超出了方法(如构造器或属性拦截),那么需要考虑使用AspectJ来实现切面。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法
拦截。
AOP的实现
经典Spring AOP
完全基于XML进行配置,通知需要实现对应的接口
获取上下文:
applicationContext.xml: ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“com/demo/spring/sample/step02/proxyfactorybean/applicationC ontext.xml”); IUserService userService = (IUserService) applicationContext.getBean(“userService”); userService.create(); <?xml version="1.0" encoding="UTF-8"?> <beans …> com.demo.spring.sample.step02.proxyfactorybean.service.IUserService
详细代码参阅:com.demo.spring.sample.step02.proxyfactorybean
纯POJO切面
通知不需要实现相关接口,只是一个简单的java类,切点和切面的定义在XML中。
由于spring在解析切点的定义时(org.springframework.aop.aspectj.AspectJExpressionPointcut),使用的是aspectj
的工具类,所以需要导入aspectjweaver.jar。
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。 若该
目标对象没有实现任何接口,则创建一个CGLIB代理。也可以通过proxy-target-class调整。cglib的相关类文件打包
在了spring-core-4.3.8.RELEASE.jar中
获取上下文:
applicationContext.xml: <?xml version="1.0" encoding="UTF-8"?> <beans …> <aop:config proxy-target-class=“false”> <aop:aspect id=“log” ref=“traceLog”> myMethodBeforeAdvice ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“com/demo/spring/sample/step02/pojo/applicationContext.xml”) ;UserService userService = (UserService) applicationContext.getBean(“userService”); userService.create(12); IRoleService roleService = (IRoleService) userService; roleService.delete(11);
详细代码参阅:com.demo.spring.sample.step02.pojo
@AspectJ注解驱动的切面
AspectJ 5使用了Java注解定义切面,Spring 使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。
但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)。
启用@AspectJ注解定义aop: 1:通过在Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:
aop:aspectj-autoproxy/ ,该配置告诉Spring容器,AOP要通过注解来定义,注解信息在加入@Aspect注解的
类中 2:AOP并不知道切面的定义在哪些类里,所以需要让Spring容器创建包含切面定义类的实例,从而找到切面定
义:
当让也可以在配置类中同时加入@Component注解,由Spring的自动扫描类来扫描AOP注解定义所在的来,然后注
册到上下文。
获取上下文:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“com/demo/spring/sample/step02/aspectj/applicationContext.xml “); IUserService userService = (IUserService) applicationContext.getBean(“userService”); userService.create(); userService.create(10);
切点定义类:
package com.demo.spring.sample.step02.aspectj; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; /**** @author 王庆丰 * @version 2017年10月12日 下午2:11:30 / @Aspect public class TraceLog { private static Logger logger = LogManager.getLogger(TraceLog.class); /** 这个切入点定义了spring包下的任意service实现类的任意方法,包结构可能是 * com.demo.spring.user.service.impl / @Pointcut("execution( com.demo.spring…service..(…))”) public void businessService() { }/*** 使用已命名的切入点,也可以使用内置的切入点表达式 */ @Before(“com.demo.spring.sample.step02.aspectj.TraceLog.businessService()”) public void before() { System.out.println(”----------------------------"); logger.debug(“前置通知”); } }
applicationContext.xml:
详细代码参阅:com.demo.spring.sample.step02.aspectj
代理模式
为其他对象提供一种代理以控制对这个对象的访问。
代理的几种实现模式,及案例代码,案例中通过代理检查参数,如果参数不符合要求,则抛出异常。详细代码参阅:
com.demo.spring.sample.step00.asm;
静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类
的字节码文件,代理类和委托类的关系在运行前就确定了。
Client.java:
/*** 静态代理演示代码
/ public class Client { /** @param args / public static void main(String[] args) { IUserService userService = new ProxyUserServiceImpl(new UserServiceImpl()); userService.create(101); } }
IUserService.java:
/** 接口,由目标类和代理实现 / public interface IUserService { public void create(int id); }
ProxyUserServiceImpl.java:
/** 代码类,拦截目标方法的调用 / public class ProxyUserServiceImpl implements IUserService { private UserServiceImpl userService = null; public ProxyUserServiceImpl(UserServiceImpl userService) { this.userService = userService; }public void create(int id) { if (id > 100) { throw new RuntimeException(); }userService.create(id); } }
UserServiceImpl.java:
/** 目标类,被代理类 */ public class UserServiceImpl implements IUserService { public void create(int id) { System.out.println(“create”); } }
动态代理
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
- JDK动态代理
Client.java:
package com.demo.spring.sample.step00.proxy.dynamic.jdk; /*** jdk动态代理,要求被代理类实现接口 / public class Client { public static void main(String[] args) { IUserService userService = (IUserService) Factory.create(IUserService.class, new UserServiceImpl()); userService.create(101); } }
Factory.java:
import java.lang.reflect.Proxy; /** 根据传入的接口class实例和对应的实现类实例生成代理对象 / public class Factory { public static Object create(Class Superclass, Object target) { Handler handler = new Handler(target); /* 动态生成代理对象: * Factory.class.getClassLoader():类加载器 * new Class[] { clazz }:接口对应的class
- handler:调用拦截器 / return Proxy.newProxyInstance(Factory.class.getClassLoader(), new Class[] {Superclass}, handler); } }
Handler.java:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** 调用拦截器 / public class Handler implements InvocationHandler { private Object targetObject = null; public Handler(Object targetObject) { this.targetObject = targetObject; }@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-----------Handler—>方法调用前---------------------"); if(“create”.equals(method.getName())){ int id=(Integer)args[0]; if (id > 100) { throw new RuntimeException(); } }Object object = method.invoke(targetObject, args); System.out.println("-----------Handler—>方法调用后---------------------"); return object; } }
IUserService.java:
/** 接口,由目标类实现 / public interface IUserService { public void create(int id); }
UserServiceImpl.java:
/** 目标类,被代理类 / public class UserServiceImpl implements IUserService { public void create(int id) { System.out.println(“create”); } } 2. cglib代理
Client.java:
/* cglib代理 / public class Client { public static void main(String[] args) { UserServiceImpl userService = (UserServiceImpl) Factory.create(UserServiceImpl.class); userService.create(10); } }
Factory.java:
import org.springframework.cglib.proxy.Enhancer; /** 根据传入的接口名称和对应的实现类实例生成代理对象 / public class Factory { public static Object create(Class Superclass) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Superclass); enhancer.setCallback(new Interceptor()); //动态生成代理对象 return enhancer.create(); } }
Interceptor.java:
import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** 目标对象拦截器,实现MethodInterceptor / public class Interceptor implements MethodInterceptor { /** @param obj 由CGLib动态生成的代理类实例, * @param method Method为上文中实体类所调用的被代理的方法引用 * @param params Object[]为参数值列表 * @param proxy 为生成的代理类对方法的代理引用。 * @return 从代理实例的方法调用返回的值。 * @throws Throwable / public Object intercept(Object obj, Method method, Object[] params, MethodProxy proxy) throws Throwable { System.out.println("-----------Interceptor—>方法调用前---------------------"); if (“create”.equals(method.getName())) { int id = (Integer) params[0]; if (id > 100) { throw new RuntimeException(); } }Object result = proxy.invokeSuper(obj, params); System.out.println("-----------Interceptor—>方法调用后---------------------"); return result; } }
UserServiceImpl.java:
/** 目标类,被代理类 **/ public class UserServiceImpl implements IUserService { public void create(int id) { System.out.println(“create”); } }
事务管理
Spring基于APO提供了事务管理,框架的事务支持提供了一致的事务管理抽象,这带来了以下好处: 1:为复杂的事
务API提供了一致的编程模型,如JTA、JDBC、Hibernate、JPA和JDO
2:支持声明式事务管理 3:提供比大多数复杂的事务API(诸如JTA)更简单的,更易于使用的编程式事务管理API
4:与Spring的各种数据访问抽象完美结合
基于XML配置的事务
基于注解配置的事务
将事务通知和切面的定义放入了java代码中
详细代码参阅:com.demo.spring.sample.step04
DAO
Spring提供的DAO(数据访问对象)支持主要的目的是便于以标准的方式使用不同的数据访问技术,不仅可以方便地在
这些持久化技术间切换, 而且在编码的时候不用考虑处理各种技术中特定的异常。 1:一致的异常层次 Spring提供了
一种方便的方法,把特定于某种技术的异常,如SQLException, 转化为自己的异常,这种异常属于以
DataAccessException 为根的异常层次。这些异常封装了原始异常对象,这样就不会有丢失任何错误信息的风险。
2:一致的DAO支持抽象类 为了便于以一种一致的方式使用各种数据访问技术,如JDBC、JDO和Hibernate, Spring
提供了一套抽象DAO类供你扩展。这些抽象类提供了一些方法,通过它们你可以 获得与你当前使用的数据访问技术
相关的数据源和其他配置信息。
applicationContext_jdbc.xml:
UserDAO.java:
import com.demo.spring.sample.step05.jdbc.dao.IUserDAO; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.jdbc.core.JdbcTemplate; /*** @author 王庆丰 * @version 2017年10月12日 下午5:19:33 / public class UserDAO implements IUserDAO { private static Logger logger = LogManager.getLogger(UserDAO.class); private JdbcTemplate jdbcTemplate = null; public void create(int id) { jdbcTemplate.execute(“insert into users(user_id) values(” + id + “)”); }public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; }public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
UserDAO2.java
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.jdbc.core.support.JdbcDaoSupport; /*
- @author 王庆丰 * @version 2017年10月12日 下午5:19:33 */ public class UserDAO2 extends JdbcDaoSupport { private static Logger logger = LogManager.getLogger(UserDAO2.class); public void create(int id) { super.getJdbcTemplate().execute(“insert into users(user_id) values(” + id + “)”); } }
脱离XML配置Spring
基于注解和XML混合配置Spring
在最大化使用注解后,Spring的配置文件大致如下:
基于注解和Java代码配置Spring
Spring3开始支持通过Java代码来配置Spring,Spring的配置可以完全脱离XML
Step08.java
import com.demo.spring.sample.step08.config.AppConfig; import com.demo.spring.sample.step08.config.JDBCConfig; import com.demo.spring.sample.step08.service.IUserService; import com.demo.spring.sample.step08.service.bo.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /*** 1:基于Java的配置和基于XML的配置是可以共存的:
* ----XML中一些基于命名空間的配置还是比较有优势的,如果以Java配置为中心,则可以使用@ImportResource导入XMl 配置* ----如果以XML配置为中心,可以将@Configuration的类注册为bean,当然也可以通过注解扫描自动注册 ** @author Administrator / public class Step08 { static AnnotationConfigApplicationContext context = null; static { // 使用AnnotationConfigApplicationContext作为应用上下文,在web环境下用 AnnotationConfigWebApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AppConfig.class, JDBCConfig.class); context.refresh(); }private static Logger logger = LogManager.getLogger(Step08.class); public static void main(String[] args) throws Exception { User user = (User) context.getBean(“user1”); IUserService userService = (IUserService) context.getBean(“userService”); User user2=new User(); user2.setId(1231); userService.create(user2); } }
AppConfig.java
import com.demo.spring.sample.step08.service.bo.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration // 表明当前类提供Spring配置文件的作用,Spring上下文会从当前类的注解中提取配置信息 //@Import(JDBCConfig.class) //导入其他类提供配置信息,也可以在初始化上下文是指定多个 @ComponentScan(basePackages = “com.demo.spring.sample.step08”) // 开启组件扫描 @EnableAspectJAutoProxy @EnableTransactionManagement //启用事务管理 //@ImportResource(“classpath:/com/acme/properties-config.xml”),如果混合使用基于java和xml的配置, 而以java配置为中心,则可以导入xml配置 @PropertySource(value = {“classpath:jdbc.properties”})//导入属性资源 public class AppConfig { private static Logger logger = LogManager.getLogger(AppConfig.class); @Value("KaTeX parse error: Expected 'EOF', got '}' at position 356: … new User(); } }̲
JDBCConfig.jav…{jdbc.driverClassName}") private String driverClassName; @Value("{jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; }@Bean public JdbcTemplate jdbcTemplate() { JdbcTemplate jdbcTemplate = new JdbcTemplate(); //调用dataSource(),相当于获取dataSource() 定义的bean,多次调用不会生成多个实例 jdbcTemplate.setDataSource(dataSource()); return jdbcTemplate; }/*** 基于java的配置中,事务管理器的默认名称为txManager ** @return / @Bean public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } }TraceLog.java
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect public class TraceLog { private static Logger logger = LogManager.getLogger(TraceLog.class); public TraceLog() { logger.debug(“TraceLog”); }@After("execution( com.demo.spring…service..(…))") public void after() { logger.debug(“最终通知”); } }
UserService.java
import com.demo.spring.sample.step08.dao.IUserDAO; import com.demo.spring.sample.step08.service.IUserService; import com.demo.spring.sample.step08.service.bo.User; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service(“userService”) public class UserService implements IUserService { private static Logger logger = LogManager.getLogger(UserService.class); @Autowired private IUserDAO userDao = null; @Transactional public void create(User user) { userDao.create(user.getId()); } }
UserDAO.java
import com.demo.spring.sample.step08.dao.IUserDAO; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /**** @author 王庆丰 * @version 2017年10月12日 下午5:19:33 */ @Repository public class UserDAO implements IUserDAO { private static Logger logger = LogManager.getLogger(UserDAO.class); @Autowired private JdbcTemplate jdbcTemplate = null; public void create(int id) { //两次插入,模拟数据库抛出异常,事务回滚 jdbcTemplate.execute(“insert into users(user_id) values(”+id+")"); //jdbcTemplate.execute(“insert into users(user_id) values(”+id+")"); } }
与其他框架的集成
集成Mybatis
Spring与MyBatis的整合需要借助于MyBatis的一个子项目MyBatis-Spring.
详细代码参考:com.demo.spring.sample.step09
1.基于XML的配置
<tx:annotation-driven transaction-manager=“transactionManager” />
2.基于Java的配置
import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.io.IOException; /*** Mybatis配置 / @Configuration public class MybatisConfig { @Value("{jdbc.ur}") private String url; @Value("{jdbc.password}") private String password; @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; }@Bean public SqlSessionFactoryBean sqlSessionFactory() throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 设置数据源 sqlSessionFactoryBean.setDataSource(dataSource()); // 设置mybatis的映射文件 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(resolver.getResources ("classpath:com/demo/spring/sample/step09/mybatis//*Mapper.xml")); return sqlSessionFactoryBean; }@Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage( "com.demo.spring.sample.step09.mybatis..dao"); return mapperScannerConfigurer; }/*** 基于java的配置中,事务管理器的默认名称为txManager ** @return */ @Bean public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } }
附录:
日志管理
Spring中强制的日志管理依赖是Jakarta Commons Logging API(JCL),它是唯一的强制性外部依赖
如果需要log4j2,需要导入三个log4j的包:
log4j-jcl-2.7.jar:与Commons Logging 的桥接包
log4j-core-2.7.jar:log4j的实现
log4j-api-2.7.jar:日志接口和创建Logger实例的类,便于log4j桥接到其它日志工具,如:SLF4J
其它日志工具桥接log4j:
log4j桥接其它日志工具: