SpringInAction:在Spring中应用切面

面向切面的Spring

散布于应用多处的功能被称为横切关注点,这些横切关注点从概念上讲是与应用的业务逻辑相分离的,把这些横切关注点与业务逻辑相分离正式面向切面编程所要解决的问题

切面实现了横切关注点的模块化(横切关注点可以被简单描述为影响应用多处的功能),在重用通用功能上,切面提供了取代继承和委托的另一种可选方案。


AOP术语

  • 通知 : 切面的工作被称为通知

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

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

  • 切点(Poincut)

    如果说通知定义了切面”什么”和”何时”的话,那么切点就定义了”何处”。切点的定义会匹配通知所要织入的一个或多个连接点

  • 切面(Aspect)

    切面时=是通知和切点的结合。通知和切点共同定义了切面的全部内容,他是什么,在何时和何处完成其功能。

  • 引入(Introduction)

    引入允许我们向现有的类添加新方法和属性

  • 织入(Weaving)

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


Spring对AOP的支持

Spring提供了四种类型的AOP支持

  • 基于代理的经典SpringAOP
  • 纯POJO切面
  • @AspectJ 注解驱动的切面
  • 注入式AspectJ切面

前三种都是SPringAOP实现的变体,SpringAOP构建在动态代理的基础之上,因此Spring对AOP的支持局限于方法拦截

借助Spring的aop命名空间,可将纯POJO转换为切面,但需要xml配置,而注解驱动的AOP借鉴了AspectJ的切面能够不用xml来完成功能

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中,直到应用需要被代理的bean时,Spring才会创建代理对象

Spring只支持方法级别的连接点

通过切点来选择连接点

Spring 所支持AspectJ切点指示器

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

Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标示bean

编写切点
package concert;

public interface Performance {
    public void perform();
}
编写Performance的perform方法执行时触发通知的调用

execution(* concert.Performance.perform(...))

execution 表示在方法执行时触发
* 表示返回任意类型
后面是方法所属的类和方法名


可以用&&连接芝士切 类似还有|| 和 !
在xml中用and or not代替
execution(* concert.Performance.perform(...)) && within(concert.*)

Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标示bean,使用beanID 或bean名称作为参数来限制切点只匹配特定的bean

execution(* concert.Performance.perform(...)) and bean('woodstock')

execution(* concert.Performance.perform(...)) and !bean('woodstock')

使用注解创建切面

注解通知
@After通知方法会在目标方法返回或抛出异常后调用
@AfterReturning通知方法会在目标方法返回后调用
@AfterThrowing通知方法会在目标方法抛出异常后调用
@Around通知方法会将目标方法封装起来
@Before通知方法会在目标方法调用之前执行
package concert;

@Aspect
public class Audience{
    @Before("execution(* concert.Performance.perform(...))")
    public void silenceCellPhones(){
        System.out.println("Silence cell phones");
    }

    @Before("execution(* concert.Performance.perform(...))")
    public void takeSeats(){
        System.out.println("Taking Seats");
    }

    @AfterReturning("execution(* concert.Performance.perform(...))")
    public void applause(){
        System.out.println("Clap,Clap");
    }

    @AfterThrowing("execution(* concert.Performance.perform(...))")
    public void silenceCellPhones(){
        System.out.println("Demanding a refund");
    }


}

以上有重复的execution 切点
可以用@Pointcut注解在切面内定义一个可重用切点

package concert;

@Aspect
public class Audience{

    @Pointcut("execution(* concert.Performance.perform(...))")
    public void performance(){}
        @Before("performance()")
    public void silenceCellPhones(){
        System.out.println("Silence cell phones");
    }

    @Before("perfromance()")
    public void takeSeats(){
        System.out.println("Taking Seats");
    }

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

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


}


Audience可以被装配为Spring中的bean


@Configuration
@EnableAspectJAutoProxy //启用AspectJ自动代理
@ComponentScan
public class ConcertConfig{

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


或者在xml中
<beans ...>
    <aop:aspectj-autoproxy />
    <bean class="concert.Audience" />
</beans>
创建环绕通知
package concert;

@Aspect
public class Audience{

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

    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint jp){
        try{
            System.out.println("Silencing");
            System.out.println("Taking Seats");
            jp.proceed();
            System.out.println("CLAP,CLAP");

        }
        catch(Throwable e){
            System.out.println("Demanding a refound");
        }
    }


}

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

如 CD磁道的播放次数与播放本身是不同的关注点,因此要用切面来记录播放次数而不是在播放方法中记录

package soundsystem;
public interface CompactDisc{

    void play();
    void playTrack(int number);

}

public class BlankDisc implements CompactDisc {


