仔细想一想SpringAOP也不难

文章已托管到GitHub,你们能够去GitHub查看阅读,欢迎老板们前来Star! 搜索关注微信公众号 【码出Offer】 领取各类学习资料!java

LOGO

SpringAOP


1、什么是AOP

AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块之间的耦合度,并有利于将来的可操做性和可维护性。git

应用场景: 如日志记录、审计、声明式事务、安全性和缓存等。github

2、场景分析

为了更好的理解AOP,渗透面向切面编程的思想。我这里举一个开发中很常见的例子。打印日志spring

首先,咱们要先理解什么是日志。express

日志: 日志是一种能够追踪某些软件运行时所发生事件的方法,软件开发人员能够向他们的代码中调用日志记录相关的方法来代表发生了某些事情。一个事件能够用一个可包含可选变量数据的消息来描述,此外,事件也有重要性的概念,这个重要性也能够被称为严重性级别(level)。开发者能够经过区分严重性级别来分析出想要的信息。编程

了解了什么是日志,那就要知道怎么打印日志,在哪里打印日志。打印日志,是引入依赖,使用日志工具来实现日志严重性级别和日志信息的打印。至于在哪里打印日志,固然是在咱们项目代码中的关键位置了。设计模式

这里咱们举一个例子在某一段代码的先后使用,有A、B、C三个方法,可是要在调用每个方法以前,要求打印一行日志“某方法被调用了!”,在调用每一个方法以后,也要求打印日志“某方法被调用完毕!”。缓存

通常人会在每个方法的开始和结尾部分都会添加一句日志打印吧,这样作若是方法多了,就会有不少重复的代码,显得很麻烦,这时候有人会想到,为何不把打印日志这个功能封装一下,而后让它能在指定的地方(好比执行方法前,或者执行方法后)自动的去调用呢?若是能够的话,业务功能代码中就不会掺杂这一下其余的代码,因此AOP就是作了这一类的工做的。安全

其工做原理为JDK动态代理和CGLIB动态代理,这里就先不展开动态代理的知识了!仍是先看AOP吧!微信

3、AOP术语

AOP做用: Spring的AOP编程便是经过动态代理类为原始类的方法添加辅助功能。

AOP术语 描述
链接点(Joinpoint) 链接点是程序类中客观存在的方法,可被Spring拦截并切入内容
切入点(Pointcut) 被Spring切入链接点
通知、加强(Advice) 能够为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。
目标对象(Target) 代理的目标对象
引介(Introduction) 一种特殊的加强,可在运行期为类动态添加Field和Method
织入(Weaving) 把通知应用到具体的类,进而建立新的代理类的过程
代理(Proxy) 被AOP织入通知后,产生的结果类
切面(Aspect) 由切点和通知组成,将横切逻辑织入切面所指定的链接点中

4、AOP术语解析

3.1 链接点

简单来讲,就是容许你使用通知、加强的地方。就好比在方法先后打印日志同样,咱们能够在一段代码的先后作操做,能够在一段代码前作操做,能够在一段代码后作操做,能够在一段代码抛异常以后作操做。因此,在这里这些能够操做的一行行代码(方法等等)都是一个个的链接点。

3.2 切入点

把一个个方法等代码看做链接点,那咱们从哪一个位置打印日志(加强操做)呢,而咱们挑选出须要打印日志的位置(也就是链接点的周围),就被称为切入点。

3.3 加强、通知

关于加强,在上面我已经说到过了,经过在切入点作的操做叫作加强,好比咱们要打印日志的话,日志就是一个加强功能操做。

3.4 目标对象

目标对象,简单来讲是要被加强的对象。

3.5 引介

容许咱们向现有的类添加新方法属性。这不就是把切面(也就是加强定义的新方法属性)用到目标对象中

3.6 织入

把加强应用到具体的目标对象中,进而建立新的代理类的过程

3.7 代理

代理就像咱们买房子的中介同样,也就是被AOP织入后产生的代理对象(中介对象),经过代理对象能够实现对咱们的目标对象加强

3.8 切面

切面是通知(加强)和切入点的结合。通知说明了干什么和何时干,而切入点说明了在哪干,这就是一个完整的切面定义。

5、SpringAOP开发步骤

5.1 pom.xml文件引入依赖

引入Spring核心依赖(spring-context)和SpringAOP依赖(spring-aspects)

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>
复制代码

5.2 建立spring-context.xml文件并添加schema

咱们须要在核心配置文件的头文件中添加aop和context的Schema

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">
</beans>
复制代码

5.3 定义原始类

模拟建立一个原始类

public interface UserService {
    public void save();
}

public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("save method executed...");
    }
}
复制代码

5.4 定义经过类

定义通知类(添加额外功能作加强)

