spring aop

约定编程~Spring AOP

1. 约定编程

1.1 约定

我们先准备一下测试的环境

先写一个服务接口和服务实现

public interface HelloService {
    void sayHello(String name);
}
public class HelloServiceImpl implements HelloService{
    @Override
    public void sayHello(String name) {
        if (name == null || name.trim().equals("")){
            throw new RuntimeException("the name is empty");
        }
        System.out.println("hello " + name +" !!!");
    }
}

这个服务很简单,就是,名字不为null和名字不为空时打印hello + name 否则抛出异常

定义一个拦截器

/**
 * 拦截器接口
 */
public interface Interceptor {
    //事前方法
    boolean before();

    //事后方法
    void after();
    /**
     * 取代原有的事件方法
     * @param invocation --回调参数,可以通过它来回调原有事件的方法
     * @return 原有事件返回的对象
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    Object around(Invocation invocation) throws InvocationTargetException,IllegalAccessException;

    //是否返回方法,事件没有发生异常的情况下调用
    void afterReturning();

    //事后异常方法,当事件发生异常后执行
    void afterThrow();

    //是否调用around方法取代原有方法
    boolean useAround();
}

接口的定义也完成了,后面的工作就是把这些方法织入流程,这就需要约定了。

先给出around(Invocation invocation)的源码

public class Invocation {
    private Object[] params;
    private Method method;
    private Object target;//被代理的对象

    public Invocation(Object[] params, Method method, Object target) {
        this.params = params;
        this.method = method;
        this.target = target;
    }
    
    public Object proceed() throws InvocationTargetException,IllegalAccessException{
        return method.invoke(target,params);
    }
	/*get set ...*/
}

通过反射去执行被代理对象的方法

实现这个拦截器

public class MyInterceptor implements Interceptor{

    @Override
    public boolean before() {
        System.out.println("before .... ");
        return useAround();
    }

    @Override
    public boolean useAround() {
        return true;
    }
    
    @Override
    public void after() {
        System.out.println("after .... ");
    }

    @Override
    public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("around before.... ");
        Object proceed = invocation.proceed();
        System.out.println("around after.... ");
        return proceed;
    }

    @Override
    public void afterReturning() {
        System.out.println("afterReturning.... ");
    }

    @Override
    public void afterThrow() {
        System.out.println("afterThrow.... ");
    }
}

我们需要一个代理对象-ProxyBean来执行这些流程,ProxyBean怎么实现后面再说,实现之后我们需要他能执行以下流程

  1. 使用 proxy调用方法时会先执行拦截器的 before方法
  2. 如果拦截器的 useAround方法返回true,则执行拦截器的 around方法,而不调用 target,对象对应的方法, around方法的参数Invocation对象存在一个 proceed方法,它可以调用 target对象对应的方法;如果 useAround方法返回 false,则直接调用 target对象的事件方法
  3. 无论怎么样,在完成之前的事情后,都会执行拦截器的 after方法
  4. 在执行 around方法或者回调 target I的事件方法时,可能发生异常,也可能不发生异常。如果发生异常,就执行拦截器的 afterThrowing方法,否则就执行 afterReturning方法。

下图是整个约定流程

image-20201103100829869

这边的服务和拦截器都已经定义好了,那么问题就是怎么把这些拦截器里面的方法织入约定的流程呢,也就是ProxyBean是怎么实现的?

1.2 ProxyBean的实现

