面向切面的Spring

前言

在软件开发中,散布于应用中多处的功能被称为横切关注点。这些横切关注点从概念上是与应用的业务逻辑相分离的。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。AspectJ---另外一种流行的AOP实现。 若是是要重用通用功能的话,最多见的面向对象技术是继承或委托。可是,若是在整个应用中都使用相同的基类,继承每每会致使一个脆弱的对象体系;而使用委托可能须要对委托对象进行复杂的调用。正则表达式

AOP

通知(Advice):定义了切面是什么以及什么时候使用。除了描述切面要完成的工做,通知还解决了什么时候执行这个工做的问题。spring

Spring切面能够应用五种类型的通知:express

  • 前置通知(Before):在目标方法被调用以前调用通知功能。
  • 后置通知(After):在目标方法完成以后调用通知,次数不会关心方法的输出是什么。
  • 返回通知(After-returning):在目标方法成功执行以后调用通知。
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知。
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用以前和调用以后执行自定义的行为。

链接点(Join Point):在用于执行过程当中可以插入切面的一个点。切面代码能够利用这些点插入到应用的正常流程中,并添加新的行为。编程

切点(PointCut):切点的定义会匹配通知全部织入的一个或多个链接点。一般使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。maven

切面(Aspect):切面是通知和切点的结合,通知和切点共同定义了切面的所有内容--它是什么,在什么时候和何处完成其功能。ide

引入(Introduction):引入容许咱们向现有的类添加新方法或属性。post

织入(Weaving):织入是把切面应用到目标对象并建立新的代理对象的过程。切面在指定的链接点被织入到目标对象中。在目标对象的生命周期里有多个点能够进行织入:ui

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

2.类加载期:切面在目标类加载到JVM时被织入。这种方式须要特殊的类加载器(ClssLoader),他能够在目标类被引入应用以前加强该目标类的字节码。this

3.运行期:切面在应用运行的某个时刻被织入。通常状况下,在织入切面时,AOP容器会为目标对象动态地建立一个代理对象。spa

Spring对AOP的支持

Spring提供了4中类型的AOP支持。 1.基于代理的经典SpringAOP。

2.纯POJO切面。

3.@AspectJ注解驱动的切面。

4.注入式AspectJ切面

经过切点来选择链接点

1.编写切点

首先:定义一个HelloWorld接口:

public interface HelloWorld
{
    void printHelloWorld();
    void doPrint();
}

切点的配置

execution(* com.xrq.aop.HelloWorld.(..)) 切点表达式。execution 在方法执行时触发, com.xrq.aop.HelloWorld.(..) 筛选制定的方法。 com.xrq.aop.HelloWorld.(..)) 是链接点。??

<aop:config>
            <aop:aspect id="time" ref="timeHandler">
                <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
                <aop:before method="printTime" pointcut-ref="addAllMethod" />
                <aop:after method="printTime" pointcut-ref="addAllMethod" />
            </aop:aspect>
        </aop:config>

2.使用注解建立切面

使用@AspectJ注解进行了标注,该注解代表 Advices 不只是一个POJO类,也是一个切面。 使用@PoinCut定义命名的切点,能够简化重复的代码。

package com.zhangguo.Spring052.aop02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 *
 */
@Component
@Aspect
public class Advices {
    @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }
    
    @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
}

2.JavaConfig启用切面的代理 在配置类的级别上经过使用@EnableAspectJAutoProxy 注解启动代理功能。

package com.zhangguo.Spring052.aop05;

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  //用于表示当前类为容器的配置类,相似<beans/>
@ComponentScan(basePackages="com.zhangguo.Spring052.aop05")  //扫描的范围,至关于xml配置的结点<context:component-scan/>
@EnableAspectJAutoProxy(proxyTargetClass=true)  //自动代理,至关于<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
public class ApplicationCfg {
    //在配置中声明一个bean,至关于<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/>
    @Bean
    public User getUser(){
        return new User();
    }
}

3.xml装配Bean,启动注解: aspectj-autoproxy 元素声明aop的启动。

<?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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
        <context:component-scan base-package="com.zhangguo.Spring052.aop02">
        </context:component-scan>
        <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>

环绕通知 Around