    private String title;
    private String artist;
    private List<String> tracks;



    public void setTitle(String title) {
        this.title = title;
    }



    public void setArtist(String artist) {
        this.artist = artist;
    }



    public void setTracks(List<String> tracks) {
        this.tracks = tracks;
    }

    public void playTrack(int number){
        System.out.println(tracks.get(i));
    }

    @Override
    public void play() {
        // TODO Auto-generated method stub
        for(int i = 0;i < tracks.size();i++){
            playTrack(i);
        }
    }

}



package soundsystem

@Aspect
public class TrackCounter{
    private Map<Integer,Integer> trackCounts = new HashMap<Integer,Integer>();

    @Pointcut("execution(* soundsystem.CompactDisc.playTrack(int))" + "&& args(trackNumber)")
    public void trackPlayed(int trackNumber){}

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber){
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber,currentCount + 1);
    }

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

}

args(trackNumber) 表明传递给playTrack()方法的int类型参数也会传递到通知中去,参数的名称trackNumber也与切点方法签名中的参数想匹配
这个参数会传递到通知方法中,这个通知方法是通过@Before注解和命名切点trackPlayed(trackNumber)定义的。
切点定义中的参数与切点方法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数转移


package soundsystem;

@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig{
    @Bean
    public CompactDisc sgtPeppers(){
        BlankDisc cd = new BlankDisc();
        cd.setTitle("...");
        cd.setArtist("...");
        List<String> tracks = new ArrayList<String>();
        tracks.add(...);
        tracks.add(...);
        tracks.add(...);

        cd.setTracks(tracks);
        return cd;

    }

    @Bean
    public TrackCounter trackCounter(){
        return new TrackCounter();
    }

}
通过注解引入新功能

利用被称为引入的AOP概念,切面可以为SPring bean添加新方法
在Spring中,切面只是实现了他们所包装bean相同接口的代理,如果除了实现这些接口,代理也能暴露新接口 切面所通知的bean看起来就像是实现了新的接口,即便底层实现类并没有实现这些接口也无所谓

当引入接口的方法被调用时,代理会把此调用委托给实现了新街口的某个其他对象,实际上,,一个bean的实现被拆分到多个类中

package concert;
public interface Encoreable{
    void performance();
}

@Aspect
public class EncoreableIntroducer{

    @DeclareParents(value="concert.Performance+",defaultImpl=DefaultEncoreable.class)
    public static Encoreable encoreable;

}

通过@DeclareParents注解讲Encoreable接口引入到Performance bean中

@DeclareParents注解由三部分组成

  • value属性指定了那种类型的bean要引入该接口。本例中是所有实现Performance的类型(加号表示是Performance的所有子类型,而不是Performance本身)

  • defaultImpl属性指定了为引入功能提供实现的类。本例指定DefaultEncoreable提供实现

  • @DeclareParents注解所标注的静态属性指明了要引入了接口,这里所引入的是Encoreable接口。

要将EncoreableIntroducer声明为一个bean


在xml中声明切面


AOP配置元素用途
< aop:advisor>定义AOP通知器
< aop:after>定义AOP后置通知(不管被通知方法是否执行成功)
< aop:after-returning>定义AOP返回通知
< aop:after-throwing>定义AOP异常通知
< aop:around>定义AOP环绕通知
< aop:aspect>定义一个切面
< aop:aspectj-autoproxy>启用@AspectJ注解驱动的切面
< aop:before>定义一个AOP前置通知
< aop:config>顶层的AOP配置元素,大多数< aop: *>元素必须包含在< aop:config>元素内
< aop:declare-parents>以透明的方式为被通知对象引入额外的接口
< aop:pointcut>定义一个切点
<aop:config>
    <aop:aspect ref="audience">//引入audiencebean
        <aop:before pointcut="execution(...)"
            method="silenceCellPhones" />
        <aop:before pointcut="execution(...)"
            method="takeSeats" />
        ...
    </aop:aspect>
</aop:comfig>

消除重复的pointcut
<aop:config>

    <aop:aspect ref="audience">//引入audiencebean
        <aop:pointcut id="performance" execution="execution(...)" />
        <aop:before pointcut="performance"
            method="silenceCellPhones" />
        <aop:before pointcut="performance"
            method="takeSeats" />
        ...
    </aop:aspect>
</aop:comfig>
声明环绕通知
<aop:config>

    <aop:aspect ref="audience">//引入audiencebean
        <aop:pointcut id="performance" execution="execution(...)" />
        <aop:around pointcut-ref="performance"
            method = "watchPerformance" />
        ...
    </aop:aspect>
