spring面向切面编程的原理_什么叫切面

spring面向切面编程的原理_什么叫切面第4章 面向切面的Springpackageconcert;importorg.aspectj.lang.annotation.*;@AspectpublicclassAudience{@Pointcut(“execu

spring面向切面编程的原理_什么叫切面"

第4章 面向切面的Spring

package concert;

import org.aspectj.lang.annotation.*;

@Aspect
public class Audience { 
   
    @Pointcut("execution(**concert.Performance.perform(..))")
    public void performance() { 
   }

    @Before("performance()")
    public void silence(){ 
   
        System.out.println("please close your phone!");
    }

    @Before("performance()")
    public void seatDown(){ 
   
        System.out.println("Taking seats");
    }

    @AfterReturning("performance()")
    public void applause(){ 
   
        System.out.println("CLAP CLAP CLAP");
    }

    @AfterThrowing("performance()")
    public void demandRefund(){ 
   
        System.out.println("Demanding a refund");
    }

    @Around("performance()")
    // 被通知的方法需要注入其中
    public void watcherPerformance(ProceedingJoinPoint jp) { 
   
        try { 
   
            System.out.println("before");
            jp.proceed();
            System.out.println("afterReturning");
        } catch (Throwable e){ 
   
            System.out.println("afterthrowing");
        }
    }
}

<?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">
    <bean id="audience" class="concert.Audience"/>
    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..))"/>
            <aop:before pointcut-ref="performance" method="silence"/>
            <aop:before pointcut-ref="performance" method="seatDown"/>
            <aop:after-returning pointcut-ref="performance" method="applause"/>
            <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
        </aop:aspect>
        
    </aop:config>
</beans>

一、重要概念

1. 切点

指定的连接点

2. 通知

要做的事情以及时间,如before,after等

3. 切面

包含了通知,切点以及何时做的信息

4. 连接点

所有被标记的可以连接的点,相当于候选切点

5. 引入

可以进行类的包装,例如一个剧场类,如果剧场有返场演出类,那么不是所有的剧场类都要添加返场类,因此可以使用引入,使用切面功能向特定剧场类添加返场类

6. 织入

spring运行aop的时期和AspectJ运行的时期不同,由于运行时期不同,因此spring只支持方法的切点,而Aspect支持构造器方法的切点,甚至字段切点

二、创建切点

创建一个演出接口

package concert;

public interface Performance { 
   
    public void perform();
}

演出前后需要让观众安静并就坐,演出成功后观众鼓掌。因此将演出作为切点,并在其前后设置安静就坐和鼓掌方法

"execution(* concert.Performance.perform(..)) && within(concert)"
    

这个语句指明了切点执行的地方,以及within限制了切点所在的位置

*表示任意的类型,… 表示perform的任意参数,也就是切点是所有在concert包中实现了perfrom的方法

在xml文件中有些不同,&& 变为 and, ||变为 or ,! 变为 not

<aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..)) and !bean('cd')"/>

三、创建切面

前后通知

观众并非演出类的关键点,而且其可以应用到任何一个演出类上,因此是一个很好的切面

package concert;

import org.aspectj.lang.annotation.*;

@Aspect
public class Audience { 
   
    // 定义一个共同切点
    @Pointcut("execution(* concert.Performance.perform(..))")
    public void performance(){ 
   }// 该方法只是一个标志,供pointcut注释依附
    
    @Before("performance()")// 将在调用perfrom之前使用该方法
    public void silence(){ 
   
        System.out.println("please close your phone!");
    }
    
    @Before("performance()")
    public void seatDown(){ 
   
        System.out.println("Taking seats");
    }
    @AfterReturning("performance()")// 在perfrom方法完成之后
    public void applause(){ 
   
        System.out.println("CLAP CLAP CLAP");
    }
    @AfterThrowing("performance()")// perfrom异常后调用该方法
    public void demandRefund(){ 
   
        System.out.println("Demanding a refund");
    }
}

抛去注释来看,该方法仍为一个POJO类,所以也可以变成Spring中的bean

@Bean
public Audience audience(){ 
   
    return new Audience();
}

如果使用javaConfig进行配置项目的话

package concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy //使用该注释启用AspectJ的自动配置
@ComponentScan
public class ConcertConfig { 
   

    @Bean
    public Audience audience(){ 
   
        return new Audience();
    }
}
	<aop:aspectj-autoproxy/>
	<bean class="concert.Audience"/>

添加自动代理功能后,AspectJ自动代理会为使用了Apect注解的bean创建一个代理,这个代理会围绕所有该切面的切点所匹配的bean。这样就会为Concernt bean创建一个代理,Audience类中的通知方法将会在perform调用前后执行。

虽然使用了Aspect的注释,但调用仍然是spring的代理,而非直接调用的AspectJ功能,必须进行直接调用

环绕通知

around注释将以perfrom为目标,可以将前后通知放在同一个方法里面

    @Around("performance()")
    public void watchPerformance(Concert concert){ 
   // 包裹被通知的方法
        try { 
   
            System.out.println("before");
            concert.perform();// 被通知的方法需要进行某种行为,否则会阻塞
            System.out.println("afterReturning");
        } catch (Throwable e){ 
   
            System.out.println("afterThrowing");
        }
    }

处理参数

package CD;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.HashMap;
import java.util.Map;

@Aspect
public class TrackCounter { 
   

    private Map<Integer, Integer> trackCounts = new HashMap<>();

    @Pointcut("execution(* CD.CompactDisc.playTrack(int))" + "&& args(track)")
    public void setTrack(int track){ 
   }

    @Before("setTrack(track)") // 切点参数为track
    public void countTrack(int track){ 
   
        int currentTrack= getPlayCount(track);
        trackCounts.put(track, currentTrack+1);
    }

    private int getPlayCount(int track){ 
   
        return trackCounts.containsKey(track)?trackCounts.get(track):0;
    }
}

四、创建介入

如果想要添加新的方法,而又不想对原有的类进行修改,可以使用aop介入方法

因为切面就是一个封装了原有类的代理,向切面中添加方法, 可以达成向原有类添加方法的效果

例如我们向Concert类添加返场功能,并非所有的音乐会都有返场,因此可以通过创建介入切面实现

package concert;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EncoreableIntroducer { 
   
    @DeclareParents(value = "concert.Performance+", // 哪种类型的bean将要使用该类型
            defaultImpl = DefaultEncoreable.class) // 使用哪种方法实现
    public static Encoreable encoreable;// 静态属性指明了要引入的方法的接口类型
}
  1. value 属性指定了哪种类型的bean要引入该接口。在本例中就是所有实现Performance的类型。加号表示是Performance的所有子类型,而非其本身
  2. defaultImpl 属性指定了为引入功能提供实现的类,例如DefaultEncoreable类实现了返场功能
  3. @DeclarParents 所注解的静态属性指明了要引入功能的接口,这里所引入的是Encoreable接口

和其他切面一样,需要将其声明为一个bean

<bean class="concert.EncoreableIntroducer"/>

spring的自动代理机制将会获取到它的声明,当spring发现该bean使用了aspect注解时,就会创建一个切面自动代理,然后执行引入功能。

此功能虽然简单,但是必须知道通知类的源码,才能进行注释的修改,xml提供了更直接的方法

五、在XML中声明切面

javaConfig虽然优秀,但是需要改动源码,如果不能向代码添加注解,就需要使用xml进行配置了

重新对Audience类进行配置

package concert;

public class Audience { 
   
// audience删除注解后,其实就是个POJO,但已经可以进行切面操作了
    public void silence(){ 
   
        System.out.println("please close your phone!");
    }

    public void seatDown(){ 
   
        System.out.println("Taking seats");
    }

    public void applause(){ 
   
        System.out.println("CLAP CLAP CLAP");
    }

