上节中我们已经定义了Performance接口,他是切面中的切点的一个目标对象。那么现在就让我们使用AspectJ注解来定义切面吧。

1.定义切面

下面我们就来定义一场舞台剧中观众的切面类Audience:

package com.spring.aop.service.aop;

import org.aspectj.lang.ProceedingJoinPoint;
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; /**
* <dl>
* <dd>Description:观看演出的切面</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月3日 下午9:58:09</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Aspect
public class Audience { /**
* 目标方法执行之前调用
*/
@Before("execution(** com.spring.aop.service.perform(..))")
public void silenceCellPhone() {
System.out.println("Silencing cell phones");
} /**
* 目标方法执行之前调用
*/
@Before("execution(** com.spring.aop.service.perform(..))")
public void takeSeats() {
System.out.println("Taking seats");
} /**
* 目标方法执行完后调用
*/
@AfterReturning("execution(** com.spring.aop.service.perform(..))")
public void applause() {
System.out.println("CLAP CLAP CLAP");
} /**
* 目标方法发生异常时调用
*/
@AfterThrowing("execution(** com.spring.aop.service.perform(..))")
public void demandRefund() {
System.out.println("Demanding a refund");
} }

我们可以看到使用了几种注解,其实AspectJ提供了五中注解来定义通知:

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

  聪明的你可能已经看到,同样的切点我们写了四遍,这是不科学的,强大的Spring怎么会没有处理的方法呢。其实我们可以使用@Pointcut注解声明一个通用的切点,在后面可以随意使用:

package com.spring.aop.service.aop;

import org.aspectj.lang.ProceedingJoinPoint;
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; /**
* <dl>
* <dd>Description:观看演出的切面</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月3日 下午9:58:09</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Aspect
public class Audience { /**
* 定义一个公共的切点
*/
@Pointcut("execution(** com.spring.aop.service.Perfomance.perform(..))")
public void performance() {
} /**
* 目标方法执行之前调用
*/
@Before("performance()")
public void silenceCellPhone() {
System.out.println("Silencing cell phones");
} /**
* 目标方法执行之前调用
*/
@Before("performance()")
public void takeSeats() {
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");
} }

  这样定义一个切点后,后面我们的方法想使用这个切点直接调用切点所在的方法就行了。实际上切面也是一个Java类,我们可以将它装配到Spring中的bean中:

/**
* 声明Audience bean
* @return
*/
@Bean
public Audience audience(){
return new Audience();
}

  但是现在Spring还不会将Audience视为一个切面,即便使用了@AspectJ注解,但它并不会被视为一个切面们这些注解不会被解析,也不会创建将其转化为切面的代理。但我们可以使用JavaConfig,然后在JavaConfig类上使用注解@EnableAspectJAutoProxy注解启动自动代理功能:

package com.spring.aop.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import com.spring.aop.service.aop.Audience; /**
* <dl>
* <dd>Description:配置类</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月3日 下午10:20:11</dd>
* <dd>@author:Kong</dd>
* </dl>
*/ @Configuration
//启动AspectJ自动代理
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig { /**
* 声明Audience bean
* @return
*/
@Bean
public Audience audience(){
return new Audience();
} }

如果你想使用XML配置也是可以的,我们要使用Spring aop命名空间中的<aop:aspectj-autoproxy>元素:

<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd"> <context:component-scan base-package="com.spring.aop" />\ <!-- 启动AspectJ自动代理 -->
<aop:aspectj-autoproxy/> <bean class="com.spring.aop.Audience" />
</beans>

  其实不管使用JAvaConfig还是Xml,AspectJ都会为使用@ApsectJ注解的Bean创建一个代理,这个代理会环绕着所有该切面所匹配的bean。

2.创建环绕通知

  环绕通知是这几种通知中相对复杂的一种,它可以在一个同时中同时编写前置和后置通知:

/**
* 环绕通知
* @param jp 通过它调用目标方法
*/
@Around("perforance()")
public void watchPerformance(ProceedingJoinPoint jp) { try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}

3.处理通知中的参数

  其实我们可以使用指示器args为切面中的方法传递参数,比如我们想统计唱片中每首歌曲的播放次数,这时我们想使用切面来完成这个工作,我们就需要向切面传递一个歌曲的编号,这时我们可以通过args向切面中的方法传递参数:

package com.spring.aop.service.aop;

