Spring面向切面编程详解

栏目: Java · 发布时间: 8年前

内容简介:Spring面向切面编程详解

一、概念

1、理论

把横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或 组成(delegation)。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用组成可能需要对委托对象进行复杂的调用。切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。Spring AOP 基于动态代理,所以Spring只支持方法连接点, 这与一些其他的AOP 框架是不同的,例如 AspectJ JBoss ,除了方法切点,它们还提供了字段和构造器接入点。 

2、AOP术语

横切关注点(cross-cutuing concern):散布在应用中多处的功能。

Spring面向切面编程详解

切面(aspect) :横切关注点模块化为特殊的类。切面是通知和切点的结合。

通知(advice):定义了切面是什么以及何时使用。

Spring切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能;

后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;

返回通知(After-returning):在目标方法成功执行之后调用通知;

异常通知(After-throwing):在目标方法抛出异常后调用通知;

环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

切点(pointcut):定义了切面在何处调用,会匹配通知所要织入的一个或多个连接点。

连接点(join point):在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。

Spring面向切面编程详解

织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

织入有三种方式可以实现,Spring采用的是第三种,在运行期织入的:

编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。

类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。

运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。SpringAOP就是以这种方式织入切面的。

Spring面向切面编程详解

3、AspectJ的切点表达式语言

Spring面向切面编程详解

Spring面向切面编程详解

注意: 只有 execution 指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说 execution 指示器是我们在编写切点定义时最主要使用的指示器 。同时需要注意的是, 表达式之间允许用 &&(and)、||(or)、!(not) 来匹配复杂的被通知类。除了上面罗列的表达式外,Spring 还提供了一个Bean 表达式来匹配 Bean 的id,例如  execution(* com.service.Performance.perform(..)) && bean(performance)

@args的正确用法:自定义一个ElementType.TYPE的注解,这个注解用来修饰自定义类型(比如自己写的一个类),一个方法以这个自定义的类的实例为参数且只能有这唯一一参数,那这个方法在调用时会被匹配@args(自定义注解)的切面拦截。

@annotation的正确用法:在切面类上用@annotation加自定义注解就可以拦截使用这个注解的方法。

@target (cn.javass.spring.chapter6.Secure)  任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

Spring面向切面编程详解

二、使用注解创建切面

1、定义切面

Spring面向切面编程详解
@Aspect //表示这是一个切面类
public class Audience {

    //使用简明的PointCut
    @Pointcut("execution(* com.service.Performance.perform(..))")
    public void performance(){}
    
    //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
    @Before("performance()")
    public void  silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
    @Before("performance()")
    public void takeSeats(){
        System.out.println("Taking seats");
    }

    //方法调用结束通知(并不是指返回值通知,即使是void的返回值,仍然会触发通知) 即 @AfterReturning("execution(* com.service.Performance.perform(..))")
    @AfterReturning("performance()")
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }

    //有异常抛出的时候通知,即 @AfterThrowing("execution(* com.service.Performance.perform(..))")
    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}

2、 启用 AspectJ 注解的自动代理 

有两种方式可以启用AspectJ 注解的自动代理:

(1)在 Java 配置文件中显示配置

Spring面向切面编程详解
@Configuration
@EnableAspectJAutoProxy //启用Aop自动代理
public class JavaConfig {
    @Bean
    public Audience getAudience(){
        return new Audience();
    }
}

(2)在XML文件中配置

Spring面向切面编程详解
<!--启用AspectJ自动代理-->
    <aop:aspectj-autoproxy/>
    <bean id="audience" class="com.aspect.Audience"/>

不管你是使用 JavaConfig 还是 XML AspectJ 自动代理都会为使用 @Aspect 注解的 bean 创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。当程序执行到连接点的时候,就会由代理转到切面触发相应的通知。

3、创建环绕通知

Spring面向切面编程详解
@Aspect
public class Audience3 {

    @Pointcut("execution(* com.service.Performance.perform(..))")
    public void performance(){}

    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinPoint) {
        System.out.println("Silencing cell phones");
        System.out.println("Taking seats");
        try {
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
            throwable.printStackTrace();
        }
    }
}

注意  ProceedingJoinPoint  作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。当要将控制权交给被通知的方法时,它需要调用 ProceedingJoinPoint proceed() 方法。 

4、切面匹配输入参数

Spring面向切面编程详解
@Aspect
public class TrackCounter {
    
    private Map<Integer,Integer> trackCounts=new HashMap<Integer, Integer>();

