• 云笔记项目-AOP知识简单学习


    在云笔记项目的过程中,需要检查各个业务层的执行快慢,如登录、注册、展示笔记本列表,展示笔记列表等,如果在每个业务层方法里都写一段代码用来检查时间并打印,不仅仅显得代码重复,而且当项目很大的时候,将大大加大工作量。这个时候AOP的概念引入了,本文在引用其他大牛博文的基础上,对AOP知识进行了简单整理,今后可以参考使用。

    什么是AOP

    AOP(Aspect Oriented Programming),即面向切面编程,底层使用了动态代理技术,在不改变原有业务逻辑的基础上,横向添加业务逻辑。就云笔记项目来说,任何一个Action,都会按照浏览器<-->控制层<-->业务层<-->持久层<-->数据库的顺序执行,每一个小功能都是按照此路程进行,这个可以理解为纵向逻辑。然后上面说的检查业务层方法执行快慢的功能,因为其分布在各个业务层里,像横切了一刀,因此形象的叫做切面。AOP是对OOP(Object Oriented Programming)的补充,它利用"横切"的技术,解开封装对象的内部,将影响到多个类的公共行为(如测试某个业务响应时间、事务管理、日志记录、异常处理等),封装到一个可以重用的模块,并将其取名“Aspect”,即切面。

    使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点发生在核心关注点的多个地方,功能基本相似,如上述的权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。另外AOP实现了"高内聚",而IOC和DI实现了"低耦合"。

    主要概念

    (1)横切关注点

    对哪些方法进行拦截,拦截后如何处理,这些关注点为横切关注点

    (2)连接点(Joinpoint)

    在Spring中连接点就是被拦截到的方法,也可以是字段或者构造器(暂时没实践过)

    (3)切入点(Pointcut)

    需配合切入表达式使用,切入点是符合切入规则的连接点,可以对选择出来的连接点进行功能的增强。主要有bean组件切入点,类切入点和方法切入点:

    (a)bean组件切入点,语法 bean(beanid),以云笔记为例,bean(userService) 切入一个类,bean(userService)||bean(noteService)||bean(noteBookService) 切入三个类,bean(userService)||bean(noteService)||bean(noteBookService) 切入三个类,bean(*Service) 切入所有后缀名字为Service的类。
    (b)类切入点,语法 within(包名.类名),以云笔记为例,如within(com.boe.Service.UserServiceImpl),within(com.boe.Service.UserServiceImpl)||within(com.boe.Service.noteBookServiceImpl),within(com.boe.*.*ServiceImpl)。
      (c)  方法切入点,语法 execution(返回值类型   包名.类名.方法名(参数类型)),比如execution(* com.boe.Service.UserServiceImpl.login(..)),代表返回类型不限定,com.boe.Service包下类UserServiceImpl,方法名为login,参数个数和类型不限定。

    以上三种切入点方式,只有方法切入点是细粒度的,可以更加精细的定位到切入点。

    (4)切面(Aspect)

    事物的横切面,是对横切关注点的抽象,简单理解就是Spring将拦截下来的切入点交给一个处理类来处理,进行功能的增强,这个处理类就是一个横切面。

    (5)通知(Advice)

    Spring拦截到连接点变成切入点交给切面类,切面类中有处理切入点的方法,这些方法就是通知(也称增强方法),按照类型来划分主要有@Before,@After,@AfterReturning,@AfterThrowing和@Around五种。

    五种通知的大致执行顺序可以根据try-catch-finally来理解,不过不是完全相同,如发生异常,@After注解的方法会执行,@AfterReturning注解的方法没机会执行。

    1  try{
    2     @Before 方法执行
    3     目标业务方法执行
    4     @AfterReturning 方法执行
    5      }catch(Exception e){
    6         @AfterThrowing 如果有异常将执行
    7       }finally{
    8         @After 方法执行
    9       }

    (6)目标对象(Target Object)

    被一个或多个切面所通知(在切入点增强)的对象,就是目标对象,Spring AOP底层是代理实现,目标对象其实也是被代理对象

    (7)织入(Weave)

    将切面应用到目标对象后代理对象创建的过程

    Spring AOP

    简单来说,Spring AOP底层调用了AspectJ AOP,而AspectJ AOP底层使用动态代理。有两种动态代理技术,一种是使用JDK动态代理,一种是使用CGLib动态代理。两种的区别在于目标方法是否有对应的接口,如果目标方法有对应的接口就使用JDK动态代理,如果目标方法没有接口就使用CGLib,使用CGLib需要导入第三方的包才能使用,如果使用不带注解的方式进行aop配置,可以在<aop:config>内配置“proxy-target-class”属性,默认情况下为false,如果设置为true,将使用CGLib代理,具体底层暂时水平不足无法深究,后续可能补充。

    接下来的例子将使用JDK动态代理,因为目标方法有对应的接口。
    Spring会帮忙动态创建对象并帮忙管理对象之间的依赖关系,因此使用Spring将大大简化AOP的使用过程,如果使用底层AspectJ AOP将加大代码量。使用AOP主要要准备确认好以下几点:

    (1)确定普通业务组件

    (2)确定切入点

    (3)确定AOP业务组件,为普通业务组件织入增强处理

    基于Spring AOP的简单应用

    Spring AOP的简单应用,将以使用注解不使用注解两种方式进行简单使用。不管使用哪种方式,都需要导入aspectjweaver.jar,大牛博客介绍说要导入aopalliance.jar,暂时这个未做导入,也可以测试通过,后续了解。以下是Maven项目下pom.xml的配置:

    1    <dependency>
    2     <groupId>aspectj</groupId>
    3     <artifactId>aspectjweaver</artifactId>
    4     <version>1.5.3</version>
    5    </dependency>   

    case 1 使用注解的情况

    (1)定义接口

    1 package Test;
    2 
    3 public interface HelloAOP {
    4     
    5     public void helloAop() throws InterruptedException;
    6 
    7 }

    (2)定义接口的实现类

     1 package Test;
     2 
     3 import org.springframework.stereotype.Component;
     4 
     5 @Component("helloAOPImpl1")
     6 public class HelloAOPImpl1 implements HelloAOP{
     7 
     8     public void helloAop() throws InterruptedException {
     9         //特地让其执行1s
    10         Thread.sleep(1000);
    11         System.out.println("Hello Aop From HelloAOPImpl1");        
    12     }
    13 }

    (3)定义横切关注点,拦截后打印两个时间

    package AOP;
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * 带注解的AOP,打印时间
     * @author clyang
     */
    @Component
    @Aspect
    public class AOP1 {
        /**
         * 使用bean组件切入点
         */
        @Before("bean(helloAOPImpl1)")
        public void testBefore() {
            System.out.println("当前时间为:"+System.currentTimeMillis());
        }
        
        @After("bean(helloAOPImpl1)")
        public void testAfter() {
            System.out.println("当前时间为:"+System.currentTimeMillis());
        }
    }

    (4)Sprng-aop1.xml中配置,按照有注解的方式进行配置

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"
     4     xmlns:aop="http://www.springframework.org/schema/aop"  
     5     xmlns:jee="http://www.springframework.org /schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
     6     xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
     7     xsi:schemaLocation="
     8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
     9         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
    10         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
    11         http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
    12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    13         http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
    14         http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
    15         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">    
    16         
    17         <!-- 使用注解的方式 -->
    18         <!-- 配置组件扫描,使得@Component注解生效-->
    19         <context:component-scan base-package="AOP"></context:component-scan>
    20         <context:component-scan base-package="Test"></context:component-scan>
    21         <!-- 注解驱动 -->
    22         <mvc:annotation-driven></mvc:annotation-driven>
    23         
    24         <!-- 添加aop注解驱动,使得@Aspect注解生效 -->
    25         <!-- 需要在头文件中添加“xmlns:aop”的命名申明,并在“xsi:schemaLocation”中指定aop配置的schema的地址 -->
    26         <aop:aspectj-autoproxy />            
    27 </beans>

    (5)写一个测试类进行测试

     1 package TestAOP;
     2 
     3 import org.junit.Before;
     4 import org.junit.Test;
     5 import org.springframework.context.ApplicationContext;
     6 import org.springframework.context.support.ClassPathXmlApplicationContext;
     7 
     8 import Test.HelloAOP;
     9 
    10 public class TestCase {
    11     ApplicationContext ac1=null;//使用注解
    12     
    13     @Before
    14     public void before() {
    15         String config1="config/spring-aop1.xml";   
    16         ac1=new ClassPathXmlApplicationContext(config1);            
    17     }
    18     
    19     /**
    20      * 使用注解
    21      * @throws InterruptedException
    22      */
    23     @Test
    24     public void test() throws InterruptedException {
    25         //测试执行业务方法时,切面方法是否执行                            
    27         HelloAOP helloAop=ac1.getBean("helloAOPImpl1",HelloAOP.class);
    28         helloAop.helloAop();
    29     }
    30 
    31 }

    (6)运行结果为:

    当前时间为:1553328677357
    Hello Aop From HelloAOPImpl1
    当前时间为:1553328678357

    发现目标方法打印出结果的前后,分别调用@Before和@After的通知,执行了打印当前时间,并延迟1s打印执行后的时间。

     (7)小花絮

    当时在测试类里,本来想通过@Resource或者@Autowired配合@Qualifier注入helloAOP实现类实体对象,结果都返回为null,参考网上博客,可能的原因分析如下:

    (1)如果使用main方法执行测试,@Resource注解以及具体执行方法,会封装在一个类里(假设类名为T)。然后main方法通过实例化这个类T,来调用它里面的执行方法,这种情况因为new关键字实例化了测试类T,测试类对象就不归Spring容器管理,导致测试类对象使用注解注入helloAOPImpl1失效。
    (2)如果使用Junit测试,原理跟main方法类似。

    case 2 不使用注解的情况

    (1)定义接口的实现类

     1 package Test;
     2 
     3 public class HelloAOPImpl2 implements HelloAOP{
     4 
     5     public void helloAop() throws InterruptedException {
     6         //特地让其执行1s
     7         Thread.sleep(1000);
     8         System.out.println("Hello Aop From HelloAOPImpl2");                
     9     }
    10 }

    (2)定义横切关注点,拦截后打印两个时间

     1 package AOP;
     2 
     3 import org.aspectj.lang.annotation.After;
     4 import org.aspectj.lang.annotation.Before;
     5 
     6 /**
     7  * 不带注解的AOP 打印时间
     8  * @author clyang
     9  *
    10  */
    11 public class AOP2 {
    12 
    13     public void testBefore() {
    14         System.out.println("当前时间为:"+System.currentTimeMillis());
    15     }
    16    
    17     public void testAfter() {
    18         System.out.println("当前时间为:"+System.currentTimeMillis());
    19     }
    20 }

    (3)Sprng-aop2.xml中配置,按照没有注解的方式进行配置

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"
     4     xmlns:aop="http://www.springframework.org/schema/aop"  
     5     xmlns:jee="http://www.springframework.org /schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
     6     xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
     7     xsi:schemaLocation="
     8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
     9         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
    10         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
    11         http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
    12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    13         http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
    14         http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
    15         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">    
    16         
    17         <!-- 不使用注解的方式-->
    18         <bean id="aop2" class="AOP.AOP2"></bean>
    19         <bean id="helloAopImpl2" class="Test.HelloAOPImpl2"></bean>
    20         
    21         <!-- 1个切面配置 -->
    22         <aop:config>
    23           <aop:aspect id="test" ref="aop2">
    24             <aop:pointcut id="time" expression="bean(helloAopImpl2)"/> <!-- 使用bean组件切入点 -->
    25             <aop:before method="testBefore" pointcut-ref="time"/>
    26             <aop:after method="testAfter" pointcut-ref="time"/>
    27           </aop:aspect>
    28         </aop:config>
    29         
    30 </beans>

    (4)测试类修改进行测试

     1 package TestAOP;
     2 
     3 import org.junit.Before;
     4 import org.junit.Test;
     5 import org.springframework.context.ApplicationContext;
     6 import org.springframework.context.support.ClassPathXmlApplicationContext;
     7 
     8 import Test.HelloAOP;
     9 
    10 public class TestCase {
    11     ApplicationContext ac1=null;//使用注解
    12     ApplicationContext ac2=null;//不使用注解
    13     
    14     @Before
    15     public void before() {
    16         String config1="config/spring-aop1.xml";   
    17         String config2="config/spring-aop2.xml";   
    18         ac1=new ClassPathXmlApplicationContext(config1);        
    19         ac2=new ClassPathXmlApplicationContext(config2);            
    20     }
    21     
    22     /**
    23      * 使用注解
    24      * @throws InterruptedException
    25      */
    26     @Test
    27     public void test() throws InterruptedException {
    28         //测试执行业务方法时,切面方法是否执行        
    29         //helloAop.helloAop();        
    30                 
    31         HelloAOP helloAop=ac1.getBean("helloAOPImpl1",HelloAOP.class);
    32         helloAop.helloAop();
    33     }
    34     /**
    35      * 不使用注解
    36      * @throws InterruptedException
    37      */
    38     @Test
    39     public void test1() throws InterruptedException {                
    40         HelloAOP helloAop=ac2.getBean("helloAopImpl2",HelloAOP.class);
    41         helloAop.helloAop();
    42     }
    43 
    44 }

    (5)单独执行test1方法进行测试,测试结果为:

    当前时间为:1553329722057
    Hello Aop From HelloAOPImpl2
    当前时间为:1553329723058

     Spring AOP使用的简单对比

    (1)如果使用注解,使用过程很简单,配置文件配置好注解扫描和Aspect自动动态代理扫描,Spring容器启动后就可以扫描到业务类和AOP类,通过通知注解,完成对目标方法的拦截,实现横切。

    (2)如果不使用注解,需要配置文件中管理bean,对业务类和AOP类进行手动管理,并在里面进行具体AOP配置。相对来说使用要稍微复杂一些。以下再参考大牛博文进行一点补充,如何在没有注解的情况下配置AOP。

    使用注解配置的三种方式

    (1)在<aop:aspect>标签内使用<aop:pointcut>声明切入点,该切入点一般只被该切面使用,expression是切入点方式,如上文有三种来写,这里写bean组件切入点。切入点使用id属性指定bean名字,在通知定义时使用pointcut-ref来引入切入点。当执行到切入点后会激活通知里对应的方法,如本例中的testBefore()方法和testAfter()方法。

            <aop:config>
              <aop:aspect id="test" ref="aop2">
                <aop:pointcut id="time" expression="bean(helloAopImpl2)"/> <!-- 使用bean组件切入点 -->
                <aop:before method="testBefore" pointcut-ref="time"/>
                <aop:after method="testAfter" pointcut-ref="time"/>
              </aop:aspect>
            </aop:config>

    (2)在<aop:config>标签内使用<aop:pointcut>声明切入点,这个切入点可以被多个切面使用,同样对切入点进行命名,在通知定义时通过bean id来引入切入点。

            <aop:config>
                <aop:pointcut id="time" expression="bean(helloAopImpl2)"/> <!-- 使用bean组件切入点 -->
                <aop:aspect id="test" ref="aop2">
                <aop:before method="testBefore" pointcut-ref="time"/>
                <aop:after method="testAfter" pointcut-ref="time"/>
              </aop:aspect>
            </aop:config>

    (3)匿名切入点Bean,在通知声明时通过pointcut属性指定切入点表达式,该切入点只被该通知使用

            <aop:config>
                <aop:aspect id="test" ref="aop2">
                <aop:before method="testBefore" pointcut="bean(helloAopImpl2)"/>
                <aop:after method="testAfter" pointcut="bean(helloAopImpl2)"/>
              </aop:aspect>
            </aop:config>

    本文中使用的是第一种方式。

    总结

    (1)AOP在需要横向扩展功能时非常有效,其底层使用了动态代理技术,动态代理技术底层使用过了反射的技术。

    (2)AOP可以使用两种配置方式来使用,带注解和不带注解的方式,并且不带注解的方式还有多种配置AOP的写法。

    (3)AOP可以使用在云笔记项目中进行业务层的性能测试。

    参考博文:

    (1)https://www.cnblogs.com/xrq730/p/4919025.html

    (2)https://www.cnblogs.com/mxck/p/7027912.html

    (3)https://www.cnblogs.com/youngchaolin/p/11594869.html

  • 相关阅读:
    ARC 基础(上)
    将字符串写到屏幕上
    UIColor 详解
    the complexity is no longer O(lgn), right?
    [LeetCode] Sum Root to Leaf Numbers, Solution
    [LeetCode] Word Ladder II, Solution
    [Microsoft] Intealeaving of two given strings, Solution
    [Yahoo] Cloest palindrome number, Solution
    [LeetCode] Longest Consecutive Sequence, Solution
    Summary
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/10578462.html
Copyright © 2020-2023  润新知