import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut; /**
* <dl>
* <dd>Description:统计每首歌曲播放次数的AOP</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月4日 上午8:19:24</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Aspect
public class TrackCounter { private Map<Integer,Integer> trackCounts = new HashMap<Integer,Integer>(); @Pointcut("execution(* com.spring.aop.service.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(trackNumber)?trackCounts.get(trackNumber):0; } }

然后我们将这个切面配置到Spring中:

package com.spring.aop.config;

import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import com.google.common.collect.Lists;
import com.spring.aop.service.CompactDisc;
import com.spring.aop.service.aop.TrackCounter;
import com.spring.aop.service.impl.BlankDisc; /**
* <dl>
* <dd>Description:显示配置播放歌曲的bean和记录播放次数的AOP</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月4日 上午8:52:05</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig { @Bean
public CompactDisc sgtPeppers(){
BlankDisc cd = new BlankDisc();
cd.setTitle("Sgt. Pepper's Lonely Hearts Club Band");
cd.setArtist("The Beatles"); List<String> tracks = Lists.newArrayList();
tracks.add("Sgn. Pepper's Lonelu Hears Club Band");
tracks.add("Wiith a Litter Help from My Friends");
tracks.add("Lucy in the Sky with Diamonds");
tracks.add("Getting Better");
tracks.add("Fixing a Hole"); //...other tracks omitted for brevity ...
cd.setTracks(tracks);
return cd;
} @Bean
public TrackCounter trackCOunter(){
return new TrackCounter();
}
}

为了证明他能够正常运行我们编写一个测试类进行测试:

package service;

import static org.junit.Assert.assertEquals;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.spring.aop.config.TrackCounterConfig;
import com.spring.aop.service.CompactDisc;
import com.spring.aop.service.aop.TrackCounter; /**
* <dl>
* <dd>Description: 测试统计歌曲播放的AOP</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月4日 上午9:08:09</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TrackCounterConfig.class)
public class TrackCounterTest { @SuppressWarnings("deprecation")
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog(); @Autowired
private CompactDisc cd; @Autowired
private TrackCounter counter; @Test
public void testTrackCounter(){
cd.playTrack(1);
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(4);
cd.playTrack(2);
cd.playTrack(4);
cd.playTrack(2); assertEquals(1,counter.getPlayCount(1));
assertEquals(3,counter.getPlayCount(2));
assertEquals(1,counter.getPlayCount(3));
assertEquals(2,counter.getPlayCount(4));
assertEquals(0,counter.getPlayCount(5));
assertEquals(0,counter.getPlayCount(0));
}
}

运行后确定我们的代码是正确的!

4.通过注解引入新功能

  像Java这种静态语言,一般一般类定义完成后,类中的方法属性就已经确定了,如果我们想要为这些类添加新方法、功能,就可以使用Spring Aop。现在我们想为所有的Performance实现引入下面的Encoreable接口:

package com.spring.aop.service;
/**
* <dl>
* <dd>Description:添加新功能</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:2016年9月4日 上午9:48:36</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
public interface Encoreable { void performEncore();
}

要实现该功能我们要创建一个新的切面:

package com.spring.aop.service.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents; import com.spring.aop.service.Encoreable;
import com.spring.aop.service.impl.DefaultEncoreable; /**
* <dl>
* <dd>Description:为目标bean添加新功能的AOP bean</dd>
* <dd>Company: 黑科技</dd>
* <dd>@date:201
6年9月4日 上午9:45:34</dd>
* <dd>@author:Kong</dd>
* </dl>
*/
@Aspect
public class EncoreableIntroducer { @DeclareParents(value="com.spring.aop.service.Perforance+",defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable; }

我们可以看到在切面中它并没有使用前置、后置等通知的注解,而是使用了@DeclareParents注解,将Encoreable接口引入到Performance bean中。
@DeclareParents注解由三部分组成:

  • value属性指定了哪种类型的bean要引入该接口(加号表示是Performance的所有子类)。
  • defaultImpl属性指定了为引入功能提供实现类
  • @DeclareParents注解所标注的静态属性指明了要引入了接口,在这里,我们所引入的是Encoreable接口。

和其他切面一样,我们需要在Spring应用中将EncoreableIntroducer声明为一个bean:
<bean class="com.spring.aop.service.aop.EncoreableIntroducer" />
至此在Java类中配置切面的内容已经全部介绍完了

Spring AOP之使用注解创建切面的更多相关文章

  1. SpringInAction--面向切片的Spring以及如何使用注解创建切面

