Java程序员从笨鸟到菜鸟(三十六)Spring IoC和AoP

Spring的两大核心特性IoC和AoP, IoC(Inersion of Control),即控制反转;AoP(Aspact-OrientedProgramming),即面向切面编程

spring的优点:

  1. 降低了组件质检的耦合性,实现软件各层之间的解耦
  2. 可以提供更多服务,如事务处理,消息服务等
  3. 提供单例模式支持
  4. 提供了AoP技术,容易实现权限拦截
  5. 提供了众多辅助类,加快应用程序的开发
  6. 对主流框架提供了集成支持
  7. 独立于各种应用服务器

IoC(控制反转)

依赖注入DI(Dependency Injection)和控制反转IoC(Inversion of Control)是同一个概念,当某个对象需要另外一个对象协助时,在传统的设计g过程中,这些工作是调用者创建被调用者,但在Spring中,创建被调用者实例的工作不再由调用者来完成,此称为控制反转,而是通过Spring来完成,然后注入调用者,因此被称为依赖注入

通俗易懂的例子来阐述IoC

某一天小明生病了,但是不清楚自己到底得了什么病,就知道一些症状,这个时候自己决定去药店买药,药店很多种药,小明选择了其中一种药,付钱吃药,希望早点好起来。但是这个过程对病人来说太辛苦了,需要根据说明书,然后自己买药,这个时候想到了直接去看医生,医生做了检查,知道了症状以及病因,短短的几分钟,医生对症下药就能治好,省时又省力

在实例中医生充当了IoC的作用,根据症状病因,对症下药。小明是对象,药品就是所需要的外部资源。通过了医生,小明不用自己去找药品,而是通过医生给药品

从几个方面来理解IoC

问题一、参与者

  1. 对象
  2. IoC/DI容器
  3. 某个对象的外部资源

问题二、依赖关系及为什么需要依赖
对象依赖IoC容器,对象需要IoC容器来提供对象需要的外部资源

问题三、注入对象和内容
IoC容器注入对象,注入对象所需的外部资源

问题四、控制反转
IoC容器控制对象,主要是控制对象实例的创建,反转是相对于正向而言,例如A要使用C,首先是A直接去创建C的对象,A类主动去获取所需要的外部资源C,这种情况称为正向,反转:就是A不再主动获取C,而是被动等待,等待IoC容器获取一个C的实例,然后再反向注入 A中

问题五、控制反转和依赖注入
依赖注入和控制反转是对同一件事情的不同描述;依赖注入是从应用程序的角度描述,应用程序依赖容器创建并注入它所需要的外部资源控制反转是从容器的角度描述,容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源

依赖注入的三种方式:

  • 接口注入
  • Constructor注入
  • setter注入

三种方式比较

接口注入
具备入侵性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限

Setter注入
对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。

如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。

如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用

构造器注入
在构造期间完成一个完整的、合法的对象,所有依赖关系在构造函数中集中呈现,依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对不变的稳定状态,只有组件的创建者关心其内部的依赖关系

依赖注入

定义一个Car接口

public interface Car {
    public void run();
}

定义一个AudiCar类实现Car接口

public class AudiCar implements Car{
    private String name; // 汽车系列名称

    @Override
    public void run() {
        System.out.println("奥迪" + name + "... is running");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

测试类:

public class TestIoC {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ioc/applicationContext-ioc.xml");
        Car car = (Car)applicationContext.getBean("audiCar");
        car.run();
    }
}

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean id="audiCar" class="ssm.ioc.AudiCar">
        <property name="name" value="A6"/>
    </bean>
</beans>

运行结果:

奥迪A6... is running

Process finished with exit code 0

IoC

IoC容器负责接纳bean,并对bean进行管理。在Spring中,BeanFactory是IoC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖.BeanFactory提供了配制框架及基本功能,而ApplicationContext则增加了更多支持企业核心内容的功能。ApplicationContext完全由BeanFactory扩展而来,因而BeanFactory所具备的能力和行为也适用于ApplicationContext。

BeanFactoryFactoryBean的区别
BeanFactory是加载的容器,加载一些bean,而FactoryBean用于创建代理类

AoP(面向切面编程)

面向切面编程完善Spring的依赖注入,面向切面编程的目标就是分离关注点,面向切面编程(AoP)是对面向对象编程(OoP)的补充,面向对象编程将程序分解成各个层次的对象,面向切面编程将程序分解成各个切面,各步骤之间有良好的隔离性、源代码无关性

基本概念

  • 切面(aspect):通知和切入点共同组成了切面:时间、地点和要发生的故事
  • 通知(advice):切面必须要完成的工作,何时要执行这个工作
  • 目标(target):被通知的对象,在没有AoP之前,将不能只关注自己要做的事情
  • 代理(proxy):向目标对象应用通知后创建的对象
  • 连接点(joinpoint):目标对象的程序执行的某个特定位置,例如方法被调用、异常被抛出
  • 切点(pointcut):通知定义了切面要发生的故事和时间,切点定义了故事的发生地点,例如方法名称 1.使用正则表达式 2.使用AspectJ表达式
  • 引入(Introduction):允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)

通俗易懂的例子来阐述AoP

假如你是个公子哥,没啥人生目标,天天就是衣来伸手,饭来张口,整天就知道玩这一件事,每天一睁眼就是想去玩(必须做的事情),但是在玩之前,需要穿衣服、穿鞋子、叠被子等等事情,这些事情就是你的关注点,但是你只想吃饭之后然后玩,怎么办?这些事情交给仆人去做,在走到饭桌之前,有仆人给你穿衣服、鞋子、叠被子,然后你就开始吃饭去玩(一天的正事),然后回来之后又一系列的仆人开始帮你干活。

