zoukankan      html  css  js  c++  java
  • spring源码阅读(三)-Spring AOP用法和理解

    说明

    AOP、Spring AOP、Aspectj

    AOP

    1. 在我们原来的的代码的基础上,方法执行前,方法返回后,方法出现异常时,进行拦截处理或者叫做增强

    Spring AOP

    1. 基于动态代理实现,默认如果使用接口,使用jdk动态代理。如果没有实现接口,则使用CGLIB实现 用法可参考
    2. Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
    3. Spring AOP 需要依赖于 IOC 容器来管理。
    4. Spring AOP 只能基于容器中的Bean实现代理增强
    5. Spring 提供了 AspectJ 的支持 注:这里的支持只是规范的支持 底层还是spring

    Aspectj

    1. AspectJ来自于Eclipse基金会
    2. 属于静态织入 注入时机可以是:
        • Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
        • Post-compile weaving:也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
        • Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar

           3.AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。

    Spring AOP

    Spring AOP沿用了AspectJ的相关概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。

    如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。

    目前 Spring AOP 一共有三种配置方式,Spring 做到了很好地向下兼容

    • Spring 1.2 基于接口的配置:最早的 Spring AOP 是完全基于几个接口的
    • Spring 2.0 schema-based 配置:Spring 2.0 以后使用 XML 的方式来配置,使用 命名空间 <aop />
    • Spring 2.0 @AspectJ 配置:使用注解的方式来配置,这种方式感觉是最方便的,还有,这里虽然叫做 @AspectJ,但是这个和 AspectJ 其实没啥关系。

    Spring 1.2 中的配置

    例子1

    1.新增2个Advice

    /**
     * @Project spring
     * @Description 记录方法入参
     */
    public class LogArgAdvice implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
              System.out.println(String.format("方法名字:%s,入参:%s",method.getName(), Arrays.toString(args)));
        }
    }
    /**
     * @Description 方法返回拦截
     */
    public class ReturnAdvice implements AfterReturningAdvice {
    
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
          System.out.println(String.format("方法:%s,返回值:%s",method.getName(),returnValue));
        }
    }

    2.配置被代理类

    public interface StudentService {
        public Integer  del(Long id);
    
        public boolean delAll();
    }
    public class StudentServiceImpl implements StudentService {
    
        @Override
        public Integer del(Long id) {
            System.out.println(String.format("执行删除id为:%s的数据",id));
            return 1;
        }
    
        @Override
        public boolean delAll() {
            System.out.println("删除所有数据");
            return true;
        }
    }

    3.xml配置

    <!--代理类-->
        <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
        <!--定义2个advice-->
        <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
        <bean name="returnAdvice"  class="org.springframework.lq.aspect.ReturnAdvice"></bean>
        <!--配置实现studentServiceImpl的代理-->
        <bean name="studentServiceImplProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
            <!--代理接口-->
            <property name="proxyInterfaces">
                <list>
                    <value>org.springframework.lq.service.StudentService</value>
                </list>
            </property>
            <!--代理对象-->
            <property name="target" ref="studentServiceImpl" ></property>
            <!--配置拦截器 这里可以配置advice advisor interceptor-->
            <property name="interceptorNames">
                <list>
                    <value>logArgAdvice</value>
                    <value>returnAdvice</value>
                </list>
            </property>
        </bean>

    4.测试

    @Test
        public void lqTEST() {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                    new String[] {LQCONTEXT}, getClass());
            StudentService studentService=ctx.getBean("studentServiceImplProxy",StudentService.class);
            studentService.del(1L);
            studentService.delAll();
    }

    输出:

    例子2

    例子1有个缺点就是,粒度太大,所有方法都实现了拦截.如果只需要对delAll进行增强呢 使用Advisor

        <!--代理类-->
        <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
        <!--配置Advisor-->
        <bean name="logAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
            <!--指定advice-->
            <property name="advice" ref="logArgAdvice"/>
            <!--需要拦截的方法 可以配置多个,号隔开-->
            <property name="mappedNames" value="delAll"/>
        </bean>
        <!--定义advice-->
        <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
        <!--配置实现studentServiceImpl的代理-->
        <bean name="studentServiceImplProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
            <!--代理接口-->
            <property name="proxyInterfaces">
                <list>
                    <value>org.springframework.lq.service.StudentService</value>
                </list>
            </property>
            <!--代理对象-->
            <property name="target" ref="studentServiceImpl" ></property>
            <!--配置拦截器 这里可以配置advice advisor interceptor-->
            <property name="interceptorNames">
                <list>
                    <value>logAdvisor</value>
                </list>
            </property>
        </bean>

    例子3

    Interceptor 如果是advisor和advice都是方法的增强 我理解 Interceptor更倾向于方法的控制

    1.定义一个拦截器

    public class OperationInterceptor  implements MethodInterceptor {
        /**
         * 拦截的方法
         */
        List<String> refuseMethod= Arrays.asList("delAll");
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            if(refuseMethod.contains(invocation.getMethod().getName())){
                throw new Exception("无权访问");
            }
            return invocation.proceed();
        }
    }

    2.定义xml

    <!--代理类-->
        <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
        <!--配置Interceptor-->
        <bean name="operationInterceptor" class="org.springframework.lq.aspect.OperationInterceptor">
        </bean>
        <!--配置实现studentServiceImpl的代理-->
        <bean name="studentServiceImplProxy"  class="org.springframework.aop.framework.ProxyFactoryBean">
            <!--代理接口-->
            <property name="proxyInterfaces">
                <list>
                    <value>org.springframework.lq.service.StudentService</value>
                </list>
            </property>
            <!--代理对象-->
            <property name="target" ref="studentServiceImpl" ></property>
            <!--配置拦截器 这里可以配置advice advisor interceptor-->
            <property name="interceptorNames">
                <list>
                    <value>operationInterceptor</value>
                </list>
            </property>
        </bean>

    3.测试

    例子4

    例子3的问题是,对于每个类型我们都要配置一个代理,如果我们有需求,对ServiceImpl结尾的类生成代理呢 之类我们使用BeanNameAutoProxyCreator

    1.xml配置

    beanNames支持正则表达式配置

        <!--代理类-->
        <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
        <!--配置Interceptor-->
        <bean name="operationInterceptor" class="org.springframework.lq.aspect.OperationInterceptor">
        </bean>
        <!--配置实现studentServiceImpl的代理-->
        <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames" value="*ServiceImpl"></property>
            <!--配置拦截器 这里可以配置advice advisor interceptor-->
            <property name="interceptorNames">
                <list>
                    <value>operationInterceptor</value>
                </list>
            </property>
        </bean>

    2.测试

    红色部分可以发现我们可以直接使用基础类型了

            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                    new String[] {LQCONTEXT}, getClass());
            StudentService studentService=ctx.getBean(StudentService.class);
            studentService.del(1L);
            studentService.delAll();

    例子5

    如果我们需要让del开头的方法进行方法控制呢 使用RegexpMethodPointcutAdvisor

    1.xml配置

    <!--代理类-->
        <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
        <!--配置Advisor-->
        <bean name="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <!--指定advice-->
            <property name="advice" ref="logArgAdvice"/>
            <!--需要拦截的方法 可以配置多个,号隔开 全名称配置 支持正则多个,号隔开-->
            <property name="pattern" value="org.springframework.lq.service.StudentService.del*"/>
        </bean>
        <!--定义advice-->
        <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
        <!--配置实现studentServiceImpl的代理-->
        <bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames" value="*ServiceImpl"></property>
            <!--配置拦截器 这里可以配置advice advisor interceptor-->
            <property name="interceptorNames">
                <list>
                    <value>logAdvisor</value>
                </list>
            </property>
        </bean>

    例子6

    是否觉得配置BeanNameAutoProxyCreator 很麻烦呢 可以使用DefaultAdvisorAutoProxyCreator

        <!--代理类-->
        <bean name="studentServiceImpl"  class="org.springframework.lq.service.StudentServiceImpl"></bean>
        <!--配置Advisor-->
        <bean name="logAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <!--指定advice-->
            <property name="advice" ref="logArgAdvice"/>
            <!--需要拦截的方法 可以配置多个,号隔开 全名称配置 支持正则多个,号隔开-->
            <property name="pattern" value="org.springframework.lq.service.StudentService.del*"/>
        </bean>
        <!--定义advice-->
        <bean name="logArgAdvice"  class="org.springframework.lq.aspect.LogArgAdvice"></bean>
        <!--配置自动代理-->
        <bean  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

    Spring 2.0 @AspectJ配置

    1.引入包

    注:虽然引入了Aspectj包,但是还是使用的是Spring API @Aspectj跟 Aspectj没有任何关系,只是spring AOP是根据Aspectj规则实现,用到了他的注解

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.11</version>
    </dependency>

    如果是使用 Spring Boot 的话,添加以下依赖即可:

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

    2.xml配置

    <aop:aspectj-autoproxy/>

    也可以通过注解开启

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }

    一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,我们称之为一个 Aspect。

    注意了,@Aspect 注解要作用在 bean 上面,不管是使用 @Component 等注解方式,还是在 xml 中配置 bean,首先它需要是一个 bean。

    如:

    <bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
        <!-- configure properties of aspect here as normal -->
    </bean>
    @Aspect
    public class NotVeryUsefulAspect {
    
    }

    3.配置Pointcut 一般翻译为切点

    @Pointcut("execution(* transfer(..))")// the pointcut expression
    private void anyOldTransfer() {}// the pointcut signature

    @Pointcut 中使用了 execution 来正则匹配方法签名,这也是最常用的,除了 execution,我们再看看其他的几个比较常用的匹配方式:

    • within:指定所在类或所在包下面的方法(Spring AOP 独有)

      如 @Pointcut("within(com.javadoop.springaoplearning.service..*)")

    • @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。

      如 @Pointcut("execution(.*(..)) && @annotation(com.javadoop.annotation.Subscribe)")

    • bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)

      如 @Pointcut("bean(*Service)")

    上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。

    对于 web 开发者,Spring 有个很好的建议,就是定义一个 SystemArchitecture:

    @Aspect
    public class SystemArchitecture {
    
        // web 层
        @Pointcut("within(com.javadoop.web..*)")
        public void inWebLayer() {}
    
        // service 层
        @Pointcut("within(com.javadoop.service..*)")
        public void inServiceLayer() {}
    
        // dao 层
        @Pointcut("within(com.javadoop.dao..*)")
        public void inDataAccessLayer() {}
    
        // service 实现,注意这里指的是方法实现,其实通常也可以使用 bean(*ServiceImpl)
        @Pointcut("execution(* com.javadoop..service.*.*(..))")
        public void businessService() {}
    
        // dao 实现
        @Pointcut("execution(* com.javadoop.dao.*.*(..))")
        public void dataAccessOperation() {}
    }

    上面这个 SystemArchitecture 很好理解,该 Aspect 定义了一堆的 Pointcut,随后在任何需要 Pointcut 的地方都可以直接引用(如 xml 中的 pointcut-ref="")。

    配置 pointcut 就是配置我们需要拦截哪些方法。

    配置

    Advice配置

    @Aspect
    public class AdviceExample {
    
        // 这里会用到我们前面说的 SystemArchitecture
        // 下面方法就是写拦截 "dao层实现"
        @Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
        public void doAccessCheck() {
            // ... 实现代码
        }
    
        // 当然,我们也可以直接"内联"Pointcut,直接在这里定义 Pointcut
        // 把 Advice 和 Pointcut 合在一起了,但是这两个概念我们还是要区分清楚的
        @Before("execution(* com.javadoop.dao.*.*(..))")
        public void doAccessCheck() {
            // ... 实现代码
        }
    
        @AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
        public void doAccessCheck() {
            // ...
        }
    
        @AfterReturning(
            pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
            returning="retVal")
        public void doAccessCheck(Object retVal) {
            // 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便
            //  ... 实现代码
        }
    
        // 异常返回
        @AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
        public void doRecoveryActions() {
            // ... 实现代码
        }
    
        @AfterThrowing(
            pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
            throwing="ex")
        public void doRecoveryActions(DataAccessException ex) {
            // ... 实现代码
        }
    
        // 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
        @After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
        public void doReleaseLock() {
            // 通常就像 finally 块一样使用,用来释放资源。
            // 无论正常返回还是异常退出,都会被拦截到
        }
    
        // 感觉这个很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
        @Around("com.javadoop.aop.SystemArchitecture.businessService()")
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            // start stopwatch
            Object retVal = pjp.proceed();
            // stop stopwatch
            return retVal;
        }
    
    }
    @Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()")
    public void logArgs(JoinPoint joinPoint) {
        System.out.println("方法执行前,打印入参:" + Arrays.toString(joinPoint.getArgs()));
    }

    Spring 2.0 schema-based配置

    解析<AOP/> 相关的xml在org.springframework.aop.config.AopNamespaceHandle 中

    <aop:config>
        <aop:aspect ref="logArgsAspect">
            <aop:pointcut id="internalPointcut"
                    expression="com.javadoop.SystemArchitecture.businessService()" />
        </aop:aspect>
    </aop:config>
  • 相关阅读:
    你喜欢使用eclipse+tomcat编程吗?!
    "ERR_GFX_D3D_INIT", GTA5-报错解决办法
    这样写JS的方式对吗?
    webui layout like desktop rich client
    2014年12月23日00:42:54——PS4
    2014年12月20日00:33:14-遮罩+进度条-extjs form.isvalid
    十分钟部署智能合约
    idea clion编译器
    parity 注记词
    go语言学习笔记
  • 原文地址:https://www.cnblogs.com/LQBlog/p/13993882.html
Copyright © 2011-2022 走看看