环绕通知接受 ProceedingJoinPoint做为参数。这个对象是必需要有的,由于你要在通知中经过它来调用被通知的方法。通知方法中能够作任何的事情,当要将控制权交给被通知的方法时,它须要调用ProceedingJoinPoint的Proceed()方法。

若是忘记调用Proceed()方法,通知实际上会阻塞对被通知方法的调用。也能够在通知中对它进行多长调用,实现充实逻辑。

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 通知类,横切逻辑
 */
@Component
@Aspect
public class Advices {
    //切点
    @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))")
    public void pointcut(){
    }
    
    //前置通知
    @Before("pointcut()")
    public void before(JoinPoint jp){
        System.out.println(jp.getSignature().getName());
        System.out.println("----------前置通知----------");
    }
    
    //最终通知
    @After("pointcut()")
    public void after(JoinPoint jp){
        System.out.println("----------最终通知----------");
    }
    
    //环绕通知
    @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println(pjp.getSignature().getName());
        System.out.println("----------环绕前置----------");
        Object result=pjp.proceed();
        System.out.println("----------环绕后置----------");
        return result;
    }
    
    //返回结果通知
    @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result")
    public void afterReturning(JoinPoint jp,Object result){
        System.out.println(jp.getSignature().getName());
        System.out.println("结果是:"+result);
        System.out.println("----------返回结果----------");
    }
    
    //异常后通知
    @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp")
    public void afterThrowing(JoinPoint jp,Exception exp){
        System.out.println(jp.getSignature().getName());
        System.out.println("异常消息:"+exp.getMessage());
        System.out.println("----------异常通知----------");
    }
}

4 经过注解引入新功能

@DeclareParents注解:

  • value 属性指定了那种类型的bean要引入该接口。
  • DefaultImpl 属性指定了为引入功能提供实现的类。
  • @DeclareParents注解所标注的静态属性指明了要引入的接口。

面向注解的切面声明有一个明显的劣势:你必须可以为通知类添加注解。若是你没有源码的话,或者不想将AspectJ注解放入到代码中,SpringXml能够实现。

4.4 在xml中声明切面

优先的原则:基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XMl的配置。 基于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:p="http://www.springframework.org/schema/p"
    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-4.3.xsd">
       
    <!-- 被代理对象 -->
    <bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean>
    
    <!-- 通知 -->
    <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean>
    
    <!-- aop配置 -->
    <aop:config proxy-target-class="true">
        <!--切面 -->
        <aop:aspect ref="advices">
            <!-- 切点 -->
            <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/>
            <!--链接通知方法与切点 -->
            <aop:before method="before" pointcut-ref="pointcut1"/>
            <aop:after method="after" pointcut-ref="pointcut1"/>
        </aop:aspect>
    </aop:config>

</beans>

注入AspectJ切面

当咱们须要建立更细粒度的通知或想监测bean的建立时,Spring所支持的AOP就比较弱了,这时,能够选择使用AspectJ提供的构造器切点,而且能够借助Spring的依赖注入把bean装配进AspectJ切面中。下面就来举个栗子:

//定义表演接口
package concert;
public interface Performance {
    void perform(); 

    void finishPerform(String performer, String title); 
}
//定义钢琴表演
package concert;
public class PianoPerform implements Performance {
    public PianoPerform(){
        System.out.println("有请钢琴表演");
    }

    @Override
    public void perform() {
        System.out.println("钢琴表演开始");
    }

    @Override
    public void finishPerform(String performer, String title) {
        System.out.println(performer + "演奏钢琴曲:" + title);
    }
}
//定义小提琴表演
package concert;
public class ViolinPerform implements Performance {
    public ViolinPerform(){
        System.out.println("有请小提琴表演");
    }

    @Override
    public void perform() {
        System.out.println("小提琴表演开始");
    }

    @Override
    public void finishPerform(String performer, String title){
        System.out.println(performer + "演奏了小提琴曲:" + title);
    }   
}
//定义工做人员,将做为切面的协做bean
package concert;
public class Worker {
    public void take(){
        System.out.println("观众已所有交出手机");
    }

    public void sendMsg(String name){
        System.out.println(name + "表演即将开始,请各位观众交出手机");
    }