上述除关注点(吃饭和玩)之外,其它的每件事情都是一个点,这些点构成了一个面,这些事情由仆人(AoP)处理,AoP的好处就是你只需干你的正事(自身的业务代码实现),其它事情(事务管控相关内容)别人(AoP)帮你干,各人各司其职,灵活组合,达到一种可配置的,可插拔的程序结构,优点:简化了代码内容,将目标对象复杂的内容进行解耦,分离业务逻辑和横切关注点

4种实现AoP的方式

  1. 经典的基于代理AoP
  2. @AspectJ注解驱动的切面
  3. 纯POJO切面
  4. 注入式AspectJ切面
方式一、基于代理的AoP

Spring支持五种类型的通知

  • Before(前) org.apringframework.aop.MethodBeforeAdvice
  • After-returning(返回后) org.springframework.aop.AfterReturningAdvice
  • After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
  • Arround(周围) org.aopaliance.intercept.MethodInterceptor
  • Introduction(引入) org.springframework.aop.IntroductionInterceptor

以咱们每天都会重复去做的事情睡觉来演示面向切面编程
定义一个接口:Sleep

public interface Sleep {
    void sleep(); // 睡觉
}

睡觉是主要的事情,也就是关注点,但是除了睡觉之外,还有一些类似脱衣服等操作,如果把这些代码全加入到sleep当中,就违反了单一原则,这时候需要使用AoP,编写一个SleepHelper类,从类名就能猜到这是一个辅助类,用于处理睡觉之外的事情,用AoP属于就是说它应该是通知

public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("起床后......穿衣服");
    }

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("睡觉前.......脱衣服");
    }
}

然后在applicationContext.xml中进行配置

<bean id="sleepHelper" class="ssm.aop.SleepHelper"/>
<bean id="human" class="ssm.aop.Human"/>

创建通知的工作已经完成,接下来第二步就是进行相关配置,比较复杂

首先配置一个切点,常用的两种方法:1.使用正则表达式 2.使用AspectJ表达式
配置切点使用正则表达式,匹配了所有的sleep()

<!-- Spring使用org.springframework.aop.support.JdkRegexpMethodPointcut来定义正则表达式切点 -->
<bean id="sleepPointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="pattern" value=".*sleep"/>
</bean>

结合通知和切点

<!-- 切点仅仅是定义了故事发生的地点,还有故事发生的时间以及最重要的故事的内容,就是通知了,我们需要把通知跟切点结合起来 -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="advice" ref="sleepHelper"/>
    <property name="pointcut" ref="sleepPointCut"/>
</bean>

切入点和通知部署完成,接下来创建代理对象

<!-- 切入点和通知都配置完成,接下来该调用ProxyFactoryBean产生代理对象了 -->
<bean id = "humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="human"/>
    <property name="interceptorNames" value="sleepHelperAdvisor"/>
    <property name="proxyInterfaces" value="ssm.aop.Sleep"/>
</bean>

测试类

public class TestAoP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Sleep sleep = (Sleep)applicationContext.getBean("humanProxy");
        sleep.sleep();
    }
}

运行结果:

睡觉前.......脱衣服
睡觉.......
起床后......穿衣服

Process finished with exit code 0

也可以简化配置,Spring提供了自动代理的功能,能让切点和通知进行自动匹配,简化之后的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean id="human" class="ssm.aop.Human"/>
    <bean id="sleepHelper" class="ssm.aop.SleepHelper"/>

    <bean id="sleepAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="sleepHelper"/>
        <property name="pattern" value=".*sleep"/>
    </bean>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>

方式二:Aspect注解

需要pom.xml加入

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.1</version>
</dependency>

在睡觉辅助类上加入@Aspect注解

@Aspect // 标志切面
public class SleepHelper1 {
    public SleepHelper1() {

    }

    @Pointcut("execution(* *.sleep())") // 指定了切点
    public void sleepPoint() {}

    @Before("sleepPoint()") // 指定了运行时通知
    public void beforeSleep() {
        System.out.println("睡觉前.......脱衣服");
    }

    @AfterReturning("sleepPoint()")
    public void afterSleep() {
        System.out.println("起床后......穿衣服");
    }
}

配置文件

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <bean id ="human" class="ssm.aop.Human"/>
    <bean id="sleepHelper" class="ssm.aop.SleepHelper1"/>

    <!-- 自动扫描被aspectj注解的类 -->
    <aop:aspectj-autoproxy />
</beans>

测试类:

public class TestAoP {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop/applicationContext-aspect.xml");
        Sleep human = (Sleep)applicationContext.getBean("human");
        human.sleep();
    }
}

运行结果:

睡觉前.......脱衣服
睡觉.......
起床后......穿衣服

Process finished with exit code 0
方式三、纯POJO实现

之前代码不变,修改配置文件

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <bean id="human" class="ssm.aop.Human"/>
    <bean id="sleepHelper" class="ssm.aspect.SleepHelper"/>

    <aop:config>
        <aop:aspect ref="sleepHelper">
            <aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))"/>
            <aop:after method="afterSleep" pointcut="execution(* *.sleep(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

运行结果:

睡觉前.......脱衣服
睡觉.......
起床后......穿衣服

Process finished with exit code 0

IoC实例参考传送门https://blog.csdn.net/gloomy_114/article/details/68946881

AoP实现方式传送门https://blog.csdn.net/udbnny/article/details/5870076

源代码传送门https://github.com/tan0130/ssm

猜你喜欢

转载自blog.csdn.net/u013090299/article/details/80764145