目录
2、准备工作:搭建Spring环境,必要的基础理论,下载Aspectj的4个jar包
5.1、Spring AOP注解-简单的切面定义,包含切点和简单通知的定义
5.2、Spring AOP注解-环绕型切面定义,包含切点和环绕通知的定义
5.3、Spring AOP注解-获取切点的参数切面定义,包含切点和获取切点参数的通知的定义
5.4、Spring AOP注解-为切点增加新方法的切面定义,包含切点、新接口指定为切点的父类的定义
1、主旨阐述
Spring AOP的注解模式将切面的配置信息以注解的形式直接标注在了切面类中,然后将切面类以bena的形式声明于Spring xml的配置文件中(当然也可以自动扫描),从而极大的简化了xml配置文件中的信息。
2、准备工作:搭建Spring环境,必要的基础理论,下载Aspectj的4个jar包
Spring AOP的注解模式结合了Aspectj,故需要这几个jar包支持
如果想了解Aspectj,请参考链接
https://my.oschina.net/itblog/blog/208067
准备工作请参考Spring3.0 学习-AOP面向切面编程_Spring AOP的XML配置模式
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.4</version>
</dependency>
3、本文要做的事
本文将使用注解模式实现Spring AOP,具体来说包含四个部分
·Spring AOP注解-简单的切面定义,包含切点和简单通知的定义
·Spring AOP注解-环绕型切面定义,包含切点和环绕通知的定义
·Spring AOP注解-获取切点的参数切面定义,包含切点和获取切点参数的通知的定义
·Spring AOP注解-为切点增加新方法的切面定义,包含切点、新接口指定为切点的父类的定义
4、代码结构说明
由于Spring AOP 注解的特点,四个部分的代码合并到了一套体系中
4.1、java文件5个:
被通知切点接口类:AopAtAspectjPerformer.java
其实现类:AopAtAspectjPerformerImpl.java
切面定义类:AtAspectJAudience.java
切面为切点类新增的接口类:AopAtAspectjPerformerAdd.java
其实现类:AtAspectjPerformAddImpl.java
代码结构如下图
4.2、Spring xml配置文件
applicationContextAopataspect.xml
目录结构
4.3、测试类
AtAspectJAudienceTest.java
目录结构
5、具体代码
为了分开表述四个部分的功能,下面的内容将以循环的形式展示上面几个文件的内容,随着循环次数的增加,每个文件里的内容多多少少都会有所增长。
5.1、Spring AOP注解-简单的切面定义,包含切点和简单通知的定义
被通知接口类:AopAtAspectjPerformer.java
package stu.bestcxx.springtest.springaopataspectj;
/**
* Spring AOP测试 接口
* 被切面通知的类
* @author WuJieJecket
*
*/
public interface AopAtAspectjPerformer {
//用于一般切点测试,比如在切点调用之前,之后或者发生异常
public void aopperform();
}
被通知接口类的实现类:AopAtAspectjPerformerImpl.java
package stu.bestcxx.springtest.springaopataspectjimpl;
import stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformer;
public class AopAtAspectjPerformerImpl implements AopAtAspectjPerformer {
@Override
public void aopperform() {
// TODO Auto-generated method stub
System.out.println("我是被通知类的方法,用于使用@Aspect注解切点的试验");
}
}
切面定义类:AtAspectJAudience.java
package stu.bestcxx.springtest.springaopataspectjimpl;
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.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformerAdd;
/**
* 使用@Aspect注解配置切面
*
* @author WuJieJecket
*
*/
@Aspect
public class AtAspectJAudience {
/**
* @Pointcut注解用于定义一个可以在@Aspect切面内可重复的切点
* 对切点进行重新命名,上面有新的标注
* 注意到,这个方法的名字为thisperform,下面通知中!Before()等指定的切点位置在括号内,所以可以定义多个@Pointcut即可以定义多个切点
* 所以,被@Pointcut注解的方法的名称就是该切点的名称,<span style="font-family: Arial, Helvetica, sans-serif;">aopperform是接口类名,具体含义与Spring AOP的xml配置含义一样</span>
*/
@Pointcut("execution(* stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformer.aopperform(..))")
public void thisperform(){
}
//定义通知-前置类型通知-获取参数
/*@Before("thisperform()")
public void beforeHere(JoinPoint point){
System.out.println("前置类型通知被触发-before-获取连接点方法的入参"+point.getArgs()[0]+";"+point.getArgs()[1]);
} */
//表演之前
@Before("thisperform()")
public void takeSeats(){
System.out.println("来自@aspect的@Before注解的通知:观众们入座了");
}
//表演之前
@Before("thisperform()")
public void turnOffCellPhones(){
System.out.println("来自@aspect的@Before注解的通知:观众们关闭了手机");
}
//表演之后
@AfterReturning("thisperform()")
public void applaud(){
System.out.println("来自@aspect的@AfterReturning注解的通知:表演结束了,真实太精彩了,观众们起身鼓掌");
}
//表演失败了
@AfterThrowing("thisperform()")
public void demandRefund(){
System.out.println("来自@aspect的@AfterThrowing注解的通知:表演不太成功啊,观众们倒喝彩");
}
}
Spring xml 配置文件:applicationContextAopataspect.xml
用以声明bean,后面的实验虽然也用到这个xml文件,但是其内容不再改变
<?xml version="1.0" encoding="UTF-8"?><!-- 一般化的Spring XML 配置 -->
<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:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<context:annotation-config/>
<!-- 自动设置代理,配合注解@Aspect -->
<aop:aspectj-autoproxy/>
<bean id="audience" class="stu.bestcxx.springtest.springaopataspectjimpl.AtAspectJAudience"/>
<bean id="perform" class="stu.bestcxx.springtest.springaopataspectjimpl.AopAtAspectjPerformerImpl"/>
</beans>
测试类:AtAspectJAudienceTest.java
package stu.bestcxx.springtest.springaopataspectjimpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformer;
import stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformerAdd;
/**
* 使用@Aspect注解配置切面
*
* @author WuJieJecket
*
*/
@DirtiesContext
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring/applicationContextAopataspect.xml"})
@TransactionConfiguration(transactionManager="defaultTransactionManager",defaultRollback=false)
public class AtAspectJAudienceTest {
@Autowired
private AopAtAspectjPerformer aopAtAspectjPerformer;
//一般注解
@Test
public void testaopperform(){
aopAtAspectjPerformer.aopperform();
}
}
运行结果:
来自@aspect的@Before注解的通知:观众们入座了
来自@aspect的@Before注解的通知:观众们关闭了手机
我是被通知类的方法,用于使用@Aspect注解切点的试验
来自@aspect的@AfterReturning注解的通知:表演结束了,真实太精彩了,观众们起身鼓掌
5.2、Spring AOP注解-环绕型切面定义,包含切点和环绕通知的定义
被通知接口类:AopAtAspectjPerformer.java
类内部新增代码片段
//用户测试环绕通知,在切点之前和之后添加其他程序
public void aopperformaround();
被通知接口类的实现类:AopAtAspectjPerformerImpl.java
类内部新增代码片段
@Override
public void aopperformaround() {
// TODO Auto-generated method stub
System.out.println("我是 被通知的方法,这里是环绕通知实验");
}
切面定义类:AtAspectJAudience.java
类内部新增代码片段:新定义了一个切点
//设置新切点-环绕注解使用
@Pointcut("execution(* stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformer.aopperformaround(..))")
public void thisperformaround(){
}
//注解环绕通知
@Around("thisperformaround()")
public void watchPerformance(ProceedingJoinPoint joinpoint){
try {
System.out.println("来自@aspect注解的@Around注解开始");
long start=System.currentTimeMillis();
joinpoint.proceed();
Thread.sleep(1);//线程中断1毫秒
System.out.println("来自@aspect注解的@Around注解结束");
long end=System.currentTimeMillis();
System.out.println("来自@aspect注解的@Around注解,累计用时="+(end-start)+"milliseconds");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
测试类:AtAspectJAudienceTest.java
类内部新增代码片段
//环绕注解
@Test
public void testaopperformaround(){
aopAtAspectjPerformer.aopperformaround();
}
运行结果:
来自@aspect注解的@Around注解开始
我是 被通知的方法,这里是环绕通知实验
来自@aspect注解的@Around注解结束
来自@aspect注解的@Around注解,累计用时=16milliseconds
5.3、Spring AOP注解-获取切点的参数切面定义,包含切点和获取切点参数的通知的定义
被通知接口类:AopAtAspectjPerformer.java
类内部新增代码片段
//用于传递参数测试,切面获取切点方法的入参
public void aopperformargs(String thisargs);
被通知接口类的实现类:AopAtAspectjPerformerImpl.java
类内部新增代码片段
@Override
public void aopperformargs(String thisargs) {
// TODO Auto-generated method stub
System.out.println("我是 被通知的方法,我的参数值为:"+thisargs);
}
切面定义类:AtAspectJAudience.java
类内部新增代码片段:新定义了一个切点和一个变量thisargs和该参数的get/set方法
/**
* 编写一个传递参数的注解式的切点
* 定义一个传递被通知方法参数到切面的通知
*/
private String thisargs;
@Pointcut("execution(* stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformer.aopperformargs(String))&&args(thisargs)")
public void thisperformargs(String thisargs){
}
@Before("thisperformargs(thisargs)")
public void justtofindargs(String thisargs){
System.out.println("切面的通知收到了被通知方法的参数:"+thisargs);
}
public String getThisargs() {
return thisargs;
}
public void setThisargs(String thisargs) {
this.thisargs = thisargs;
}
测试类:AtAspectJAudienceTest.java
类内部新增代码片段
//传递参数注解
@Test
public void testaopperformargs(){
aopAtAspectjPerformer.aopperformargs("+我是参数哦+");
}
运行结果:
切面的通知收到了被通知方法的参数:+我是参数哦+
我是 被通知的方法,我的参数值为:+我是参数哦+
5.4、Spring AOP注解-为切点增加新方法的切面定义,包含切点、新接口指定为切点的父类的定义
被通知接口类:AopAtAspectjPerformer.java
类内部新增代码片段
//用于测试切面为切点增加新方法
public void aopperformAddNew();
被通知接口类的实现类:AopAtAspectjPerformerImpl.java
类内部新增代码片段
@Override
public void aopperformAddNew() {
// TODO Auto-generated method stub
System.out.println("我是被通知的方法,这里是Spring AOP增加新方法的实验,切面使我所属的对象可以通过强制类型转化的方式调用指定接口的实现,我得多说一句,即使不调用我也无碍,因为新增的方法的切面设置是针对接口类这一层的,而我只是一个方法而已。");
}
切面为切点新增的接口类:AopAtAspectjPerformerAdd.java
package stu.bestcxx.springtest.springaopataspectj;
/**
* Spring AOP测试 接口
* @author WuJieJecket
*
*/
public interface AopAtAspectjPerformerAdd {
public void aopperformadd();
}
切面为切点新增的接口类的实现类:AtAspectjPerformAddImpl.java
package stu.bestcxx.springtest.springaopataspectjimpl;
import stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformerAdd;
public class AtAspectjPerformAddImpl implements AopAtAspectjPerformerAdd {
@Override
public void aopperformadd() {
// TODO Auto-generated method stub
System.out.println("我是被切面通知的方法新实现的接口的方法,被通知的接口强制转化为我的接口类能够调用我");
}
}
切面使用注解模式的AOP为切点增加新方法无需也无能把新接口在Spring的xml配置文件声明为Bean
切面定义类:AtAspectJAudience.java
类内部新增代码片段:
/**
* 注解式,为被通知的接口实现增加新接口实现的切点设置
* value=被通知的接口
* defaultImpl=新增接口的实现
* static是新接口
*/
@DeclareParents(
value="stu.bestcxx.springtest.springaopataspectj.AopAtAspectjPerformer+",
defaultImpl=AtAspectjPerformAddImpl.class)
public static AopAtAspectjPerformerAdd aopAtAspectjPerformerAdd;
测试类:AtAspectJAudienceTest.java
类内部新增代码片段
//新增接口实现注解
@Test
public void testAopAtAspectjPerformerAdd(){
//切点类本身的方法调用
aopAtAspectjPerformer.aopperformAddNew();
//被通知接口强制转化为新增接口,即可调用新增接口的方法的实现
((AopAtAspectjPerformerAdd)aopAtAspectjPerformer).aopperformadd();
}
运行结果:
我是被通知的方法,这里是Spring AOP增加新方法的实验,切面使我所属的对象可以通过强制类型转化的方式调用指定接口的实现,我得多说一句,即使不调用我也无碍,因为新增的方法的切面设置是针对接口类这一
层的,而我只是一个方法而已。
我是被切面通知的方法新实现的接口的方法,被通知的接口强制转化为我的接口类能够调用我
Spring3.0 学习-AOP面向切面编程_Spring AOP的注解模式即Aspectj模式 结束
2019年02月19日补充
表达式例子如下:
任意公共方法的执行:
execution(public * *(..))
任何一个以“set”开始的方法的执行:
execution(* set*(..))
AccountService 接口的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:
execution(* com.xyz.service.*.*(..))
定义在service包和所有子包里的任意类的任意方法的执行:
execution(* com.xyz.service..*.*(..))
定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:
execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
20190219补充
package com.bestcxx.stu.basic.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* @Title:
* @Description:定义切面
* @Author: jie.wu
*/
@Aspect
public class AopService {
//----------------例子1-before 传递所有参数
//定义切点
//service 下所有包和子包下的所有方法,默认会传递所有参数
@Pointcut("execution(* com.bestcxx.stu.basic.service..*.*(..))")
public void aroundHere(){
}
//定义通知-前置类型通知-获取参数
@Before("aroundHere()")
public void beforeHere(JoinPoint point){
System.out.println("前置类型通知被触发-before-获取连接点方法的入参"+point.getArgs()[0]+";"+point.getArgs()[1]);
}
//---------------------例子2-仅传递指定参数
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
//service 下所有包和子包下的所有方法
//需要为指定传递的参数提供 set/get 方法
@Pointcut("execution(* com.bestcxx.stu.basic.service..*.*(..))&&args(name)")
public void around2Here(String name){
}
//定义通知-前置类型通知-获取参数
@Before("around2Here(name)")
public void before2Here(String name){
System.out.println("前置类型通知被触发-before2-获取连接点方法的入参"+name);
}
//------------------例子3-around 传递参数
//环绕,需要有返回值,否则会影响方法
@Around("aroundHere()")
public Object aroundHere(ProceedingJoinPoint point){
System.out.println("前置类型通知被触发-arount 前 -获取连接点方法的入参"+point.getArgs()[0]+";"+point.getArgs()[1]);
Object object=null;
try{
object=point.proceed();
}catch(Throwable e){
System.out.println("报错了");
}
System.out.println("前置类型通知被触发-arount 后-获取连接点方法的入参"+point.getArgs()[0]+";"+point.getArgs()[1]);
return object;
}
}
maven 依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
2019年06月05日补充-使用扫描模式
切面类的@Aspect 需要 <aop:aspectj-autoproxy/>
同时,切面类本身被识别为Spring bean 需要@Component
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.bestcxx.stu.tem.aspect"/>
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.bestcxx.stu.tem.aspect.service.TemService.sayHello(..))")
public void around() {
}
@Before("around()")
public void before(){
System.out.println("来自@aspect的@Before注解的通知:这是一个切点");
}
}
2019年12月22日补充:Spring自身代码实现 SpingAOP
Spring 以自身代码实现 SpringAOP Spring 源码学习 —— ProxyBeanFactory