JDK提供了一个可以创造代理对象的方法,方法如下

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws ...
  • ClassLoader loader 类加载器

  • Class<?>[] interfaces 把代理对象绑定在那些接口下

  • InvocationHandler 类中有一个invoke方法可以实现代理对象的逻辑

    /**
    * Processes a method invocation on a proxy instance and returns
    * the result.  This method will be invoked on an invocation handler
    * when a method is invoked on a proxy instance that it is
    * associated with.
    *proxy 代理对象
    *method 被代理的方法
    *args 被代理方法的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args)
    

那么ProxyBean使用一个方法获取到proxy的实例

 public static Object getProxyBean(Object target, Interceptor interceptor)

该方法就有两个约定

  1. target对象必须存在接口
  2. 返回一个proxy对象,可以根据target实现的接口对他进行强制转换

ProxyBean就需要有如下两个条件

  1. 需要有参数target(必须实现接口)和Interceptor(定义了流程)
  2. 同时要继承InvocationHandler实现invoke方法,这样当我们获取到代理对象执行方法是就可以在Invoke中实现约定的流程

ProxyBean的具体实现

public class MyProxyBean implements InvocationHandler {
    private Object target;//保存被代理的对象
    private Interceptor interceptor;//保存需要织入的流程

    /**
     * 绑定代理对象
     * @param target 被代理的对象
     * @param interceptor 拦截器
     * @return 代理对象
     */
    public static Object getProxyBean(Object target, Interceptor interceptor){
        MyProxyBean myProxyBean = new MyProxyBean();
        myProxyBean.target = target;
        myProxyBean.interceptor = interceptor;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),myProxyBean);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        boolean hasException = false;//异常标志位
        Object returnObj = null;
        Invocation invocation  = new Invocation(args,method,target);//可以通过它来实现被代理对象的方法的调用
        try {
            if (this.interceptor.before()){//需要
                returnObj = this.interceptor.around(invocation);//执行around取代原有的方法
            }else {
                returnObj = method.invoke(target,method);//直接调用方法
            }
        }catch (Exception e){
            hasException = true;
        }
        this.interceptor.after();
        if (hasException){
            this.interceptor.afterThrow();//方法执行后存在异常时调用
        }else {
            this.interceptor.afterReturning();//方法执行后存在异常时调用
            return returnObj;
        }
        return null;
    }
}

测试代码(测试代码中就是按照之前getProxyBean的约定编程的,只要按照约定编程就能将代码织入事先约定的流程)

@Test
void proxyTest(){
    HelloService helloService = new HelloServiceImpl();
    MyInterceptor myInterceptor = new MyInterceptor();
    HelloService proxyHelloService = (HelloService)MyProxyBean.getProxyBean(helloService, myInterceptor);
    proxyHelloService.sayHello("yogurt");
    System.out.println("====================name is null================================");
    proxyHelloService.sayHello(null);
}

测试结果

image-20201103110112619

1.3 总结

测试的结果告诉我们,提供一定的约定规则后,只要按照约定编程就能实现某些功能。spring Aop也是如此,它能通过与我们的约定,把对应的方法通过动态代理技术织入约定的流程。所以掌握spring Aop的根本就是掌握其对我们的约定规则。

2.AOP概念

2.1 为什么使用AOP

使用AOP主要是将一些与业务逻辑无关的代码复用,开发者可以集中关注于业务逻辑的开发

比如说,操作日志、安全检测、事务处理等等。

2.2 AOP术语以及流程

AOP术语

  1. 连接点 joint point

    指被拦截的对象,但是spring只支持方法,这里指的是特定的方法,比如HelloService的sayHello()就是一个连接点,Aop通过动态代理将它织入对应的流程

  2. 切点 point cut

    由于我们的切面不单单指一个方法,可能需要织入流程的方法有很多,所以可以切点可以通过正则表达式去匹配这些方法

  3. 通知 advice

    就是按照约定的流程下的方法,分为前置通知( before advice)、后置通知( afteradvice)、环绕通知( around advice)、事后返回通知(after Returning advice)和异常通知(after Throwing advice),它会根据约定织入流程中,需要弄明白它们在流程中的顺序和运行 的条件

  4. 目标对象 target

    被代理的对象,例如HelloServiceImpl

  5. 引入 introduction

    指引入新的类和其方法,增强现有Bean的功能。

  6. 织入 weaving

    它是一个过程,过程就是通过动态代理技术为原有的服务生成代理对象,将与切点匹配的连接点拦截,并按照约定将各类通知织入流程

  7. 切面 aspect

    它是一个可以定义切点,通知和引入的内容,spring AOP可以通过它来增强bean的功能或者将对应的方法织入流程