    什么叫做切片..什么叫做AOP... 与大多数技术一样,AOP已经形成了自己的术语.描述切面的常用术语有通知(advice).切点(pointcut)和连接点(join point). (一大串书上的 ...

  2. Spring AOP事务管理(使用切面把事务管理起来)

    在<Spring Transaction 分析事务属性(事务的基本概念.配置)>基础上 http://blog.csdn.net/partner4java/article/details/ ...

  3. Spring Boot 2.X(八):Spring AOP 实现简单的日志切面

    AOP 1.什么是 AOP ? AOP 的全称为 Aspect Oriented Programming,译为面向切面编程,是通过预编译方式和运行期动态代理实现核心业务逻辑之外的横切行为的统一维护的一 ...

  4. 运用Spring Aop,一个注解实现日志记录

    运用Spring Aop,一个注解实现日志记录 1. 介绍 我们都知道Spring框架的两大特性分别是 IOC (控制反转)和 AOP (面向切面),这个是每一个Spring学习视频里面一开始都会提到 ...

  5. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  6. 利用Spring AOP和自定义注解实现日志功能

    Spring AOP的主要功能相信大家都知道,日志记录.权限校验等等. 用法就是定义一个切入点(Pointcut),定义一个通知(Advice),然后设置通知在该切入点上执行的方式(前置.后置.环绕等 ...

  7. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  8. Spring+AOP+Log4j 用注解的方式记录指定某个方法的日志

    一.spring aop execution表达式说明 在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义 ...

  9. Spring(十九):Spring AOP(三):切面的优先级、重复使用切入点表达式

    背景: 1)指定切面优先级示例:有的时候需要对一个方法指定多个切面,而这多个切面有时又需要按照不同顺序执行,因此,切面执行优先级别指定功能就变得很实用. 2)重复使用切入点表达式:上一篇文章中,定义前 ...

随机推荐

  1. iOS下拉刷新和上拉刷新

    在iOS开发中,我们经常要用到下拉刷新和上拉刷新来加载新的数据,当前这也适合分页.iOS原生就带有该方法,下面就iOS自带的下拉刷新方法来简单操作. 上拉刷新 1.在TableView里,一打开软件, ...

  2. DataTable转换成IList 【转载】

    链接:http://www.cnblogs.com/hlxs/archive/2011/05/09/2087976.html#2738813 留着学习 using System; using Syst ...

  3. html05

    1.js中的对象-内置对象-外部对象-自定义对象 2.常见的内置对象有哪些?-String对象-Number对象-Boolean对象-Array对象-Math对象-Date对象-RegExp正则对象- ...

  4. Object之总结(一)

    一.Object类中一共有12个方法.一个私有方法,两个保护方法,9个公共方法.另外还有一个静态代码块. 1.registerNatives方法.私有静态本地无参数无返回值. 2.finalize方法 ...

  5. javascript的Object对象的defineProperty和defineProperties

    Object的属性 查看官网:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Obje ...

  6. zw版【转发·台湾nvp系列Delphi例程】HALCON ZoomImageFactor2

    zw版[转发·台湾nvp系列Delphi例程]HALCON ZoomImageFactor2 procedure TForm1.Button1Click(Sender: TObject);var op ...

  7. uva11354 LCA+最小生成树+dp

    源自大白书 题意 有n座城市通过m条双向道路相连,每条道路都有一个危险系数.你的任务是回答若干个询问,每个询问包含一个起点s和一个终点t,要求找到一条从s到t的路,使得途径所有的边的大最大危险系数最小 ...

  8. Python: translate()审查清理文本字符串

    ①凌乱的字符串如下: ②创建一个小的转换表格然后使用translate()方法 空白字符\t和\f已经被重新映射到一个空格. \r直接被删除 ③构建一个更大的表格,删除所有的和音符

  9. Git本地仓库与远程github同步的时候提示fatal: remote origin already exists 错误解决办法

    Git本地仓库与远程github同步的时候提示fatal: remote origin already exists 错误解决办法 1.git在本地的电脑创建了仓库,要远程同步github的仓库.使用 ...

  10. 网络营销相关缩写名称CPM CPT CPC CPA CPS SEM SEO解析

    网络营销相关缩写名称CPM CPT CPC CPA CPS SEM SEO解析 CPM CPT CPC CPA CPS SEM SEO在网络营销中是什么意思?SEO和SEM的区别是? CPM(Cost ...