public class MyAdvice implements MethodBeforeAdvice { //实现前置通知接口
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before advice executed...");
    }
}
复制代码

5.5 定义bean

配置bean对象

<!--原始对象-->
<bean id="us" class="com.mylifes1110.service.impl.UserServiceImpl" />

<!--辅助(加强)对象-->
<bean id="myAdvice" class="com.mylifes1110.advice.MyAdvice" />
复制代码

5.6 定义切入点造成切面

定义切入点(PointCut)并造成切面(Aspect)

<aop:config>
    <!--切点-->
    <aop:pointcut id="myPointCut" expression="execution(* save())" />
    <!--组装切面-->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>
复制代码

5.7 加强结果

使用的前置通知,结果加强的打印语句before advice executed...会在save()方法的前面打印save method executed...

6、通知

定义通知类,达到通知(加强)效果。实现不一样的接口并覆盖方法来达到不一样的通知效果

通知名称 接口 描述
前置通知 MethodBeforeAdvice接口 在目标对象的前面作加强
后置通知 AfterAdvice接口 注意:此接口内方法为空,后置默认使用第三种便可
后置通知 AfterReturningAdvice接口 在目标对象的后面作加强
异常通知 ThrowsAdvice 在目标对象发生异常后作加强
环绕通知 MethodInterceptor 在目标对象的先后作加强

7、通配切入点

根据表达式通配切入点

通配表达式顺序: 返回值类型 全类名.方法名(形参)

注意: 能够用..来实现通配形参列表,可使用*来通配方法名或返回值类型

<!-- public int com.mylifes1110.service.UserServiceImpl.queryUser(int,String,com.entity.User) -->
<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.mylifes1110.bean.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.mylifes1110.bean.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.mylifes1110.bean.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.mylifes1110.bean.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.mylifes1110..*.*(..))" />
复制代码

8、代理模式

8.1 代理模式

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。

功能分离
image-20190420002535800

8.2 代理模式应用场景模拟

经过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

场景模拟: 咱们在租赁房子须要走以下流程:

  1. 发布租房信息
  2. 带租户看房
  3. 签合同
  4. 收房租

可若是你是房东,生活中还有其余的杂事,怎么办呢?那你是否是能够把不重要不核心的环节交给中介(代理)去作呢?好比:发布租房信息和带租户看房。这两件事情交给中介去作就行了,咱们本身处理本身的事情,并且中间联系好租户咱们走比较重要的流程就能够,好比签合同、收房租。

8.3 建立Service接口和实现类

建立Service接口和实现类来模拟动态代理的应用场景

package com.mylifes1110.service;

public interface LandlordService {
    void rent();
}

package com.mylifes1110.service.impl;

import com.mylifes1110.service.LandlordService;

public class LandlordServiceImpl implements LandlordService {
    @Override
    public void rent() {
        System.out.println("签合同");
        System.out.println("收款");
    }
}
复制代码

8.4 静态代理

以下是静态代理设计模式解决代理问题

  • 静态代理流程,建立一个代理类并实现相同接口,建立实现类对象,在代理类中添加辅助功能并调用实现类对象核心方法,使得辅助功能和核心方法一块儿触发,完成代理
  • 静态代理的问题
    • 随着辅助功能的数量增长,代理类也会增长,致使代理类数量过多,不利于项目的管理。
    • 多个代理类的辅助功能代码冗余,修改时,维护性差。
静态代理
image-20190420004330551

建立静态代理类

package com.mylifes1110.advice1;

import com.mylifes1110.service.LandlordService;
import com.mylifes1110.service.impl.LandlordServiceImpl;

/** * @ClassName Proxy * @Description 静态代理类 * @Author Ziph * @Date 2020/7/19 * @Since 1.8 * @Version 1.0 */

public class Proxy implements LandlordService {
    private LandlordService landlordService = new LandlordServiceImpl();

    @Override
    public void rent() {
        // 代理事件
        System.out.println("发布消息");
        System.out.println("看房子");
        // 核心事件
        landlordService.rent();
    }
}
复制代码

静态代理实现

package com.mylifes1110.advice1;

import org.junit.Test;

public class ProxyTest {
    /** * @MethodName proxyTest * @Param [] * @Description 静态代理实现 * @Author Ziph * @Date 2020/7/10 */
    @Test
    public void proxyTest() {
        new Proxy().rent();
    }
    /** * 结果: * * 发布消息 * 看房子 * 签合同 * 收款 */
}
复制代码

8.5 JDK和CGLIB的选择

spring底层,包含了jdk代理和cglib代理两种动态代理生成机制。

基本规则是:目标业务类若是有接口则用JDK代理,没有接口则用CGLib代理。若是配置true:<aop:config proxy-target-class="true">,则用CGLIB代理