</aop:comfig>
为通知传递参数
<aop:config>

    <aop:aspect ref="audience">//引入audiencebean
        <aop:pointcut id="trackCounter" execution="execution(* soundsystem.CompactDisc.playTrack(int)) and  args(trackNumber)" />
        <aop:before pointcut="trackCounter"
            method="countTrack" />

        ...
    </aop:aspect>
</aop:comfig>
引入新功能
<aop:aspect>
    <aop:declare-parents
        type-matching="concert.Performance+"
        implement-interface="concert.Encoreable"
        delegate-ref="encoreableDelegate" />
</aop:aspect>
目录 第一部分spring的核心 第1章开始spring之旅 1.1spring是什么 1.2开始spring之旅 1.3理解依赖注入 1.3.1依赖注入 1.3.2di应用 1.3.3企业级应用的依赖注入 1.4应用aop 1.4.1aop介绍 1.4.2aop使用 1.5小结 第2章基本bean装配 2.1容纳你的bean 2.1.1beanfactory介绍 2.1.2使用应用上下文 2.1.3bean的生命 2.2创建bean 2.2.1声明一个简单的bean 2.2.2通过构造函数注入 2.3注入bean属性 2.3.1注入简单的数值 2.3.2使用其他的bean 2.3.3装配集合 2.3.4装配空值 2.4自动装配 2.4.1四种自动装配类型 2.4.2混合使用自动和手动装配 2.4.3何时采用自动装配 2.5控制bean创建 2.5.1bean范围化 2.5.2利用工厂方法来创建bean 2.5.3初始化和销毁bean 2.6小结 第3章高级bean装配 3.1声明父bean和子bean 3.1.1抽象基bean类型 3.1.2抽象共同属性 3.2方法注入 3.2.1基本的方法替换 3.2.2获取器注入 3.3注入非springbean 3.4注册自定义属性编辑器 3.5使用spring的特殊bean 3.5.1后处理bean 3.5.2bean工厂的后处理 3.5.3配置属性的外在化 3.5.4提取文本消息 3.5.5程序事件的解耦 3.5.6让bean了解容器 3.6脚本化的bean 3.6.1给椰子上lime 3.6.2脚本化bean 3.6.3注入脚本化bean的属性 3.6.4刷新脚本化bean 3.6.5编写内嵌的脚本化bean 3.7小结 第4章通知bean 4.1aop简介 4.1.1定义aop术语 4.1.2springaop的支持 4.2创建典型的spring切面 4.2.1创建通知 4.2.2定义切点和通知者 4.2.3使用proxyfactorybean 4.3自动代理 4.3.1为spring切面创建自动代理 4.3.2自动代理@aspectj切面 4.4定义纯粹的pojo切面 4.5注入aspectj切面 4.6小结 第二部分企业spring 第5章使用数据库 5.1spring的数据访问哲学 5.1.1了解spring数据访问的异常体系 5.1.2数据访问的模板化 5.1.3使用dao支持类 5.2配置数据源 5.2.1使用jndi数据源 5.2.2使用数据源连接池 5.2.3基于jdbc驱动的数据源 5.3在spring里使用jdbc 5.3.1处理失控的jdbc代码 5.3.2使用jdbc模板 5.3.3使用spring对jdbc的dao支持类 5.4在spring里集成hibernate 5.4.1选择hibernate的版本 5.4.2使用hibernate模板 5.4.3建立基于hibernate的dao 5.4.4使用hibernate3上下文会话 5.5spring和java持久api 5.5.1使用jpa模板 5.5.2创建一个实体管理器工厂 5.5.3建立使用jpa的dao 5.6spring和ibatis 5.6.1配置ibatis客户模板 5.6.2建立基于ibatis的dao 5.7缓存 5.7.1配置缓存方案 5.7.2缓存的代理bean 5.7.3注解驱动的缓存 5.8小结 第6章事务管理 6.1理解事务 6.1.1仅用4个词解释事务 6.1.2理解spring对事务管理的支持 6.2选择事务管理器 6.2.1jdbc事务 6.2.2hibernate事务 6.2.3jpa事务 6.2.4jdo事务 6.2.5jta事务 6.3在spring编写事务 6.4声明式事务 6.4.1定义事务参数 6.4.2代理事务 6.4.3在spring2.0里声明事务 6.4.4定义注释驱动事务 6.5小结 第7章保护spring 7.1springsecurity介绍 7.2验证用户身份 7.2.1配置providermanager 7.2.2根据数据库验证身份 7.2.3根据ldap仓库进行身份验证 7.3控制访问 7.3.1访问决策投票 7.3.2决定如何投票 7.3.3处理投票弃权 7.4保护web应用程序 7.4.1代理springsecurity的过滤器 7.4.2处理安全上下文 7.4.3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值