Java-Spring-IOC和AOP应用篇

一、概念

        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 的创建由工厂模式实现,类图如下:

        其中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 的子类主要包含两个方面:

  1. ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息,它下面又有多个子类,其中最经常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 类。
  2. 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编程,程序员只需要参与三个部分:

  1. 定义普通业务组件
  2. 定义切入点,一个切入点可能横切多个业务组件
  3. 定义增强处理,增强处理就是在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;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值