class DefaultAopProxyFactory{
    // 该方法中明肯定义了 JDK代理和CGLib代理的选取规则
    // 基本规则是:目标业务类若是有接口则用JDK代理,没有接口则用CGLib代理
    public AopProxy createAopProxy(){...}
}
复制代码

8.6 JDK动态代理

JDK动态代理是JDK底层基于接口实现的,也就是说咱们必须经过实现JDK动态代理的接口并覆盖方法来完成

package com.mylifes1110.advice2;

import com.mylifes1110.service.LandlordService;
import com.mylifes1110.service.impl.LandlordServiceImpl;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
    /** * @MethodName proxyTest * @Param [] * @Description JDK动态代理实现 * @Author Ziph * @Date 2020/7/10 */
    @Test
    public void proxyTest() {
        // 须要使用代理的目标
        LandlordService landlordService = new LandlordServiceImpl();
        // 匿名内部类
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 代理事件
                System.out.println("发布消息");
                System.out.println("看房子");
                return method.invoke(landlordService, args);
            }
        };

        // 动态构建代理类
        LandlordService proxy = (LandlordService) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),
                landlordService.getClass().getInterfaces(),
                handler);

        proxy.rent();

        /** * 结果: * * 发布消息 * 看房子 * 签合同 * 收款 */
    }
}
复制代码

8.7 CGLIB动态代理

CGLIB动态代理是Spring底层基于继承父类实现的,也就是说咱们必须经过继承所指定的父类并覆盖其方法来完成

package com.mylifes1110.advice3;

import com.mylifes1110.service.LandlordService;
import com.mylifes1110.service.impl.LandlordServiceImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;

import java.lang.reflect.Method;

/** * @ClassName ProxyTest * @Description CGLIB动态代理实现 * @Author Ziph * @Date 2020/7/19 * @Since 1.8 * @Version 1.0 */

public class ProxyTest {
    public static void main(String[] args) {
        final LandlordService landlordService = new LandlordServiceImpl();
        // 建立字节码加强对象
        Enhancer enhancer = new Enhancer();
        // 设置父类(等价于实现原始类接口)
        enhancer.setSuperclass(landlordService.getClass());
        // 设置回调函数(额外功能代码)
        enhancer.setCallback(new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                // 代理事件
                System.out.println("发布消息");
                System.out.println("看房子");
                Object ret = method.invoke(landlordService, args);
                return ret;
            }
        });
        // 建立动态代理类
        LandlordService proxy = (LandlordService) enhancer.create();
        proxy.rent();
        /** * 结果: * * 发布消息 * 看房子 * 签合同 * 收款 */
    }
}
复制代码

9、后处理器

9.1 后处理器的了解

  • spring中定义了不少后处理器;
  • 每一个bean在建立完成以前 ,都会有一个后处理过程,即再加工,对bean作出相关改变和调整;
  • spring-AOP中,就有一个专门的后处理器,负责经过原始业务组件(Service),再加工获得一个代理组件。
经常使用后处理器
系统后处理器

9.2 定义后处理器

/** * 定义bean后处理器 * 做用:在bean的建立以后,进行再加工 */
public class MyBeanPostProcessor implements BeanPostProcessor{

    /** * 在bean的init方法以前执行 * @param bean 原始的bean对象 * @param beanName * @return * @throws BeansException */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器 在init以前执行~~~" + bean.getClass());
        return bean;
    }
	/** * 在bean的init方法以后执行 * @param bean postProcessBeforeInitialization返回的bean * @param beanName * @return * @throws BeansException */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器 在init以后执行~~~" + bean.getClass());
        return bean;// 此处的返回是 getBean() 最终的返回值
    }
}
复制代码

9.3 配置后处理器

<!-- 配置后处理器,将对工厂中全部的bean声明周期进行干预 -->
<bean class="com.mylifes1110.beanpostprocessor.MyBeanPostProcessor"></bean>
复制代码

9.4 Bean的生命周期

建立Bean对象 -> 构造方法 -> Setter方法注入属性、知足依赖 -> 后处理器前置过程 -> init初始化 -> 后处理器后置过程 -> 构建完成 -> 销毁

9.5 动态代理源码(了解)

// AbstractAutoProxyCreator是 AspectJAwareAdvisorAutoProxyCreator的父类
// 该后处理器类中的 wrapIfNecessary方法即动态代理生成过程
AbstractAutoProxyCreator#postProcessAfterInitialization(Object bean, String beanName){
    if (!this.earlyProxyReferences.contains(cacheKey)) {
        // 开始动态定制代理
        return wrapIfNecessary(bean, beanName, cacheKey);
   	}
}
复制代码

在这里插入图片描述
相关文章
相关标签/搜索