Spring aop<4>

AOP(Aspect Oriented Programing)
面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控/事务管理/安全检查/缓存实现等)

Spring 实现AOP方式有cglib和Java动态代理,默认实现方式为Java动态代理。

AOP相关术语

  • Joinpoint:连接点 指那些被拦截到的点.在Spring中,这些点指方法(因为Spring只支持方法类型的连接点).
  • Pointcut:切入点 指需要(配置)被增强的Joinpoint
  • Advice:通知/增强 指拦截到Joinpoint后要做的操作.通知分为前置通知/后置通知/异常通知/最终通知/环绕通知等.
  • Aspect:切面 切入点和通知的结合.
  • Target:目标对象 需要被代理(增强)的对象.
  • Proxy:代理对象 目标对象被AOP 织入 增强/通知后,产生的对象.
  • Weaving:织入 指把增强/通知应用到目标对象来创建代理对象的过程(Spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入).
  • Introduction: 引介 一种特殊通知,在不修改类代码的前提下,可以在运行期为类动态地添加一些Method/Field(不常用).
  1. 引入图中的包


    image.png
  2. 在spring配置文件中配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"    
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd>
 <!--和schema一起启动@Aspectj支持-->
    <aop:aspectj-autoproxy/>

或者

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"    
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd>
 <!--第二种方式启动@Aspectj支持-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
</beans>
  1. 使用@Aspect定义一个切面类
@Aspect
public class TestAspectModel {
...
}
  1. 定义一个需要增强的类
@Component(value = "firstaspect")
public class Firstaspect {
    public void noReturn() {
        System.out.println("执行方法noReturn()");
    }
    public String hasReturn(String argument) {
        System.out.println("执行方法hasReturn()"+argument);
        return " (hasReturn方法执行返回值) "+argument;
    }

    public void throwException() {
        System.out.println("执行方法throwException()");
        throw new IllegalArgumentException("i am a runtime exception");
    }
}
  1. 定义增强方法
    增强方式有:

@Before
@AfterReturning
@AfterThrowing
@After
@Around

5.1 @Before

@Aspect
public class TestAspectModel {

    @Before("execution(* com.lq.play.model.*.*(..))")
    public void before() {
        System.out.println("Before!");
    }
}

测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {

    @Autowired
    private Firstaspect firstaspect;

    @Test
    public void test() throws Exception {
        firstaspect.hasReturn("");
    }
}

执行结果:

Before!
执行方法hasReturn()

@Before 增强只能在目标方法执行之前织入增强,如果需要阻止目标方法执行,可通过抛出一个异常来实现。
5.2 @AfterReturning
@AfterReturning增强方法将在目标方法正常完成后被织入。
@AfterReturning注解有两个属性:

  • pointcut/value:两个属性作用相同,既可以是一个已有的切入点,也可以是一个切入点表达式,当指定了pointcut属性之后,value值将被覆盖。
  • returning:指定一个形参名,该形参可用于访问方法返回值。在Advice方法中可以指定形参的类型,会限制目标方法必须返回指定类型的值或者没有返回值。
    在TestAspectModel 中增加一个织入方法:
@AfterReturning(returning = "returnValue",pointcut = "execution(* com.lq.play.model.*.*(..))")
//@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
public void afterReturning(JoinPoint joinPoint,Object returnValue) {
    System.out.println("afterReturning!");
    System.out.println("afterReturning!: 目标方法返回值:"+returnValue);
    System.out.println("afterReturning! 被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
    System.out.println("afterReturning! 被织入增强处理的目标方法的参数为:"+joinPoint.getArgs());
    System.out.println("afterReturning! 被织入增强处理的目标对象为:" +joinPoint.getTarget());
}

执行结果:

执行方法hasReturn()
afterReturning!
afterReturning!: 目标方法返回值: (hasReturn方法执行返回值)
afterReturning! 被织入增强处理的目标方法为:hasReturn
afterReturning! 被织入增强处理的目标方法的参数为:[Ljava.lang.Object;@2a8448fa
afterReturning! 被织入增强处理的目标对象为:com.lq.play.model.Firstaspect@6f204a1a

5.3 @AfterThrowing
@AfterThrowing增强方法将在目标方法正常完成后被织入。
@AfterThrowing注解有两个属性:

  • pointcut/value:两个属性作用相同,既可以是一个已有的切入点,也可以是一个切入点表达式,当指定了pointcut属性之后,value值将被覆盖。
  • throwing:指定一个形参名,该形参可用于访问目标方法抛出的异常。在Advice方法中可以指定形参的类型,会限制目标方法必须抛出指定类型的异常。
    向TestAspectModel增加织入方法:
 @AfterThrowing(throwing = "ex",pointcut = "execution(* com.lq.play.model.*.*(..))")
    public void afterThrowing(IllegalArgumentException ex) {
        System.out.println("afterThrowing:"+ex);
    }

向Firstaspect添加抛异常方法

 public void throwException() {
        System.out.println("执行方法throwException()");
        throw new IllegalArgumentException("i am a runtime exception");
    }

测试用例

@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {

    @Autowired
    private Firstaspect firstaspect;

    @Test
    public void test() throws Exception {
//        firstaspect.hasReturn("");
        firstaspect.throwException();
    }
}

返回结果:

执行方法throwException()
afterThrowing:java.lang.IllegalArgumentException: i am a runtime exception

java.lang.IllegalArgumentException: i am a runtime exceptionat com.lq.play.model.Firstaspect.throwException(Firstaspect.java:22)at com.lq.play.model.Firstaspect$$FastClassBySpringCGLIB$$89d307c5.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)...

5.4 @After
@AfterReturning增强处理在目标方法成功执行后才会被织入。
@After增强处理不管目标方法成功与否,它都会被织入,和异常捕捉的finally关键字类似。
增强处理类:
```java
@Aspect
public class TestAspectModel {

    @After("execution(* com.lq.play.model.*.*(..))")
    public void after() {
        System.out.println("After!");
    }

    @AfterReturning(returning = "returnValue", pointcut = "execution(* com.lq.play.model.*.*(..))")
    public void afterReturning(JoinPoint joinPoint, Object returnValue) {
        System.out.println("afterReturning!");
        System.out.println("afterReturning!: 目标方法返回值:" + returnValue);
        System.out.println("afterReturning! 被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
        System.out.println("afterReturning! 被织入增强处理的目标方法的参数为:" + joinPoint.getArgs());
        System.out.println("afterReturning! 被织入增强处理的目标对象为:" + joinPoint.getTarget());
    }
}

测试用例

@ContextConfiguration(locations = {"classpath:config/spring/spring-servlet.xml"})
public class ApplicationTest {

    @Autowired
    private Firstaspect firstaspect;

    @Test
    public void test() throws Exception {
//        firstaspect.hasReturn("");
        firstaspect.throwException();
    }
}

返回结果

执行方法throwException()
After!
java.lang.IllegalArgumentException: i am a runtime exception
at com.lq.play.model.Firstaspect.throwException(Firstaspect.java:22)at com.lq.play.model.Firstaspect$$FastClassBySpringCGLIB$$89d307c5.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

执行了@After增强,没有执行@AfterReturning

5.6 @Around
这个是功能最强大的增强处理,既可以在执行方法前织入增强动作,也可以在执行目标方法之后织入增强动作。

  • @Around可以决定目标方法在什么时候执行,如何执行,甚至可以组织目标方法的执行。
  • @Around可以改变执行目标方法的参数值,也可以改标执行目标方法之后的返回值。
  • @Around增强方法的第一个参数必须是ProceedingJoinPoint类型(至少包含一个形参),如果程序没有调用ProceedingJoinPoint 参数的proceed方法,则目标方法不会被执行。
  • 调用ProceedingJoinPoint 参数的proceed方法时,可以传入一个Object[]对象作为参数,该数组中的值将被传入目标方法为执行方法的实参。
    织入方法:
@Aspect
public class TestAspectModel {
    @Around("execution(* com.lq.play.model.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{

        System.out.println("around!");
        System.out.println("around! 被织入增强处理的目标方法为:"+pjp.getSignature().getName());
        System.out.println("around! 被织入增强处理的目标方法的参数为:"+Arrays.toString(pjp.getArgs()));
        System.out.println("around! 被织入增强处理的目标对象为:" +pjp.getTarget());
        System.out.println("around!");
        Object[] args = pjp.getArgs();
        if (args != null && args.length > 0) {
            args[0]="around 增加的参数前缀"+args[0];
        }

        Object returnValue =  pjp.proceed(args);

        return "around 增加的执行后前缀"+(String)returnValue;
    }
}

目标方法

   public String hasReturn(String argument) {
        System.out.println("执行方法hasReturn()"+"传入的参数值:"+argument);
        return "(hasReturn方法执行返回值)"+argument;
    }

测试用例:

   System.out.println(firstaspect.hasReturn(""));

执行结果:

around!
around! 被织入增强处理的目标方法为:hasReturn
around! 被织入增强处理的目标方法的参数为:[Ljava.lang.Object;@533bda92
around! 被织入增强处理的目标对象为:com.lq.play.model.Firstaspect@304bb45b
around!
执行方法hasReturn()传入的参数值:around 增加的参数前缀
around 增加的执行后前缀(hasReturn方法执行返回值)around 增加的参数前缀

五个增强都加入执行结果

around!
around! 被织入增强处理的目标方法为:hasReturn
around! 被织入增强处理的目标方法的参数为:[Ljava.lang.Object;@7bd7d6d6
around! 被织入增强处理的目标对象为:com.lq.play.model.Firstaspect@43f02ef2
around!
Before!
执行方法hasReturn()传入的参数值:around 增加的参数前缀
After!
afterReturning!
afterReturning!: 目标方法返回值:around 增加的执行后前缀(hasReturn方法执行返回值)around 增加的参数前缀
afterReturning! 被织入增强处理的目标方法为:hasReturn
afterReturning! 被织入增强处理的目标方法的参数为:[Ljava.lang.Object;@5b7a7f33
afterReturning! 被织入增强处理的目标对象为:com.lq.play.model.Firstaspect@43f02ef2

执行了

  • @Around
  • @Before
  • 目标方法
  • @After
  • @AfterReturning
  1. 访问目标方法的参数
    在增强方法里第一个参数定义为JoinPoint类型(ProceedingJoinPoint 即为JoinPoint子类),JoinPoint代表了织入增强处理的连接点。JoinPoint包含如下几个方法。
  • Object[] getArgs():返回目标方法执行时的参数。
  • Signature getSignature():返回被增强的方法的相关信息。
  • Object getTarget():返回被织入增强处理的目标对象。
  • Object getThis(): 返回AOP框架为目标对象生成的代理对象。

示例如@Around的增强方法:

 @Around("execution(* com.lq.play.model.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("around!");
        System.out.println("around! 被织入增强处理的目标方法为:" + pjp.getSignature().getName());
        System.out.println("around! 被织入增强处理的目标方法的参数为:" + Arrays.toString(pjp.getArgs()));
        System.out.println("around! 被织入增强处理的目标对象为:" + pjp.getTarget());
        System.out.println("around!");
        Object[] args = pjp.getArgs();
        if (args != null && args.length > 0) {
            args[0] = "around 增加的参数前缀" + args[0];
        }

        Object returnValue = pjp.proceed(args);

        return "around 增加的执行后前缀" + (String) returnValue;
    }

可以指定织入优先级

  • 让切面类实现org.springframework.core.Ordered接口,实现该接口值需要实现一个int getOrder()方法,该方法返回值越小,则优先级越高。
  • 直接使用@Order注解来修饰一个切面类,指定一个int类型的value属性,该方法返回值越小,则优先级越高。

可以为表达式指定参数,如下:

 @AfterReturning(returning = "returnValue", pointcut = "execution(* com.lq.play.model.*.*(..))&&args(arg1)")
//@AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
    public void afterReturning(JoinPoint joinPoint, Object returnValue,String arg1) {}
  1. 定义切入点
    当多个表达式相同时,可以定义一个
 @Pointcut("execution(* com.lq.play.model.*.*(..))")
 public void myPointCut() {
 }

然后其它引入即可

 @AfterReturning(returning = "returnValue",pointcut = "myPointCut()")
  1. 也可以使用xml配置
  <bean name="testAspectModel" class="com.lq.play.aspect.TestAspectModel" />
    <!-- 进行 aop 的配置 -->
    <aop:config><!-- 配置切入点表达式 :哪些类的方法需要进行增强 哪些类的方法需要进行增强 -->
        <aop:pointcut expression="execution(* com.lq.play.model.*.*(..))" id="pointcut1"/>
        <aop:pointcut expression="execution(* com.lq.play.model.*.*(..))" id="pointcut2"/>
        <!-- 配置切面 -->
        <aop:aspect ref="testAspectModel">
            <aop:before method="before" pointcut-ref ="pointcut1" />
            <aop:after-returning method ="afterReturning" pointcut-ref ="pointcut1" returning="returnValue" />
            <aop:around method="around" pointcut-ref ="pointcut1" />
            <aop:after-throwing method ="afterThrowing" pointcut-ref ="pointcut1" throwing="ex"/>
            <aop:after method="after" pointcut-ref ="pointcut1" />
        </aop:aspect>
    </aop:config>

<aop:aspect .../>有如下三个属性:

  • id定义改切面的标识名

  • ref用于将ref属性所引用的普通Bean转换为切面Bean.

  • order:指定该切面Bean的优先级,值越小,优先级越高。

  • <aop:before.../>

  • <aop:after-returning.../>

  • <aop:before.../>

  • <aop:after-throwing .../>

  • <aop:after.../>

这几个元素都不支持子元素,但有如下属性:

  • pointcut 织入点表达式
  • pointcut-ref 和pointcut只能有一个,指定一个pointcut
  • method 指定将该方法转为增强处理方法
  • throwing 指定抛出的异常参数
  • returning 目标方法返回值
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容