下图解释了spring aop的流程约定

image-20201103113710470

3.AOP的使用

3.1 切面、切点、通知

需求:UserServiceImpl的printUser方法织入流程

UserService和UserServiceImpl

public interface UserService {
    void printUser(User user);
}
@Service
public class UserServiceImpl implements UserService{
    @Override
    public void printUser(User user) {
        System.out.println(user.toString());
    }
}

定义切面、切点、通知

@Aspect
@Component
public class MyAspect {
    @Pointcut("execution(* com.yogurt.chapter4.aop.service.UserServiceImpl.printUser(..))")
    public void pointCut(){

    }
    @Before("pointCut()")
    public void before(){
        System.out.println("before ....");
    }
    @After("pointCut()")
    public void after(){
        System.out.println("after ....");
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before ....");
        Object result = pjp.proceed();
        System.out.println("around after ....");
        return result;
    }
    @AfterReturning("pointCut()")
    public void afterReturn(){
        System.out.println("afterReturn ....");
    }

    @AfterThrowing("pointCut()")
    public void afterThrowing(){
        System.out.println("afterThrowing ....");
    }
}

使用@PointCut来定义切点他的配置值是一个正则式

execution(* com.yogurt.chapter4.aop.service.UserServiceImpl.printUser(..))

  1. execution表示在执行的时候拦截正则匹配的方法
  2. *表示返回任意类型
  3. com.yogurt.chapter4.aop.service.UserServiceImpl类的全限定名
  4. printUser(..)表示任意参数的printUser方法

对于这个正则式而言还可以使用AspectJ指示器

image-20201103202458522

上面的正则式也可以这样表达

@Pointcut("execution(* com.yogurt.chapter4.*.*.*.printUser(..)) && bean(userServiceImpl)")

bean中的内容表示对Spring Bean的限定

3.2 环绕通知

@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before ....");
    Object result = pjp.proceed();
    System.out.println("around after ....");
    return result;
}

环绕通知非常强大,他的参数ProceedingJoinPoint pjp是一个被spring封装过的对象,他的proceed方法可以回调原有目标对象的方法,断点调试可以知道这个对象持有tagart、method、param,这样可以通过反射来调用目标对象的方法了。

3.3 引入

前面的例子只要传入的user为null会抛出异常,执行afterThrowing方法,那么我现在的需求是引入一个接口增强UserService使传过来的user为空时不执行printUser方法。

定义一个UserValidator接口和对应的实现类

public interface UserService {
    void printUser(User user);
}
public class UserValidatorImpl implements UserValidator{
    @Override
    public boolean validate(User user) {
        return user != null;
    }
}

将接口加入切面

public class MyAspect {

    @DeclareParents(value = "com.yogurt.chapter4.aop.service.UserServiceImpl+"
                    ,defaultImpl = UserValidatorImpl.class)
    private UserValidator userValidator;

这里使用 @DeclareParents引入新的接口来增强服务有两个配置项

  • value 指向你要增强的目标对象,全限定名加+号
  • defaultImpl 指向引入接口的默认实现类

测试

@Test
void test2() {
    User user = new User();
    user.setUserName("yogurt");
    user.setNote("always");
    //强制转换
    UserValidator userValidator = (UserValidator) userService;
    //判断user是否为空
    if (userValidator.validate(user)) {
        userService.printUser(user);
    }
}

userService能强制转换为UserValidator实例的原理:

jdk的proxy类的newProxyInstance方法可以传入多个接口

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

让该代理对象挂在这两个接口下,且这些接口代理类能自由切换并使用他们的方法

原文地址:https://www.cnblogs.com/iandf/p/13922533.html