zoukankan      html  css  js  c++  java
  • Spring:AOP

    什么是AOP?

    AOP:Aspect Oriented Programming,中文翻译为”面向切面编程“。面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码
    AOP把软件的功能模块分为两个部分:核心关注点横切关注点。业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为横切关注点。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离
    使用AOP有诸多好处,如:
    1.集中处理某一关注点/横切逻辑
    2.可以很方便的添加/删除关注点
    3.侵入性少,增强代码可读性及可维护性

    1 在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用aop来做。
    2     AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能,日志代码往往横向地散布在所有对象层次中,而且它与对象的核心代码往往毫无关系。除了日志还有安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
    3   AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
    4   使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
    View Code

    代码实现

    本文基于SpringBoot编写了一个简单的Spring AOPDemo。

    maven依赖添加如下
    <!--引入SpringBoot的Web模块-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
     
    <!--引入AOP依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    注意:在完成了引入AOP依赖包后,不需要去做其他配置。AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy,不需要在程序主类中增加@EnableAspectJAutoProxy来启用。

    web请求入口

    对应系统纵向的核心业务模块。

    @RestController
    public class RedisController {
    
        @Reference(version = "1.0.0")
        private GoodsInfoService goodsInfoService;
        @Autowired
        private CacheService cacheService;
    
        @GetMapping("/detail")
        public GoodsInfo detail(Integer id){
            GoodsInfo goods = (GoodsInfo)cacheService.getCacheByKey("mall_goods:"+id);
            if(null == goods){
                goods = goodsInfoService.getById(id);
                cacheService.setCacheToRedis("mall_goods:"+id,goods,1800);
            }
            return goods;
        }
    }
    

      

    定义切面类

    在类上添加@Aspect 和@Component 注解即可将一个类定义为切面类。
    @Aspect 注解 使之成为切面类
    @Component 注解 把切面类加入到IOC容器中
    @Component
    @Aspect
    public class WebAspect {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(WebAspect.class);
    
        @Autowired
        private ExceptionHandler exceptionHandler;
    
        private final String pointcut = "execution(* com.mall.web.controller.*.*(..))";
    
        @Pointcut(pointcut)
        public void executeService(){}
    
    
        /**
         * AOP 前置通知
         *
         * @param joinPoint
         */
        @Before("executeService()")
        public void doBeforeAdvice(JoinPoint joinPoint){
            ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            LOGGER.info("【AOP前置通知】url:" + request.getRequestURI()+", " +
                    "ip:" + DeviceUtils.getIpAddr(request)+ ", " +
                    "method:" + request.getMethod()+ ", " +
                    "class_method:" + joinPoint.getSignature().getDeclaringTypeName() + "."
                    + joinPoint.getSignature().getName());
        }
    
    
        /**
         * 环绕通知
         *
         * @param pjp
         */
        @Around("executeService()")
        public Object doAroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
            StopWatch clock = new StopWatch();
            //返回的结果
            Object result = null;
            //方法名称
            String className = pjp.getTarget().getClass().getName();
            String methodName = pjp.getSignature().getName();
    
            try {
                clock.start();
                //处理入参特殊字符和sql注入攻击
                checkRequestParam(pjp);
                //执行访问接口操作
                result = pjp.proceed();
                clock.stop();
    
                //后置通知
                if (!methodName.equalsIgnoreCase("initBinder")) {
                    long constTime = clock.getTotalTimeMillis();
                    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                    HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
                    LOGGER.info("【AOP环绕通知】 接口[" + request.getRequestURI() + "]" + "-" + "[" + request.getMethod() + "]" + " 请求耗时:" + constTime + "ms");
                }
            }catch (Exception e){
                LOGGER.error(e.getMessage(),e);
                result = exceptionHandler.exceptionGet(e);
            }
            return result;
        }
    
        @AfterReturning(value = "executeService()", returning = "result")
        public void doAfterReturning(Object result) {
            LOGGER.info("【AOP后置通知】 返回值:" + result);
        }
    
        /**
         * 检查防SQL注入和非法字符
         * @param pjp
         */
        private void checkRequestParam(ProceedingJoinPoint pjp){
            Object[] args = pjp.getArgs();
            RequestFacade  facade= (RequestFacade) args[0];
            String str = (String) facade.getAttribute("RequestStr");
            if (!IllegalStrFilterUtils.sqlStrFilter(str)) {
                LOGGER.info("访问接口:" + pjp.getSignature() + ",输入参数存在SQL注入风险!");
                throw new DescribeException(ExceptionEnum.REQUEST_ERROR);
            }
            if (IllegalStrFilterUtils.isIllegalStr(str)) {
                LOGGER.info("访问接口:" + pjp.getSignature() + ",输入参数含有非法字符!");
                throw new DescribeException(ExceptionEnum.REQUEST_ERROR);
            }
        }
    }

    测试

    请求http://192.168.0.101:9001/detail?id=1

    环绕通知

    这里单独讲解一下功能强大的环绕通知,环绕通知可以将你所编写的逻辑将被通知的目标方法完全包装起来。我们可以使用一个环绕通知来代替之前多个不同的前置通知和后置通知。如下所示,前置通知和后置通知位于同一个方法中,不像之前那样分散在不同的通知方法里面。

    /**
    * @description  使用环绕通知
    */
    @Around("BrokerAspect()")
    public void doAroundGame(ProceedingJoinPoint pjp) throws Throwable {
        try{
            System.out.println("方法执行前!");
            pjp.proceed();
            System.out.println("方法执行后");
        }
        catch(Throwable e){
            System.out.println("异常通知:*****!");
        }
    }

    环绕通知接受ProceedingJoinPoint作为参数,它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,需要调用ProceedingJoinPoint的proceed()方法。当你不调用proceed()方法时,将会阻塞被通知方法的访问。

    切点表达式用于描述切点的位置信息,在此简单描述文中切点表达式的含义。
    推荐一个切点表达式总结的博客https://www.cnblogs.com/zhangxufeng/p/9160869.html

    转载自:https://www.cnblogs.com/LemonFive/p/10983875.html

  • 相关阅读:
    Android layout属性大全
    如何看懂Code128条形码
    二维码
    在线条形码生成器
    GS1已分配给国家(地区)编码组织的前缀码
    POJ 3321 Apple Tree DFS序+fenwick
    bootstrap之WaitForIdle&amp;&amp;Clear
    ubuntu14操作系统chrome标签和书签乱码解决
    动态规划-hdoj-4832-百度之星2014初赛第二场
    截取符合指数分布的一部分样本的理论与实验
  • 原文地址:https://www.cnblogs.com/mengY/p/12145934.html
Copyright © 2011-2022 走看看