    //@Pointcut("execution(* com.service.CompactDisc.playTrack(int)) && args(trackNumber)") //带入输入参数
    //@Pointcut("target(com.service.CompactDisc) && args(trackNumber)") // target 匹配目标对象(非AOP对象)为指定类型
    //@Pointcut("within(com.service..*) && args(trackNumber)") //com.service 包以及子包下的所有方法都执行
    //@Pointcut("within(com.service..CompactDisc+) && args(trackNumber)") //com.service 包的CompactDisc类型以及子类型
     @Pointcut("this(com.service.CompactDisc) && args(trackNumber)") //匹配当前AOP代理对象类型,必须是类型全称,不支持通配符
     public void trackPlayed(int trackNumber){}

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber){
        int playCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber,playCount+1);
        System.out.println(trackCounts.toString());
    }

    public int getPlayCount(int trackNumber){
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}

5、利用切面注入新功能

Java 并不是动态语言。一旦类编译完成了,我们就很难为该类添加新的功能了。但是,我们的切面编程却可以做到动态的添加方法...话虽如此,其实也不过是障眼法罢了。实际上,面向切面编程,不过是把方法添加到切面代理中,当要对添加的方法调用的时候,可以把被通知的 Bean 转换成相应的接口。也就是 代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个 bean 的实现被拆分到了多个类中。(说实话,想了半天,实在想不到这个功能有什么作用......)

Spring面向切面编程详解

(1) 重新定义一个接口和实现类

Spring面向切面编程详解
public interface Encoreable {
    void performEncode();
}
Spring面向切面编程详解
public class DefaultEncoreable implements Encoreable {
    
    public void performEncode() {
        System.out.println("this is DefaultEncoreable");
    }
}

(2) 把接口实现类嵌入到目标类代理中

Spring面向切面编程详解
Spring面向切面编程详解
@Aspect
public class EncoreableIntroducer {
    
    @DeclareParents(value = "com.service.CompactDisc+",
              defaultImpl = DefaultEncoreable.class) //value 表示要嵌入哪些目标类的代理 。 defaultImpl:表示要嵌入的接口的默认实现方法
    public static Encoreable encoreable;
}

(3) JUnit 测试

Spring面向切面编程详解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test02 {

    @Autowired
    private CompactDisc compactDisc;
    
    @Test
    public void test02(){
        compactDisc.playTrack(123);
        Encoreable compactDisc = (Encoreable) this.compactDisc; //当要调用添加的新功能的时候,这个用法相当于由代理转换到对应类实现,不会报类型转换错误
        compactDisc.performEncode();

    }
}

Spring面向切面编程详解

三、使用XML声明切面

Spring面向切面编程详解

Spring面向切面编程详解

1、定义切面

Spring面向切面编程详解
public class AudienceXML {
    
    public void  silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    public void takeSeats(){
        System.out.println("Taking seats");
    }
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}

2、XML配置切面

Spring面向切面编程详解
<aop:config>
        <aop:aspect ref="audienceXML">
            <aop:pointcut id="performance" expression="execution(* com.service.Performance.perform(..))"/>
            <aop:before method="silenceCellPhones" pointcut-ref="performance"/>
            <aop:before method="takeSeats" pointcut-ref="performance"/>
            <aop:after-returning method="applause" pointcut-ref="performance"/>
            <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>

3、创建环绕通知

Spring面向切面编程详解
public class Audience3XML {
    
    public void watchPerformance(ProceedingJoinPoint joinPoint) {
        System.out.println("Silencing cell phones");
        System.out.println("Taking seats");
        try {
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
            throwable.printStackTrace();
        }
    }
}
Spring面向切面编程详解
<aop:config>
        <aop:aspect ref="audience3XML">
            <aop:pointcut id="performance3" expression="execution(* com.service.Performance.perform(..))"/>
            <aop:around method="watchPerformance" pointcut-ref="performance3"/>
        </aop:aspect>
    </aop:config>

4、匹配输入参数

<aop:config>
        <aop:aspect ref="trackCounter">
            <aop:pointcut id="trackPlayed" expression="execution(* com.service.CompactDisc.playTrack(int)) and args(trackNumber)"/>
            <aop:before method="countTrack" pointcut-ref="trackPlayed"/>
        </aop:aspect>
    </aop:config>

5、注入新功能

<aop:config>
        <aop:aspect>
            <aop:declare-parents types-matching="com.service.CompactDisc+"
                                 implement-interface="com.service.Encoreable"
                                 default-impl="com.service.impl.DefaultEncoreable"
                                 delegate-ref="encoreableDelegate"/>

        </aop:aspect>
    </aop:config> 

本文永久更新链接地址 http://www.linuxidc.com/Linux/2017-12/149783.htm


以上所述就是小编给大家介绍的《Spring面向切面编程详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning JavaScript

Learning JavaScript

Shelley Powers / Oreilly & Associates Inc / 2006-10-17 / $29.99

As web browsers have become more capable and standards compliant, JavaScript has grown in prominence. JavaScript lets designers add sparkle and life to web pages, while more complex JavaScript has led......一起来看看 《Learning JavaScript》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具