一、概念
Spring是Java EE编程领域的一个轻量级开源框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。 Spring是一个开源容器框架,它集成各类型的工具,通过核心的Bean factory实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。
传统J2EE应用的开发效率低,应用服务器厂商对各种技术的支持并没有真正统一,导致J2EE的应用没有真正实现Write Once及Run Anywhere的承诺。Spring作为开源的中间件,独立于各种应用服务器,甚至无须应用服务器的支持,也能提供应用服务器的功能,如声明式事务、事务处理等。
Spring致力于J2EE应用的各层的解决方案,而不是仅仅专注于某一层的方案。可以说Spring是企业应用开发的“一站式”选择,并贯穿表现层、业务层及持久层。然而,Spring并不想取代那些已有的框架,而是与它们无缝地整合。
1.Spring的作用
了解一个框架我们可以先从它的官网入手:spring.io。在官方我们可以看到这样一段描述:
- Microservices:微服务开发。 也就是说我们现在随便开发一个应用,由于功能模块众多,未来都有可能成为一个大型应用,如果将所有的模块代码全写在一个项目里面是不合适的,我们应该把整个应用所有功能拆分成一个一个微小的功能模块,每一个微小的功能模块称为一个微服务。而 Spring可以帮助我们快速开发一个微服务。
- Reactive:响应式编程。 它是基于异步非阻塞的方式,是可以通过整个应用之间构建一个异步数据流的方式。异步数据流允许我们占用服务器少量线程资源,包括少量的CPU或内存资源,就可以构建一个高吞吐量的应用。所以 Spring可以帮助我们做一个响应式开发。
- Cloud:分布式云开发。 当把大型应用拆分成一个一个微小模块的时候,就会产生分布式应用。Spring 便提供了分布式开发的解决方案 Spring Cloud。
- Web apps:Web开发。 使用 SpringMVC 来开发一个Web应用,发请求返回 json数据、发请求返回页面,这是 Spring 带来的基本功能。
- Serverless:无服务开发。 简单来说,就是 Faas(Function as a service,函数级服务)。也就是 Spring 可以为我们做函数式服务。简单快速的开发一个服务,无需购买任何的服务器。以前要购买几核几G的服务器,浪费资源,空闲的时候就闲置了。而 Spring 就可以将函数式服务上传到云平台,到底占用多少资源,我们可以按量计费,实时计费。如此可以节省很多人力财力资源。
- Event Driven:事件驱动。 Spring 可以基于事件的方式将整个的分布式系统来构建出一个实时的(Streaming data)数据流。通过这个实时数据流,Spring 就可以通过响应式的方式,让整个系统占用少量的资源,就能完成高吞吐的业务。
- Batch:批处理。 批处理业务。
以上并不都是 Spring Framework 这一个解决方案完成的,而是由各种解决方案解决的。如:Spring Boot、Spring Framework、Spring Data、Spring Cloud…
2.Spring的特点
官方文档中对Spring的介绍如下:
The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise applications - on any kind of deployment platform.
A key element of Spring is infrastructural support at the application level: Spring focuses on the "plumbing" of enterprise applications so that teams can focus on application-level business logic, without unnecessary ties to specific deployment environments.
简单翻译一下就是:
Spring框架为现代基于java的企业应用程序提供了一个全面的编程和配置模型——适用于任何类型的部署平台。
Spring的一个关键元素是应用程序级别的基础设施支持:Spring专注于企业应用程序的“管道”,以便团队可以专注于应用程序级别的业务逻辑,而无需与特定的部署环境建立不必要的联系。
在Spring的官网中对它的特性描述如下:
Features
Core technologies: dependency injection, events, resources, i18n, validation, data binding, type conversion, SpEL, AOP.
Testing: mock objects, TestContext framework, Spring MVC Test,
WebTestClient
.Data Access: transactions, DAO support, JDBC, ORM, Marshalling XML.
Spring MVC and Spring WebFlux web frameworks.
Integration: remoting, JMS, JCA, JMX, email, tasks, scheduling, cache and observability.
Languages: Kotlin, Groovy, dynamic languages.
Spring为JavaEE开发提供了一个轻量级的解决方案,主要表现为:
- IOC(或者叫做DI)的核心机制,提供了bean工厂(Spring容器),降低了业务对象替换的复杂性,提高了组件之间的解耦。
- AOP的将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和管理的便捷性
- ORM和DAO提供了与第三方持久层框架的良好整合,简化了底层数据访问。
- 提供了优秀的Web MVC框架。
可以说Spring是贯穿表现层、业务层、持久层,为javaEE提供一站式解决方案的框架。此外,使用Spring还有如下好处:
- 低侵入设计,代码污染极低。
- 基于Spring框架的应用,可以独立于各种应用服务器,实现 write once, run anywhere,
- Spring可以与第三方框架良好整合(如ORM,DAO等模块与其他框架整合),但同时Spring提供了高度开放性,应用不会被强制依赖Spring,开发者可以自由选择Spring的部分或者全部。
Spring并不局限于中间层,而是为系统各层都提供了企业级解决方案
使用Spring框架可以带来诸多好处,例如进行数据库事务处理,远程调用,JMS消息处理,JMX操作处理,而这些处理都不需要开发人员直接使用相关API(JDBC, JMX, JMS 等)
3.Spring Framework架构组成
Spring框架是分模块存在,除了最核心的Spring Core Container(即Spring容器)是必要模块之外,其他模块都是可选,视需要而定。
官方文档Spring4.0的架构图中,包含了20多个子模块,大致可以分为四类 1)核心容器(Core Container) 2)数据访问和集成(Data Access/Integration) 3)Web 4)AOP。
本质上Spring可以总结为以下七个模块。
其中常用模块的大致功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。可以将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和管理的便捷性。
Spring DAO:为JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
二、核心组件
Spring 框架中的核心组件只有三个:Core、Context 和 Beans,其中Beans 组件最为重要;
Spring 就是面向 Bean 的编程(BOP,Bean Oriented Programming):关键点是把对象之间的依赖关系转而用配置文件来管理,也就是依赖注入机制;
Spring 设计策略类似面向对象编程(Object Oriented Programming),Spring将对象包装在 Bean 中,进而对这些对象管理以及进行一些额外操作;
三者关系:Bean 中包装Object,Object中包含数据;Context是Bean 依赖关系的集合;Core 是发现、建立和维护每个 Bean 之间的关系所需要的一些列的工具;
1.Bean 组件在 Spring 的 org.springframework.beans 包下:Bean 的定义、Bean 的创建以及对 Bean 的解析
a.Bean 的定义,BeanDefinition,类图如下:
Bean 的定义就是完整的描述了在 Spring 的配置文件中定义的 <bean/> 节点中所有的信息,包括各种子节点。当 Spring 成功解析已定义的一个 <bean/> 节点后,在 Spring 的内部它就回被转化成 BeanDefinition 对象。以后所有的操作都是对这个对象完成的。
b.Bean 的创建由工厂模式实现,类图如下:![](https://i-blog.csdnimg.cn/blog_migrate/7a24434e31e7effe55bf96cb060a5c21.png)
其中BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory;最终实现所有的接口的默认实现类是DefaultListableBeanFactory;ListableBeanFactory 接口表示这些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。
c.Bean 的解析,类图如下:
2.Context 组件在 Spring 的 org.springframework.context 包下:环境构建
Context 相关的类结构图如下:
ApplicationContext 是 Context 的顶级父类,他除了能标识一个应用环境的基本信息外,他还继承了扩展了 Context 功能的五个接口;
ApplicationContext 继承了 BeanFactory(再次Spring 容器中运行的主体对象是 Bean);ApplicationContext 继承了 ResourceLoader 接口用于访问到任何外部资源(Core 的功能);
ApplicationContext 的子类主要包含两个方面:
- ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息,它下面又有多个子类,其中最经常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 类。
- WebApplicationContext 顾名思义,就是为 web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用的少。
Context 作为 Spring 的 Ioc 容器,基本上整合了 Spring 的大部分功能,或者说是大部分功能的基础:标识一个应用环境;利用 BeanFactory 创建 Bean 对象;保存对象关系表;能够捕获各种事件;
3.Core 组件: Spring 的核心组件
Core 组件作为 Spring 的核心组件,包含了很多的关键类,重要组成部分就是定义了资源的访问方式;
Core 相关的类结构图如下:
Resource 接口继承了 InputStreamSource 接口,这个接口中有个 getInputStream 方法,返回的是 InputStream 类,所有的资源都被可以通过 InputStream 类来获取,屏蔽资源提供者;
ResourceLoader 接口实现加载资源,保持资源的加载者的统一,默认实现是 DefaultResourceLoader;
Context 和 Resource 的类关系图如下:
Context 是把资源的加载、解析和描述工作委托给了 ResourcePatternResolver 类来完成,他相当于一个接头人,他把资源的加载、解析和资源的定义整合在一起便于其他组件使用;
三、IOC-控制反转
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法
没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系
完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,所谓控制反转就是:获得依赖对象的方式反转了。
1.控制反转
Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
它的底层设计模式采用了工厂模式,所有的 Bean 都需要注册到Bean工厂中,将其初始化和生命周期的监控交由工厂实现管理。程序员只需要按照规定的格式进行Bean开发,然后利用XML文件进行bean 的定义和参数配置,其他的动态生成和监控就不需要调用者完成,而是统一交给了平台进行管理。
控制反转是软件设计大师 Martin Fowler在 2004 年发表的”Inversion of Control Containers and the Dependency Injection pattern”提出的。这篇文章系统阐述了控制反转的思想,提出了控制反转有依赖查找和依赖注入实现方式。控制反转意味着在系统开发过程中,设计的类将交由容器去控制,而不是在类的内部去控制,类与类之间的关系将交由容器处理,一个类在需要调用另一个类时,只要调用另一个类在容器中注册的名字就可以得到这个类的实例,与传统的编程方式有了很大的不同,“不用你找,我来提供给你”,这就是控制反转的含义 。
2.源码理解
如何创建 BeanFactory 工厂?
Ioc 容器,实际上是 Context 组件结合其他两个组件共同构建了一个 Bean 关系网,源码如下:
源码片段0:ApplicationContext =new FileSystemXmlApplicationContext(xmlPath);
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML files and automatically refreshing the context.
* @param configLocations array of file paths
* @throws BeansException if context creation failed
*/
//构造方法
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
//实际调用
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
源码片段1:AbstractApplicationContext 的refresh 方法
//构建的入口就在 AbstractApplicationContext 类的 refresh 方法中,方法的代码如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 // Prepare this context for refreshing.
prepareRefresh();
//告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从 // Tell the subclass to refresh the internal bean factory.
//子类的refreshBeanFactory()方法启动 --父子工厂均进行刷新
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器特性,例如类加载器、事件处理器等 // Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的BeanPost事件处理器 // Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
//调用所有注册的BeanFactoryPostProcessor的Bean // Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
//为BeanFactory注册BeanPost事件处理器. // Register bean processors that intercept bean creation.
//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和国际化相关. // Initialize message source for this context.
initMessageSource();
//初始化容器事件传播器. // Initialize event multicaster for this context.
initApplicationEventMulticaster();
//调用子类的某些特殊Bean初始化方法 // Initialize other special beans in specific context subclasses.
onRefresh();
//为事件传播器注册事件监听器. // Check for listener beans and register them.
registerListeners();
//初始化所有剩余的单态Bean. // Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件 // Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
//销毁以创建的单态Bean // Destroy already created singletons to avoid dangling resources.
destroyBeans();
//取消refresh操作,重置容器的同步标识. // Reset 'active' flag.
cancelRefresh(ex);
throw ex;
}
}
}
refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器;第8行ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();以后均为注册容器的信息源和生命周期事件;
源码片段2:上述代码第8行中的obtainFreshBeanFactory方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法,具体实现调用子类容器的refreshBeanFactory()方法
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
源码片段3:上述代码第3行中的refreshBeanFactory方法
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//如果已经有容器,销毁容器中的bean,关闭容器
destroyBeans();
closeBeanFactory();
}
try {
//创建IoC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等
customizeBeanFactory(beanFactory);
//调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean定义;
源码片段4:上述代码第13行中的loadBeanDefinitions(beanFactory)方法
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
……
//实现父类抽象的载入Bean定义方法
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容 器使用该读取器读取Bean定义资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的
//祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器
beanDefinitionReader.setResourceLoader(this);
//为Bean读取器设置SAX xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制
initBeanDefinitionReader(beanDefinitionReader);
//Bean读取器真正实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
}
//Xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取Bean定义资源的定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
//的Bean定义资源
reader.loadBeanDefinitions(configResources);
}
//如果子类中获取的Bean定义资源定位为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
//的Bean定义资源
reader.loadBeanDefinitions(configLocations);
}
}
//这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法
//该方法在ClassPathXmlApplicationContext中进行实现,对于我们
//举例分析源码的FileSystemXmlApplicationContext没有使用该方法
protected Resource[] getConfigResources() {
return null;
} ……
}
Bean生命周期
<bean id="batchAddLineActiona" class="nc.ui.pgrade_set.action.Pgradepd_setAddLineActiona" init-method="initUI" >
<property name="model"><ref bean="batchModel"/></property>
<property name="voClassName" value="nc.vo.pgradepd_set.Grade_setVO" />
<property name="exceptionHandler"><ref bean="exceptionHandler" /></property>
</bean>
概念Bean:一个组件;
Bean 的标识 (id 和 name),Id 属性具有唯一性,name 属性可以指定一个或者多个名称,第一名称默认为标识;
Bean 的 class属性,具体实现类(注意是实现类,不能是接口)的全路径包名.类名,在 Spring 配置文件中 class 属性指明 Bean 的来源,也就是 Bean 的实际路径,它指向一个实体类;
Bean 的作用域 scope:
- Singleton( 单例 ):
- 表示Spring IoC容器中只会存在一个共享的bean实例;
- non-Singleton(也称 prototype):
- 每次对该bean请求(将其注入到另一个bean中,或者调用getBean())时都会创建一个新的bean实例;
- request
- 针对每一个 HTTP 请求都会产生一个新的 Bean,请求结束时销毁
- 仅在基于web的Spring ApplicationContext情形下有效
- session
- 针对某个HTTP Session都会产生一个新的 Bean,HTTP Session最终被废弃时销毁
- 仅在基于web的Spring ApplicationContext情形下有效
- global session
- 全局的HTTP Session中(基于portlet的web应用),一个bean定义对应一个实例
- 仅在基于web的Spring ApplicationContext情形下有效;
概念Inversion of Control(控制反转):对组件对象的控制权转移给外部容器;对象的协作关系由容器来建立。(不再由对象自己来负责)
- Bean实例化
- 实例化的三种方式:构造器实例化、静态工厂方式实例化、实例工厂方式实例化;
- 属性注入
- OC注入,根据Bean的定义信息进行属性配置;
- 自身方法、Bean级生命周期接口和方法、容器级生命接口和方法、工厂后处理器接口偶和方法
-
BeanNameAware接口,调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID;
-
BeanFactoryAware接口,调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean);
-
BeanPostProcessors,调用postProcessBeforeInstantiation(Class<?>c,String beanName) 方法;
-
Spring配置文件中配置了init-method属性,自动调用其配置的初始化方法;
-
BeanPostProcessors,调用postProcessAfterInstantiation(Object bean,String beanName) 方法;
-
- 此时实例化的Bean就可以使用了,并且将保留在BeanFactory中直到它不在被使用;
- 销毁接口和方法
-
关闭容器时,DisposableBean接口,执行destroy()方法;
-
关闭容器时,Spring配置文件中配置了destroy-method属性,自动调用其配置的销毁方法;
-
依赖注入的几种方式
注解注入:Autowired、Resource、Qualifier、Service、Controller、Repository、Component;
Autowired是自动注入,代替setter和getter方法;Repository类似Autowired(按类型等),但是它是按照名称注入;Qualifier和Autowired配合使用,指定bean的名称(接口多实现时接口声明变量确定实现类);Service,Controller,Repository分别标记类是Service层类,Controller层类,数据存储层的类;一般注入的是DAO实现类和Service的接口;
set注入:定义私有变量,借助setXXX方法设置属性值;XML文件的<property>标签声明(name,value或ref);
构造器注入:带有参数的构造函数注入;
工厂注入:静态工厂的方法注入,实例工厂的方法注入;
四、AOP-面向切面编程
1.面向切面
Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
2.代理模式
代理模式:提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。
代理模式的关键点是:代理对象与目标对象,代理对象是对目标对象的扩展,并会调用目标对象;
代理模式优点:在目标对象已经实现的功能操作的基础上,增加额外的功能操作,拓展目标对象的功能;(不修改已经写好的代码,运用代理模式实现新功能)
静态代理模式:代理对象需要与目标对象实现一样的接口
// 接口
public interface IUserDao {
void save();
}
/**
* 接口实现, 目标对象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
/**
* 代理对象,静态代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
//代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//执行的是代理的方法
}
}
JDK动态代理模式:代理对象不需要实现接口,但目标对象必须实现接口
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfac
其中,在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader:
指定当前目标对象使用类加载器,获取加载器的方法是固定的;Class<?>[] interfaces:
目标对象实现的接口的类型,使用泛型方式确认类型;InvocationHandler h:
事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入;
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
// 目标对象
IUserDao target = new UserDao();
// 【原始的类型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.save();
}
}
Cglib动态代理:子类代理,目标对象不需要实现接口,根据目标对象构建一个子类对象从而实现对目标对象功能的扩展;
/**
* 目标对象,没有实现任何接口
*/
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor {
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance() {
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
/**
* 测试类
*/
public class App {
@Test
public void test() {
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
在Spring的AOP编程中:如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理;
3.概念
切面:与业务无关,却被业务模块共同调用的逻辑,封装起来,提高代码复用,利于维护等;
连接点:被拦截的方法;(拦截哪些方法,增加哪些功能)
切入点:对连接点进行拦截的定义;
通知:拦截到连接点后要执行操作或处理,如前置before、后置after-returning、异常after-throwing、最终after、环绕around通知五类;
引入:在不修改代码的前提下,为类添加新的属性或者方法;
织入:将切面应用于目标对象并且导致代理对象的创建;
4.使用场景
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务
5.实现方式
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
- 定义普通业务组件
- 定义切入点,一个切入点可能横切多个业务组件
- 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
1、基于xml配置Spring AOP;通过<aop:config>来配置
AOP配置要定义切面aspect、定义切点pointcut、定义连接通知方法等;
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" />
<bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" />
<bean id="timeHandler" class="com.xrq.aop.TimeHandler" />
<aop:config>
<aop:aspect id="time" ref="timeHandler">
<aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
<aop:before method="printTime" pointcut-ref="addAllMethod" />
<aop:after method="printTime" pointcut-ref="addAllMethod" />
</aop:aspect>
</aop:config>
</beans>
实现代码如下:
public class AopTest {
//main方法测试
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("aop.xml");
HelloWorld hw1 = (HelloWorld) ctx.getBean("helloWorldImpl1");
HelloWorld hw2 = (HelloWorld) ctx.getBean("helloWorldImpl2");
hw1.printHelloWorld();
System.out.println();
hw1.doPrint();
System.out.println();
hw2.printHelloWorld();
System.out.println();
hw2.doPrint();
}
}
//定义接口
public interface HelloWorld {
void printHelloWorld();
void doPrint();
}
//实现类1
public class HelloWorldImpl1 implements HelloWorld {
public void printHelloWorld() {
System.out.println("Enter HelloWorldImpl1.printHelloWorld()");
}
public void doPrint() {
System.out.println("Enter HelloWorldImpl1.doPrint()");
return;
}
}
//实现类2
public class HelloWorldImpl2 implements HelloWorld {
public void printHelloWorld() {
System.out.println("Enter HelloWorldImpl2.printHelloWorld()");
}
public void doPrint() {
System.out.println("Enter HelloWorldImpl2.doPrint()");
return;
}
}
//横切关注点--增加打印时间
public class TimeHandler {
public void printTime() {
System.out.println("CurrentTime = " + System.currentTimeMillis());
}
}
打印结果:
CurrentTime = 1446129611993
Enter HelloWorldImpl1.printHelloWorld()
CurrentTime = 1446129611993
CurrentTime = 1446129611994
Enter HelloWorldImpl1.doPrint()
CurrentTime = 1446129611994
CurrentTime = 1446129611994
Enter HelloWorldImpl2.printHelloWorld()
CurrentTime = 1446129611994
CurrentTime = 1446129611994
Enter HelloWorldImpl2.doPrint()
CurrentTime = 1446129611994
2、基于注解配置
目标对象对应的原代码,即被代理类代码中,增加注解@Service等;
在通知类代码中,增加注解@Component会被IOC容器管理、@Aspect声明是切面;在方法前增加@Before、@After等通知注解;
/**
* 被代理的目标类
*/
@Service("math")
public class Math{
......
}
/**
* 通知类,横切逻辑
*
*/
@Component
@Aspect
public class Advices {
@Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
public void before(JoinPoint jp){
System.out.println("----------前置通知----------");
System.out.println(jp.getSignature().getName());
}
@After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
public void after(JoinPoint jp){
System.out.println("----------最终通知----------");
}
}
配置文件:
<!--XML仅仅声明需要用到AOP即可-->
<aop:aspectj-autoproxy/>
<!--或者-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
注:1、2中的execution(* com.xrq.aop.HelloWorld.*(..))语句中,execution实际上是切点函数(AspectJ切点函数),除此之外还有within(包名.*)匹配指定的包的所有连接点,this(类名)该类名下所有方法,args(多个参数类型)参数个数以及类型能匹配上的方法,annotation特殊制定注解;
注:除以上知识还有AspectJ注解通知、零配置实现IOC和AOP;