SpringBoot之AOP面向切面编程总结

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

一、什么是AOP

面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称作“横切关注点(Cross-cutting concerns, Horizontal concerns)”。

日志功能即是横切关注点的一个典型案例,因为日志功能往往横跨系统中的每个业务模块,即“横切”所有有日志需求的类及方法体。而对于一个信用卡应用程序来说,存款、取款、帐单管理是它的核心关注点,日志和持久化将成为横切整个对象结构的横切关注点。

切面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与切面相关的编程概念还包括元对象协议、主题(Subject)、混入(Mixin)和委托(Delegate)。

二、AOP中的相关概念

Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

三、Spring AOP

Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。在Java中动态代理有两种方式:JDK动态代理和CGLib动态代理

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。


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

/**
 * <p>
 *
 * @author leone
 * @since 2018-11-09
 **/
public class JdkProxy {

    interface IUserService {
        Integer delete(Integer userId);
    }

    static class UserServiceImpl implements IUserService {
        @Override
        public Integer delete(Integer userId) {
            // 业务
            System.out.println("delete user");
            return userId;
        }
    }

    // 自定义InvocationHandler
    static class UserServiceProxy implements InvocationHandler {
        // 目标对象
        private Object target;

        public UserServiceProxy(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------方法调用前---------");
            //执行相应的目标方法
            Object result = method.invoke(target, args);
            System.out.println("------方法调用后---------");
            return result;
        }
    }

    public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        // 创建调用处理类
        UserServiceProxy handler = new UserServiceProxy(userService);
        // 得到代理类实例
        IUserService proxy = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),
                                 new Class[]{IUserService.class}, handler);
        // 调用代理类的方法
        Integer userId = proxy.delete(3);
        System.out.println(userId);
    }

}

而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * <p>
 *
 * @author leone
 * @since 2018-11-09
 **/
public class CglibProxy {

    static class UserService implements MethodInterceptor {

        private Object target;

        /**
         * 业务方法
         *
         * @param userId
         * @return
         */
        public Integer delete(Integer userId) {
            System.out.println("delete user");
            return userId;
        }

        /**
         * 利用Enhancer类生成代理类
         *
         * @param target
         * @return
         */
        public Object getInstance(Object target) {
            this.target = target;
            // 创建加强器,用来创建动态代理类
            Enhancer enhancer = new Enhancer();
            // 为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
            enhancer.setSuperclass(target.getClass());
            // 设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
            enhancer.setCallback(this);
            // 创建动态代理类对象并返回
            return enhancer.create();
        }


        /**
         * @param o
         * @param method
         * @param objects
         * @param methodProxy
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
                                throws Throwable {
            System.out.println("------方法调用前---------");
            Object object = methodProxy.invokeSuper(o, objects);
            System.out.println("------方法调用后---------");
            return object;
        }

    }

    public static void main(String[] args) {
        UserService userService = new UserService();
        UserService proxy = (UserService) userService.getInstance(userService);
        Integer userId = proxy.delete(2);
        System.out.println(userId);
    }

}

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,可以强制使用CGLIB实现AOP
2、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

四、SpringBoot引入AOP依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

五、Advice的主要类型

六、切点表达式

1.通配符

2.逻辑运算符

表达式可由多个切点函数通过逻辑运算组成

例如 execution(* chop(..)) && target(Horseman)  表示Horseman及其子类的chop方法

例如 execution(* chop(..)) || args(String)  表示名称为chop的方法或者有一个String型参数的方法

例如 execution(* chop(..)) and !args(String)  表示名称为chop的方法但是不能是只有一个String型参数的方法

表示满足某一匹配模式的所有目标类方法连接点。如execution(* save(..))表示所有目标类中的 save()方法。

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如下是execution表达式的语法

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

execution(<修饰符> <返回类型> <类路径> <方法名>(<参数列表>) <异常模式> )

七、切点函数

八、spring-boot-aop 实战

1.在类上使用 @Component 注解把切面类加入到IOC容器中
2.在类上使用 @Aspect 注解使之成为切面类

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 描述一个切面类
 *
 * @author leone
 * @since 2018-06-21
 **/
@Slf4j
@Aspect
@Component
public class AopConfig {

    /**
     * 1.通配符
     * [*]  匹配任意字符,但只能匹配一个元素
     * <p>
     * [..] 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
     * <p>
     * [+]  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
     * <p>
     * 切点表达式分为 修饰符  返回类型  包路径  方法名  参数
     * <p>
     * 2.切点表达式
     * <p>
     * 3.逻辑运算符
     * 表达式可由多个切点函数通过逻辑运算组成
     * ** && 与操作,求交集,也可以写成and
     * <p>
     * 例如 execution(* chop(..)) && target(Horseman)  表示Horseman及其子类的chop方法
     * <p>
     * ** || 或操作,任一表达式成立即为true,也可以写成 or
     * <p>
     * 例如 execution(* chop(..)) || args(String)  表示名称为chop的方法或者有一个String型参数的方法
     * <p>
     * ** ! 非操作,表达式为false则结果为true,也可以写成 not
     * <p>
     * 例如 execution(* chop(..)) and !args(String)  表示名称为chop的方法但是不能是只有一个String型参数的方法
     */
    @Pointcut("execution(* com.leone.boot.aop.service.*.*(..))")
    public void pointCut() {
    }

    /**
     * 环绕通知在 target 开始和结束执行
     *
     * @param point
     * @return
     */
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint point) {
        long start = System.currentTimeMillis();
        String methodName = point.getSignature().getName();
        log.info("around method name: {} params: {}", methodName, Arrays.asList(point.getArgs()));
        try {
            log.info("around end time: {}", (System.currentTimeMillis() - start) + " ms!");
            return point.proceed();
        } catch (Throwable e) {
            log.error("message: {}", e.getMessage());
        }
        return null;
    }

    /**
     * 前置通知在 target 前执行
     *
     * @param joinPoint
     */
    // @Before("@annotation(com.leone.boot.aop.anno.AopBefore)")
    // @Before("within(com.leone.boot.aop.controller.*)")
    // @Before("@within(org.springframework.web.bind.annotation.RestController)")
    // @Before("target(com.leone.boot.aop.controller.UserController)")
    @Before("@target(com.leone.boot.aop.anno.ClassAop) && @annotation(com.leone.boot.aop.anno.AopBefore)")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        log.info("before inform method name: {} param: {}", methodName, args);
    }

    /**
     * 后置通知在target后执行
     *
     * @param joinPoint
     */
    @After("@args(org.springframework.stereotype.Component) && 
            execution(* com.leone.boot.aop.controller.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        log.info("after inform method name : {} param: {}", methodName, args);
    }

    /**
     * 后置返回在target返回后执行
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "within(com.leone.boot.aop.controller.*)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterReturning inform method name: {} return value: {}", methodName, result);
    }

    /**
     * 后置异常通知在target异常后执行
     *
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "args(com.leone.boot.common.entity.User) && 
            execution(* com.leone.boot.aop.controller.*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterThrowing inform method name: {} exceptions: {}" + methodName, ex);
    }
}

测试类:

import com.leone.boot.aop.anno.AopBefore;
import com.leone.boot.aop.anno.ClassAop;
import com.leone.boot.aop.interf.UserService;
import com.leone.boot.common.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author leone
 * @since 2018-06-21
 **/
@Slf4j
@ClassAop
@RestController
@RequestMapping("/api")
public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }


    @AopBefore
    @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
    public User findOne(@PathVariable Long userId) {
        return userService.findOne(userId);
    }

    @AopBefore
    @RequestMapping("/user")
    public User save(User user) {
        return user;
    }
}

 

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看