    public void broadcast(String performer, String title){
        System.out.println(performer + "演奏完毕,刚才演奏的曲子叫:" + title);
    }
}
//定义切面
package concert;
public aspect Audience {    
    private Worker worker;

    public Audience(){}

    //经过setter方法注入
    public void setWorker(Worker worker){
        this.worker = worker;
        System.out.println("工做人员已入场");
    }

    //定义piano构造器切点和后置通知
    pointcut piano():execution(concert.PianoPerform.new());
    after():piano(){
        worker.sendMsg("钢琴");
    }

    //定义violin构造器切点和后置通知
    pointcut violin():execution(concert.ViolinPerform.new());
    after():violin(){
        worker.sendMsg("小提琴");
    }

    //定义不带参数方法切点和前置通知
    pointcut perform():execution(* concert.Performance.perform());
    before():perform(){
        worker.take();
    }

    //定义带两个参数的切点和后置通知
    pointcut finishPerform(String performer, String title):execution(* concert.Performance.finishPerform(String, String)) && args(performer, title);
    after(String performer, String title):finishPerform(performer, title){
        worker.broadcast(performer, title);
    }
}

XML配置文件:spring.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--<bean id="piano" class="concert.PianoPerform" lazy-init="true"/>-->

    <bean id="worker" class="concert.Worker"/>

    <!--Spring须要经过静态方法aspectOf得到audience实例,Audience切面编译后的class文件附在文末-->
    <bean class="concert.Audience" factory-method="aspectOf">
        <property name="worker" ref="worker" /><!--经过Spring把协做的bean注入到切面中-->
    </bean>

    <!--这里注意一下bean的顺序,由于在构造器切点后置通知时调用了worker的sendMsg(String)方法,因此避免出现空指针异常,我们先把worker声明在前-->
    <!--若是要将piano或者violin声明在前,能够设置lazy-init="true"-->
    <!--因此spring是从上到下解析并实例化bean?仍是解析完整个文件再实例化呢?欢迎评论区留言交流-->

    <bean id="piano" class="concert.PianoPerform"/>

    <bean id="violin" class="concert.ViolinPerform"/>
</beans>
//主程序
package concert;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainClass {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");

        Performance piano = context.getBean("piano", Performance.class);
        piano.perform();
        piano.finishPerform("亚莎·海菲兹", "致爱丽斯");

        Performance violin = context.getBean("violin", Performance.class);
        violin.perform();
        violin.finishPerform("霍洛维茨", "爱之喜悦");
    }

}
<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>       
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>        
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
        <!--编译aspect的插件,必需要将Audience.aj编译为class文件,
            否则spring建立audience bean的时候找不到类-->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.8</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <complianceLevel>1.8</complianceLevel>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

Audience.class文件

package concert;

import concert.Worker;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class Audience {
    private Worker worker;

    static {
        try {
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }

    }

    public void setWorker(Worker worker) {
        this.worker = worker;
        System.out.println("工做人员已入场");
    }

    public Audience() {
    }

    @After(
        value = "piano()",
        argNames = ""
    )
    public void ajc$after$concert_Audience$1$dd71540a() {
        this.worker.sendMsg("钢琴");
    }

    @After(
        value = "violin()",
        argNames = ""
    )
    public void ajc$after$concert_Audience$2$57c630b6() {
        this.worker.sendMsg("小提琴");
    }

    @Before(
        value = "perform()",
        argNames = ""
    )
    public void ajc$before$concert_Audience$3$1cad9822() {
        this.worker.take();
    }

    @After(
        value = "finishPerform(performer, title)",
        argNames = "performer,title"
    )
    public void ajc$after$concert_Audience$4$1840cdb9(String performer, String title) {
        this.worker.broadcast(performer, title);
    }

    //Aspect提供的静态方法,返回Audience切面的一个实例,Spring便可经过factory-method属性得到该实例
    //<bean class="concert.Audience" factory-method="aspectOf">
    public static Audience aspectOf() {
        if(ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("concert_Audience", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }

    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
}

写在最后: Spring所支持的AOP已经能够知足不少需求,若是要求更高,可使用AspectJ提供的更丰富的切点类型,固然须要熟悉AspectJ语法。 本文只是简单的举出了部分切点类型和通知类型,更多的类型读者能够自行尝试。欢迎留言指正,感谢您的阅读!

相关文章
相关标签/搜索