SSM框架-Spring 面向切面编程

Spring 面向切面编程

OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP(面向切面编程)技术则恰好解决了这些不足,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

1.使用前后置通知统计所有方法的执行时间

AOP基本概念

  • Aspect(切面): 切面由切点 (Pointcut) 和增强/通知 (Advice) 组成,它既包括了横切逻辑的定义、也包括了连接点的定义;

  • Joint point(连接点):能够被拦截的地方:Spring AOP 是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点;

  • Pointcut(切点):具体定位的连接点,上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点;

  • Advice(通知/增强):表示添加到切点的一段逻辑代码,并定位连接点的方位信息。简单来说就定义了是干什么的,具体是在哪干;

  • Spring AOP提供了5种 Advice类型给我们,分别是:前置(Before)、后置(After)、返回(AfterReturning)、异常(AfterThrowing)、环绕(Around);

  • Target(目标对象):织入Advice的目标对象;

  • Weaving(织入):将增强/通知添加到目标类的具体连接点上的过程。

切点完整表达式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

参数 参数说明
modifiers-pattern 修饰符 可选 public private protected
declaring-type-pattern 方法的声明类型
name-patterm 方法名称类型,例 set* 则表示以set开头的所有的方法名称
param-pattern 参数匹配:(..) 表示任意多个参数,每个参数任意多个类型,(*,String) 表示两个参数,第一个是任意类型,第二个是String
throws-pattern 异常的匹配模式
ret-type-pattern 返回类型 必选 * 代表任意类型

五种通知

前置通知:在连接点前面执行,前置通知不会影响连接点的执行;

后置通知:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容;

异常返回通知:在连接点抛出异常后执行;

正常返回通知:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行;

环绕通知:最为强大的通知,它能够让你编写的逻辑将被通知的目标方法完全包裹起来。实际上就像在一个通知方法中同时编写前置通知和后置通知。定义通知的时候在通知方法中添加了入参ProceedingJoinPoint,这个参数是必须写的。因为需要在通知中使用ProceedingJoinPoint.proceed()调用目标方法。

任务代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package Educoder;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.text.SimpleDateFormat;

@Component("BlogAdvice")
@Aspect
public class BlogAdvice {
//定义切点
@Pointcut("execution(* Educoder.BlogService.service*(..))")
public void My() {
}
//定义前置通知,输出当前时间2019.1.1 00:00:00
/********** Begin **********/
@Before("execution(* Educoder.BlogService.service*(..))")
public void before() {
System.out.println("当前时间2019.1.1 00:00:00");
}
/********** End **********/
//定义后置通知,输出当前时间2019.1.1 00:01:00,执行耗时60000
/********** Begin **********/
@After("execution(* Educoder.BlogService.service*(..))")
public void After() {
System.out.println("当前时间2019.1.1 00:01:00,执行耗时60000");
}
/********** End **********/
}

2.使用环绕通知统计所有带参方法的执行时间

环绕通知和前后通知的区别

  • 目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的;

  • 环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用。

    任务代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package Educoder;

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;

    import org.springframework.stereotype.Component;

    import java.text.ParseException;
    import java.text.SimpleDateFormat;

    @Component("BlogAdvice")
    @Aspect
    public class BlogAdvice {
    //定义切点
    @Pointcut("execution(* Educoder.BlogService.service*(String))")
    public void My() {
    }


    //定义环绕通知,实现统计目标类所有带参方法运行时长
    @Around("My()")
    public void around(ProceedingJoinPoint point) throws ParseException, InterruptedException {
    /********** Begin **********/
    try {
    point.proceed();
    } catch (Throwable throwable) {
    throwable.printStackTrace();
    }
    System.out.print("程序a执行耗时60000");
    /********** End **********/

    }

    }

第二关有点问题,我看评论区,大家都不知道怎么让Pointcut只锁定输出业务管理一的,网上也没找到答案,所以要在命令行里面打开BlogService类把业务功能2的输出语句注释掉。

3.AOP实现原理-JDK动态代理

代理模式(Proxy)

代理模式就是给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,在不改变目标对象方法的情况下对方法进行增强。

AOP实现的两种方式

切面类中的方法会根据相应的策略对目标对象进行增强,Spring AOP使用JDK动态代理或CGLIB为给定目标对象创建代理。如果要代理的目标对象实现了至少一个接口,则AOP默认使用JDK动态代理,否则使用CGLIB代理。

JDK动态代理步骤

Proxy.newInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  • 第一个参数ClassLoader loader:类加载器,一般写当前类;
  • 第二个参数Class<?>[] interfaces:代理类所需要实现的接口;
  • 第三个参数InvocationHandler h:处理类,一般写匿名类。

步骤

  • 定义一个java.lang.reflect.InvocationHandler接口的实现类,重写invoke方法;
  • InvocationHandler对象作为参数传入java.lang.reflect.ProxynewProxyInstance方法中;
  • 通过调用java.lang.reflect.ProxynewProxyInstance方法获得动态代理对象;
  • 通过代理对象调用目标方法。

任务代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package educoder;

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

public class MyProxy {
public static UserService createUserService() {
//请在以下提示框内填写你的代码
/**********Begin**********/
//目标类
UserServiceImpl userserviceImpl = new UserServiceImpl();
//切面类
Authority authority = new Authority();
//代理类:将目标类与切面类结合
UserService proxyUserService = (UserService) Proxy.newProxyInstance(MyProxy.class.getClassLoader(), userserviceImpl.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目标方法执行前
authority.before();
//放行目标方法
Object invoke = method.invoke(userserviceImpl, args);
return invoke;
}
});
//返回代理对象
return proxyUserService;

/**********End**********/
}
}

4.AOP实现原理-CgLib动态代理

CGLIB代理模拟AOP实现主要是采用字节码增强框架cglib,在运行时创建目标类的子类,从而对目标类进行增强。

CGLIB动态代理步骤

  • 定义一个org.springframework.cglib.proxy.MethodInterceptor接口的实现类,重写intercept方法;
  • 获取org.springframework.cglib.proxy.Enhancer类的对象;
  • 分别调用Enhancer对象的setSuperclasssetCallback方法,使用create方法获取代理对象;
  • 通过代理对象调用目标方法。

    任务代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package educoder;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;

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

    public class MyProxy {
    public static UserServiceImpl createUserService(){
    //请在以下提示框内填写你的代码
    /**********Begin**********/
    //目标类
    UserServiceImpl user=new UserServiceImpl();
    //切面类
    Authority authority=new Authority();
    //cglib核心类
    Enhancer enhancer=new Enhancer();
    enhancer.setSuperclass(user.getClass());
    enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    //目标方法执行前
    authority.before();
    //放行目标方法
    Object invoke = methodProxy.invokeSuper(o,objects);
    return invoke;
    }
    });
    //获取代理对象
    UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
    //返回代理对象
    return proxy;
    /**********End**********/
    }
    }

    实训地址:头歌编程