    public void demandRefund(){ 
   
        System.out.println("Demanding a refund");
    }
}
<bean id="audience" class="concert.Audience"/>
<bean id="encoreableDelegate" class="concert.DefaultEncoreable"/>
    <aop:config> <!--开始配置切面-->
        <aop:aspect ref="audience"><!--切面类指定为audience bean-->
            <!--定义切点-->
            <aop:pointcut id="performance" expression="execution(* concert.Performance.perform(..)) and !bean('audience')"/>
            <!--定义前置方法 pointcut-ref指定引用的切点定义 method指定引用的POJO方法-->
            <aop:before pointcut-ref="performance" method="silence"/>
            <aop:before pointcut-ref="performance" method="seatDown"/>
            <aop:after-returning pointcut-ref="performance" method="applause"/>
            <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
            <aop:around pointcut-ref="performance" method="encoreable"/>
            <!--切面引入新方法-->
            <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" default-impl="concert.DefaultEncoreable" delegate-ref="encoreableDelegate"/> 使用ref引用被封装为bean的实现类
        </aop:aspect>
    </aop:config>

带参数的切面

package CD;

import java.util.HashMap;
import java.util.Map;

public class TrackCounter { 
   

    private Map<Integer, Integer> trackCounts = new HashMap<>();

    public void setTrack(int track){ 
   }
    
    public void countTrack(int track){ 
   
        int currentTrack= getPlayCount(track);
        trackCounts.put(track, currentTrack+1);
    }

    private int getPlayCount(int track){ 
   
        return trackCounts.containsKey(track)?trackCounts.get(track):0;
    }
}
<?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:c="http://www.springframework.org/schema/c" 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">

    <bean id="cd" class="CD.BlankDisc">
        <property name="artist" value="王菲"/>
        <property name="title" value="浮躁"/>
        <property name="tracks">
            <list>
                <value>浮躁</value>
                <value>无常</value>
                <value>分裂</value>
            </list>
        </property>
    </bean>

    <bean id="cdPlayer" class="CD.CDPlayer">
        <constructor-arg name="cd" ref="cd"/>
    </bean>

    <bean id="trackCounter" class="CD.TrackCounter"/>

    <aop:config>
        <aop:aspect ref="trackCounter">
            <aop:pointcut id="trackPlayed" expression="execution(* CD.TrackCounter.countTrack(int)) and args(track)" />
            <aop:before pointcut-ref="trackPlayed" method="countTrack"/>
        </aop:aspect>
    </aop:config>
</beans>

六、注入AspectJ切面

由于java构造器的特殊性,spring基于代理的aop不能把通知应用于对象的创建过程,因此需要使用AspectJ

在实现切面的时候,可能需要多个类进行协作,这样就可以使用spring将其封装为bean,利用依赖注入来管理切面

这时候新建一个评论家类,评论家独立于演出,并且演出结束后会给出意见

package concert;

public aspect CriticAspect { 
   
    public CriticAspect(){ 
   }

    // 定义切点
    pointcut performance() : execution(* concert.Performance.perfrom(..));

    after() : performance() { 
   // 切点方法结束之后执行方法体中的内容
        System.out.println(criticismEngine.getCriticism());
    }

    private CriticismEngine criticismEngine;
    // 通知方法所需使用的POJO类,使用setter方法减少和切面之间的耦合
    public void setCriticismEngine(CriticismEngine criticismEngine) { 
   
        this.criticismEngine = criticismEngine;
    }
}
package concert;

public interface CriticismEngine { 
   
    public String getCriticism();
}

package concert;

public class CriticismEngineImpl implements CriticismEngine { 
   
    @Override
    public String getCriticism() { 
   
        int i = (int) (Math.random()*criticismPool.length);
        return criticismPool[i];
    }

    private String[] criticismPool;

    // 要注入的方法,注入评论池
    public void setCriticismPool(String[] criticismPool){ 
   
        this.criticismPool = criticismPool;
    }
}

spring bean由spring容器初始化,而aspect切面由aspect在运行时创建。等spring有机会为评论家切面注入评论池的时候,其已经被实例化了。因为spring不能负责评论家切面的创建,也就不能把aspect简单的声明为一个bean。所以可以通过factory-method的静态aspectOf方法获得一个aspect单例。

spring不能简单使用bean来创建一个切面实例,而是需要对由aspectJ创建的切面实例进行引用,也就是通过aspectOf工厂方法进行引用,然后执行注入。

    <bean class="concert.CriticAspect" factory-method="aspectOf">
        <property ref="criticismEngines" name="criticismEngine"/>
    </bean>

今天的文章spring面向切面编程的原理_什么叫切面分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/84153.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注