• spring深入学习(四)-----spring aop


    AOP概述

    aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些逻辑是重复的,我无非是需要记录请求的时间以及响应时间,另外可能需要加上请求入参以及响应出参。这时候就可以把这些http服务看成切面,通过aop的方式在方法前和方法后去做点什么操作。

    aop的实现者有很多,包括AspectJ、Spring AOP等等,当然我们重点就放在Spring aop上了。

    在spring中,aop可以由jdk动态代理或cglib实现,这地方的示例可以看下本人之前的文章了动态代理

    jdk动态代理跟cglib有个重要的区别就是jdk动态代理的类必须要实现了某个接口,但是cglib则没这个要求。

    针对cglib,举例如下:

    spring aop简单示例

    业务代码

    @Component("knight")
    public class BraveKnight {
        public void saying(){
            System.out.println("我是骑士..(切点方法)");
        }
    }
    package com.cjh.aop2;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * 注解方式声明aop
     * 1.用@Aspect注解将类声明为切面(如果用@Component("")注解注释为一个bean对象,那么就要在spring配置文件中开启注解扫描,<context:component-scan base-package="com.cjh.aop2"/>
     *      否则要在spring配置文件中声明一个bean对象)
     * 2.在切面需要实现相应方法的前面加上相应的注释,也就是通知类型。
     * 3.此处有环绕通知,环绕通知方法一定要有ProceedingJoinPoint类型的参数传入,然后执行对应的proceed()方法,环绕才能实现。
     */
    @Component("annotationTest")
    @Aspect
    public class AnnotationTest {
        //定义切点
        @Pointcut("execution(* *.saying(..))")
        public void sayings(){}
        /**
         * 前置通知(注解中的sayings()方法,其实就是上面定义pointcut切点注解所修饰的方法名,那只是个代理对象,不需要写具体方法,
         * 相当于xml声明切面的id名,如下,相当于id="embark",用于供其他通知类型引用)
         * <aop:config>
            <aop:aspect ref="mistrel">
                <!-- 定义切点 -->
                <aop:pointcut expression="execution(* *.saying(..))" id="embark"/>
                <!-- 声明前置通知 (在切点方法被执行前调用) -->
                <aop:before method="beforSay" pointcut-ref="embark"/>
                <!-- 声明后置通知 (在切点方法被执行后调用) -->
                <aop:after method="afterSay" pointcut-ref="embark"/>
            </aop:aspect>
           </aop:config>
         */
        @Before("sayings()")
        public void sayHello(){
            System.out.println("注解类型前置通知");
        }
        //后置通知
        @After("sayings()")
        public void sayGoodbey(){
            System.out.println("注解类型后置通知");
        }
        //环绕通知。注意要有ProceedingJoinPoint参数传入。
        @Around("sayings()")
        public void sayAround(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("注解类型环绕通知..环绕前");
            pjp.proceed();//执行方法
            System.out.println("注解类型环绕通知..环绕后");
        }
    }
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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-4.3.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.cjh.aop2"/>
        <!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 -->
        <aop:aspectj-autoproxy/>
    </beans>

    测试:

    package com.cjh.aop2;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ac = new ClassPathXmlApplicationContext("com/cjh/aop2/beans.xml");
            BraveKnight br = (BraveKnight) ac.getBean("knight");
            br.saying();
        }
    }

    自动创建代理

    1、BeanNameAutoProxyCreator

    通过名字就可以看出来是干嘛的,可以根据bean的名字来创建自动代理,示例如下:

    业务类:

    public interface UserService {
        void print();
    }
    public class UserServiceImpl implements UserService {
        public void print(){
            System.out.println(getClass()+"#print");
        }
    }

    拦截器:

    public class MyMethodInterceptor implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println(getClass()+"调用方法前");
            Object ret=invocation.proceed();
            System.out.println(getClass()+"调用方法后");
            return ret;
        }
    }

    配置类:

    @Configuration
    public class AppConfig {
        //要创建代理的目标Bean
        @Bean
        public UserService userService(){
            return new UserServiceImpl();
        }
        //创建Advice或Advisor
        @Bean
        public Advice myMethodInterceptor(){
            return new MyMethodInterceptor();
        }
        //使用BeanNameAutoProxyCreator来创建代理
        @Bean
        public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
            BeanNameAutoProxyCreator beanNameAutoProxyCreator=new BeanNameAutoProxyCreator();
            //设置要创建代理的那些Bean的名字
            beanNameAutoProxyCreator.setBeanNames("userSer*");
            //设置拦截链名字(这些拦截器是有先后顺序的)
            beanNameAutoProxyCreator.setInterceptorNames("myMethodInterceptor");
            return beanNameAutoProxyCreator;
        }
    }

    测试:

    public class Main {
        public static void main(String[] args) {
            ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
            UserService userService= applicationContext.getBean(UserService.class);
            userService.print();
        }
    }

    自定义注解+BeanNameAutoProxyCreator

    基于上面说的,对一些http接口做个响应时间监控,先以一个接口为例吧!

    注解(PerformanceMonitor):

    package com.ty.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    //代表此注解是运行过程中生效
    @Retention(RetentionPolicy.RUNTIME)
    //此注解作用于方法上
    @Target(ElementType.METHOD)
    public @interface PerformanceMonitor {
        String value() default "";
    }

    AnnotationController:

    package com.ty.controller;
    
    import com.ty.annotation.PerformanceMonitor;
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class AnnotationController {
    
        @PerformanceMonitor
        public String testServer() {
            System.out.println("do someThing");
            return "test";
        }
    }

    BeanNameAutoProxyConfig:

    package com.ty.configuration;
    
    import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class BeanNameAutoProxyConfig {
    
        @Bean
        public BeanNameAutoProxyCreator create() {
            BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
            //为所有的controller创建自动动态代理
            beanNameAutoProxyCreator.setBeanNames("*Controller");
            //代理的具体的业务逻辑交给bean-----performanceInterceptor
            beanNameAutoProxyCreator.setInterceptorNames("performanceInterceptor");
            return beanNameAutoProxyCreator;
        }
    }

    PerformanceInterceptor:

    package com.ty.interceptor;
    import com.ty.annotation.PerformanceMonitor;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    import org.springframework.cglib.proxy.MethodProxy;
    import org.springframework.stereotype.Service;
    
    import java.lang.reflect.Method;
    
    @Service
    public class PerformanceInterceptor implements MethodInterceptor {
    
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            Object result = null;
            //判断被代理类的方法上是否使用了@PerformanceMonitor注解
            PerformanceMonitor performanceMonitor = methodInvocation.getMethod().getAnnotation(PerformanceMonitor.class);
            if(performanceMonitor != null) {
                //具体的代理逻辑,实现aop的效果
                System.out.println("----------before----" + methodInvocation.getMethod().getName());
                result = methodInvocation.proceed();
                System.out.println("----------after----" + methodInvocation.getMethod().getName() + ";该笔调用耗时:" + (System.currentTimeMillis() - t1) + "ms");
            }else {
                //不使用该注解,则走原方法的调用逻辑
                result = methodInvocation.proceed();
            }
    
            return result;
        }
    }

    applicationContext.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--自动扫描包配置-->
        <context:component-scan base-package="com.ty"/>
    
        <bean id="student" class="com.ty.beans.Student">
            <!-- property代表的是set方法注入-->
            <property name="age" value="27"></property>
            <property name="name" value="马云"></property>
        </bean>
    
        <bean id="school" class="com.ty.beans.School">
            <!-- constructor代表的是构造器注入-->
            <constructor-arg ref="student"></constructor-arg>
        </bean>
    </beans>

    测试类:

    package com.ty.beans;
    
    import com.ty.controller.AnnotationController;
    import javafx.application.Application;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @ContextConfiguration(locations = { "classpath:applicationContext.xml" })
    @RunWith(SpringJUnit4ClassRunner.class)
    public class IOCTest  {
    
        @Test
        public void testAnnotation() {
            ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            AnnotationController annotationController = (AnnotationController) context.getBean("annotationController");
            annotationController.testServer();
        }
    }

    运行结果:

    ----------before----testServer
    do someThing
    ----------after----testServer;该笔调用耗时:8ms

    这样也就实现了性能监控的目的,并且以后项目中所有的controller的接口,只要需要这个功能,加个注解即可。

    @AspectJ

    基于@AspectJ实现aop是相对简单的方式,上面也有类似的案例,下面详细的说说这个玩意。

    aop主要包括切面切入点增强方法等核心组成部分。

    1、切面

    //用来声明这是一个AspectJ
    @Aspect     
    //在此处声明一个Component 是因为Spring扫描注解并不能识别AspectJ 因此在此处声明,不必在applicationContext.xml配置bean标签了
    @Component
    public class ServiceLog {
        。。。
    }

    2、切入点

    @Pointcut("execution(* com.sample.service.impl..*.*(..))")
    public void pc() {}

    a、execution()是最常用的切点函数,整个表达式可以分为五个部分,其语法如下所示:

    • execution(): 表达式主体。
    • 第一个*号:表示返回类型,*号表示所有的类型。
    • 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
    • 第二个*号:表示类名,*号表示所有的类。
    • *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

    b、@annotation

    另外切入点中的execution()也可以用@Annotation代替,如下:

    @AfterReturning("@annotation(com.ty.annotation.PerformanceMonitor)")
    public void needTestFun() {
        System.out.println("可以对注明@PerformanceMonitor的方法进行增强");
    }

    对于所有注明@PerformanceMonitor的方法进行增强。

    或者还有一种用法,可以方便拿到注解上的信息

    @Around(value = "@annotation(apiOperation)")
    public Object logApiCallInfo(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) {
        //TODO 1、这里方便拿到注解的信息。不过注意@annotation(apiOperation)名称要与参数名称一致
        String apiCode = apiOperation.value();
     
        //TODO 调用业务接口
        result = joinPoint.proceed();
        
        //TODO 获取接口请求参数,并拼装成json,格式化输出
        Object[] args = joinPoint.getArgs();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        JSONObject jsonObject = new JSONObject();
        if(parameterNames != null && parameterNames.length > 0 && args != null && args.length > 0) {
            for(int i = 0; i < parameterNames.length; i++) {
                jsonObject.put(parameterNames[i], args[i]);
            }
            //标准化输出
            jsonObject.toJsonString();
        }
    }

    3、增强方法

    //环绕通知注解,pc()则是上面的pointcut切入点
    @Around("pc()")
    //环绕通知会多ProceedingJoinPoint这个参数
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        。。。
    }
    • @Before
    • @AfterReturning
    • @Around
    • @AfterThrowing
    • @After

    4、applicationContext.xml

    <aop:aspectj-autoproxy/>
  • 相关阅读:
    win10 + uefi
    curl post请求方式
    Unable to process request: General SSLEngine problem.Unable to connect to neo4j at `localhost:7687`, because the certificate the server uses has changed.
    SpringMVC Ueditor1.4.3 未找到上传数据
    Unsupported major.minor version 52.0
    Spring官网改版后下载
    Linux防火墙(Iptables)的开启与关闭
    MySQLWorkbench里的稀奇事之timestamp的非空默认值
    秒杀系统
    java . 请在小于99999的正整数中找符合下列条件的数,它既是完全平方数,又有两位数字相同,如:144,676。
  • 原文地址:https://www.cnblogs.com/alimayun/p/10781073.html
Copyright © 2020-2023  润新知