美文网首页
spring AOP

spring AOP

作者: printf200 | 来源:发表于2019-10-21 09:21 被阅读0次

    分布于应用中多处的功能称为横切关注点,通过这些横切关注点在概念上是与应用的业务逻辑相分离的,但其代码往往直接嵌入在应用的业务逻辑之中。将这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的。

    什么是面向切面编程

    image

    切面实现了横切关注点的模块化

    面向切面编程中,通过声明的方式定义通用功能(安全、事务等)以何种方式在何处应用,而无需修改受影响的类(CourseService、StudentService等)。

    AOP术语

    通知(Advice):何种功能、何时

    切面的工作被称为通知,同时通知还要解决何时执行这个工作的问题。Spring切面可以应用5种类型的通知:

    • Before:在方法被调用之前调用通知;

    • After:在方法调用之后调用通知;

    • After-returning:在方法成功执行后;

    • After-throwing:在方法抛出异常后;

    • Around:在方法调用之前和之后都会调用通知;

    连接点(Joinpoint):能够应用通知的点

    连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中。

    切点(Pointcut):何处,应用通知的连接点的集合

    切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称来指定这些切点,或是利用正则表达式定义匹配来指定这些切点。

    切面(Aspect)

    切面是通知和切点的结合,即何时在何处完成何种功能。

    引入(Introduction)

    引入允许我们向现有的类添加新方法或属性,从而可以在无需修改现有类的情况下,让它们具有新的行为和状态。

    织入(Weaving)

    将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:

    • 编译期:需要特殊的编译器,AspectJ的织入编译器就是这种方式;

    • 类加载期:在目标类加载到JVM时被织入,需要特殊的类加载器。

    • 运行期:在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是这种方式。

    Spring对AOP的支持

    • 基于代理的经典AOP;

    • @AspectJ注解驱动的切面;

    • 纯POJO切面;

    • 注入式AspectJ切面(适合Spring个版本);

    Spring是在运行期将切面织入到所管理的Bean中的,如图所示,代理类封装了目标类,当拦截到方法调用时,在调用目标Bean的方法之前,代理会执行切面逻辑。真正应用需要被代理的Bean时,Spring才会创建代理对象。Spring的切面由包裹了目标对象的代理类实现,代理类处理方法的调用,执行额外的切面逻辑,并调用目标方法。

    image

    Spring的切面由包裹了目标对象的代理类实现,代理类处理方法的调用,执行额外的切面逻辑,并调用目标方法。

    Spring只支持方法连接点,缺少对字段连接点的支持,例如拦截对象字段的修改。也不支持构造器连接点,也就无法在Bean创建时应用通知。

    使用切点选择连接点

    Spring AOP中,需要使用AspectJ的切点表达式来定义切点。

    AspectJ指示器 描述
    arg() 限制连接点匹配参数为指定类型的执行方法
    @args() 限制连接点匹配参数由指定注解标注的执行方法
    execution() 用于匹配是连接点的执行方法
    this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
    target() 限制连接点匹配目标对象为执行类型的类
    @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
    within() 限制连接点匹配指定的类型
    @within() 限制连接点匹配指定注解所标注的类型
    @annotation() 限制匹配带有指定注解连接点

    编写切点

    image

    使用AspectJ切点表达式来定位

    这里使用了execution()指示器来选择Instrument的play()方法。表达式以*开头表示不关心返回值的类型,然后指定了全限定类名和方法名,使用..作为方法的参数列表,表示可以是任意的入参。

    使用&&将execution()和within()进行连接,那么也就可以使用||(或)和!(非)。

    image

    使用within()指示器限制切点范围

    使用Spring的bean()指示器

    bean()使用Bean id来作为参数,从而限制切点只匹配特定的Bean,如:

    execution(* com.springinaction.springidol.Instrument.play()) and bean(eddie)

    这里,表示在执行Instrument的play()方法时应用通知,但限定Bean的id为eddie。

    在XML中声明切面

    AOP配置元素 描述
    <aop:advisor> 定义AOP通知器
    <aop:after> 定义AOP后置通知(不管该方法是否执行成功)
    <aop:after-returning> 在方法成功执行后调用通知
    <aop:after-throwing> 在方法抛出异常后调用通知
    <aop:around> 定义AOP环绕通知
    <aop:aspect> 定义切面
    <aop:aspect-autoproxy> 定义@AspectJ注解驱动的切面
    <aop:before> 定义AOP前置通知
    <aop:config> 顶层的AOP配置元素,大多数的<aop:*>包含在<aop:config>元素内
    <aop:declare-parent> 为被通知的对象引入额外的接口,并透明的实现
    <aop:pointcut> 定义切点

    所需jar包:

    image

    表演者接口:

    public interface Performer {
    
        void perform();
    
    }
    
    

    歌唱家类:

    public class Instrumentalist implements Performer {
    
        private String song;
    
        private Instrument instrument;
    
        public Instrumentalist() {
    
        }
    
        public void perform() {
    
            System.out.print("Playing " + song + " : ");
    
            instrument.play();
    
        }
    
        public void setSong(String song) { // 注入歌曲
    
            this.song = song;
    
        }
    
        public String getSong() {
    
            return song;
    
        }
    
        public String screamSong() {
    
            return song;
    
        }
    
        public void setInstrument(Instrument instrument) { // 注入乐器
    
            this.instrument = instrument;
    
        }
    
    }
    
    

    乐器接口:

    public interface Instrument {
    
        public void play();
    
    }
    
    

    吉他类:

    public class Guitar implements Instrument {
    
        public void play() {
    
            System.out.println("Strum strum strum");
    
        }
    
    }
    
    

    下面定义一个观众类:

    public class Audience {    // 表演之前
        public void takeSeats() {
            System.out.println("The audience is taking their seats.");
        }    // 表演之前
        public void turnOffCellPhones() {
            System.out.println("The audience is turning off their cellphones");
        }    // 表演之后
        public void applaud() {
            System.out.println("CLAP CLAP CLAP CLAP CLAP");
        }    // 表演失败之后
        public void demandRefund() {
            System.out.println("Boo! We want our money back!");
        }
    }
    
    

    声明前置和后置通知

    <?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: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.xsd">
    
        <bean id="eddie" class="com.springinaction.springidol.Instrumentalist">
            <property name="instrument">
                <bean class="com.springinaction.springidol.Guitar" />
            </property>
            <property name="song" value="my love" />
        </bean>
    
        <bean id="audience" class="com.springinaction.springidol.Audience" />
    
        <aop:config>
            <aop:aspect ref="audience"><!-- 引用audience Bean -->
                <!-- 声明切入点 -->
                <aop:pointcut id="performance"
                    expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
                <!-- 表演之前 -->
                <aop:before pointcut-ref="performance" method="takeSeats" />
                <aop:before pointcut-ref="performance" method="turnOffCellPhones" />
                <!-- 表演之后 -->
                <aop:after-returning pointcut-ref="performance"
                    method="applaud" />
                <!-- 表演失败之后 -->
                <aop:after-throwing pointcut-ref="performance"
                    method="demandRefund" />
            </aop:aspect>
        </aop:config></beans>
    
    

    <aop:config>中,可以声明一个或多个通知器、切面或者切点。pointcut属性定义了通知所引用的切点。最终的通知逻辑如何织入到业务逻辑中:

    image

    Audience切面包含4中通知,这些通知把通知=逻辑织入到匹配的切面的切点方法中

    测试代码:

    @Test
        public void testBeforeAndAfter() throws PerformanceException{
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
            Performer performer = (Performer) context.getBean("eddie");
            performer.perform();
        }
    
    

    测试结果:

    The audience is taking their seats.
    The audience is turning off their cellphones
    Playing my love : Guitar Guitar Guitar
    CLAP CLAP CLAP CLAP CLAP

    声明环绕通知

    前置通知和后置通知之间共享消息需要使用成员变量,而Audience是单例,使用成员变量有可能存在线程安全问题。使用环绕通知可以完成之前前置和后置所实现的相同功能,而且只需一个方法。

    package com.springinaction.springidol;
    import org.aspectj.lang.ProceedingJoinPoint;
    public class AroundAudience {    
    public void watchPerformance(ProceedingJoinPoint joinpoint) {        
    try {            // 表演之前
                System.out.println("The audience is taking their seats.");
                System.out.println("The audience is turning off their cellphones");            
                long start = System.currentTimeMillis();            // 执行被通知的方法
                joinpoint.proceed();            // 表演之后
                long end = System.currentTimeMillis();
                System.out.println("CLAP CLAP CLAP CLAP CLAP");
                System.out.println("The performance took " + (end - start) + " milliseconds.");
            } catch (Throwable t) {            // 表演失败之后
                System.out.println("Boo! We want our money back!");
            }
        }
    }
    
    

    ProceedingJoinPoint作为入参,从而可以在通知里调用被通知的方法。

    XML配置:

    <bean id="audience" class="com.springinaction.springidol.AroundAudience" />
    <aop:config>
        <aop:aspect ref="audience"><!-- 引用audience Bean -->
            <!-- 声明切入点 -->
            <aop:pointcut id="performance"
                expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
            <aop:around method="watchPerformance" pointcut-ref="performance" />
        </aop:aspect>
        </aop:config></pre>
    
    

    一个实际例子---日志记录

    AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:
    1)JoinPoint
     java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
     Signature getSignature() :获取连接点的方法签名对象;
     java.lang.Object getTarget() :获取连接点所在的目标对象;
     java.lang.Object getThis() :获取代理对象本身;
    2)ProceedingJoinPoint
    ProceedingJoinPoint继承JoinPoint子接口,它新增了个用于执行连接点方法的方法:
     java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;

    二、代码演示。

    SecurityHandler.java

    package com.tgb.spring;  
    
    import org.aspectj.lang.JoinPoint;  
    
    public class SecurityHandler{  
    
        private void checkSecurity(JoinPoint joinPoint){  
    
            for (int i = 0; i < joinPoint.getArgs().length; i++) {  
    
                System.out.println(joinPoint.getArgs()[i]);  
    
            }  
    
            System.out.println(joinPoint.getSignature().getName());  
            System.out.println("=====checkSecurity====");  
        }  
    }  
    
    

    Client.java

    package com.tgb.spring;  
    
    import org.springframework.beans.factory.BeanFactory;  
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;  
    
    import com.tgb.spring.UserManager;  
    
    public class Client {  
    
        public static void main(String[] args) {  
    
        BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");  
    
            UserManager userManager=(UserManager) factory.getBean("userManager");  
    
            userManager.addUser("张三", "123");  
    
            //userManager.delUser(1);  
    
        }  
    
    }  
    
    

    UserManager.java

    package com.tgb.spring;  
    
    public interface UserManager {  
    
        public void addUser(String username,String password);  
    
        public void delUser(int userId);  
    
        public String findUserById(int userId);  
    
        public void modifyUser(int userId,String username,String password);  
    
    }  
    
    

    UserManagerImpl.java

    package com.tgb.spring;  
    
    public class UserManagerImpl implements UserManager {  
    
        public void addUser(String username, String password) {  
    
            //checkSecurity();  
    
                System.out.println("===UserManager.addUser===");  
    
        }  
    
        public void delUser(int userId) {  
    
            //checkSecurity();  
    
            System.out.println("===UserManager.delUser===");  
    
        }  
    
        public String findUserById(int userId) {  
    
            //checkSecurity();  
    
            System.out.println("===UserManager.findUserById===");  
    
        return  "张三";  
    
        }  
    
        public void modifyUser(int userId, String username, String password) {  
    
            //checkSecurity();  
    
            System.out.println("===UserManager.modifyUser===");  
    
        }  
    
    //  private void checkSecurity(){  
    
    //      System.out.println("checkSecurity");  
    
    //  
    
    //  }  
    
    }  
    
    

    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:aop="http://www.springframework.org/schema/aop"  
    
             xmlns:tx="http://www.springframework.org/schema/tx"  
    
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  
    
               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd  
    
               http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  
    
    <bean id="userManager" class="com.tgb.spring.UserManagerImpl" />  
    
    <bean id="securityHandler" class="com.tgb.spring.SecurityHandler"/>  
    
    <aop:config>  
    
        <aop:aspect id="securityAspect" ref="securityHandler">  
    
             <aop:pointcut id="addAddMethod" expression="execution(* com.tgb.spring.*.*(..))" />  
    
            <aop:before method="checkSecurity" pointcut-ref="addAddMethod" />  
    
        </aop:aspect>  
    
    </aop:config>  
    
    </beans>  
    
    

    相关文章

      网友评论

          本文标题:spring AOP

          本文链接:https://www.haomeiwen.com/subject/nomouctx.html