Spring AOP 五大通知类型

1.前置通知

在目标方法执行以前执行执行的通知。spring

前置通知方法,能够没有参数,也能够额外接收一个JoinPoint,Spring会自动将该对象传入,表明当前的链接点,经过该对象能够获取目标对象 和 目标方法相关的信息。express

注意,若是接收JoinPoint,必须保证其为方法的第一个参数,不然报错。设计模式

配置方式:app

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    ">
    
    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan>
    
    <aop:config proxy-target-class="true">
        <!-- 配置切入点  -->
        <aop:pointcut 
            expression="execution(* cn.tedu.service.UserServiceImpl.addUser(..))" 
            id="pc01"/>
            
        <!-- 配置切面 -->
        <aop:aspect ref="firstAspect">
       <<!-- 前置通知 --> <aop:before method="before" pointcut-ref="pc01"/> </aop:aspect> </aop:config> </beans>
复制代码
复制代码
package cn.tedu.service;

import org.springframework.stereotype.Service;
/**
 * UserServiceImple:目标对象
 */
@Service("userService")
public class UserServiceImple implements UserService {

    @Override
    public void addUser(String name) {
        System.out.println("增长用户。。");
    }

    @Override
    public void updateUser() {
        System.out.println("修改用户。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户。。");
    }

    @Override
    public void query() {
        System.out.println("查询用户。。");
    }
}
复制代码
复制代码
package cn.tedu.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
/**
 * FirstAspect:切面代码
 */
@Component
public class FirstAspect {
    public void before(JoinPoint jp){ // 能够选择额外的传入一个JoinPoint链接点对象,必须用方法的第一个参数接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 经过JoinPoint对象获取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...["+clz+"]...["+name+"]...");
    }
}
复制代码
复制代码
package cn.tedu.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.tedu.service.UserService;
/**
 * AOPTest:测试代码
 */
public class AOPTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.addUser("cjj"); // 一个链接点
    }
}
复制代码

执行结果:ide

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
增长用户。。

2.环绕通知

在目标方法执行以前和以后均可以执行额外代码的通知。post

在环绕通知中必须显式的调用目标方法,目标方法才会执行,这个显式调用时经过ProceedingJoinPoint来实现的,能够在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,注意这个参数必须处在环绕通知的第一个形参位置。测试

**要注意,只有环绕通知能够接收ProceedingJoinPoint,而其余通知只能接收JoinPoint。spa

环绕通知须要返回返回值,不然真正调用者将拿不到返回值,只能获得一个null。设计

环绕通知有控制目标方法是否执行、有控制是否返回值、有改变返回值的能力。代理

环绕通知虽然有这样的能力,但必定要慎用,不是技术上不可行,而是要当心不要破坏了软件分层的“高内聚 低耦合”的目标。

配置方式:

<!-- 环绕通知 -->
 <aop:around method="around" pointcut-ref="pc1"/>
public Object around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("1 -- around before...");
        Object obj = jp.proceed(); //--显式的调用目标方法
        System.out.println("1 -- around after...");
        return obj;
    }

运行结果:

1 -- around before...
增长用户。。
1 -- around after...

3.后置通知

在目标方法执行以后执行的通知。

在后置通知中也能够选择性的接收一个JoinPoint来获取链接点的额外信息,可是这个参数必须处在参数列表的第一个。

 配置方式:

<!-- 后置通知 -->
<aop:after-returning method="afterReturn" pointcut-ref="pc1"/>
public void afterReturn(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...");
    }

执行结果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
增长用户。。
1 -- around after...
1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...

在后置通知中,还能够经过配置获取返回值

必定要保证JoinPoint处在参数列表的第一位,不然抛异常

配置方式:

<!-- 后置通知 -->
<aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/>
public void afterReturn(JoinPoint jp, Object msg){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]...");
    }

执行结果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
增长用户。。
1 -- around after...
1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...[cjj]...

4.异常通知

在目标方法抛出异常时执行的通知

能够配置传入JoinPoint获取目标对象和目标方法相关信息,但必须处在参数列表第一位

另外,还能够配置参数,让异常通知能够接收到目标方法抛出的异常对象。

配置方法:

<!-- 异常通知 -->
<aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/>
public void afterThrow(JoinPoint jp,Throwable e){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1afterThrow..["+clz+"]..["+name+"].."+e.getMessage());
    }

代码报异常后

执行结果:

1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
1 -- afterThrow..[class cn.tedu.service.UserServiceImple]..[addUser]../ by zero

5.最终通知

是在目标方法执行以后执行的通知。

和后置通知不一样之处在于,后置通知是在方法正常返回后执行的通知,若是方法没有正常返-例如抛出异常,则后置通知不会执行。

而最终通知不管如何都会在目标方法调用事后执行,即便目标方法没有正常的执行完成。

另外,后置通知能够经过配置获得返回值,而最终通知没法获得。

最终通知也能够额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但必定要保证必须是第一个参数。

配置方式:

<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="pc1" />
public void after(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- after..["+clz+"]..["+name+"]...");
    }

执行结果:

复制代码
1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]...
1 -- around before...
增长用户。。
1 -- around after...
1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...[cjj]...
1 -- after..[class cn.tedu.service.UserServiceImple]..[addUser]...
cjj
复制代码

源码

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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-3.2.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd "
    >

    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan>
    
    <!-- proxy-target-class属性值决定是基于接口的仍是基于类的代理被建立 -->
    <aop:config proxy-target-class="true"> 
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* cn.tedu.service.UserServiceImple.addUser(..))" id="pc1"/>        
        
        <!-- 配置切入面 -->
        <aop:aspect ref="firstAspect">
            <!-- 前置通知 -->    
            <aop:before method="before" pointcut-ref="pc1"/>
            
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pc1"/>
            
            <!-- 后置通知 -->
            <!-- <aop:after-returning method="afterReturn" pointcut-ref="pc1"/> -->
            <aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/>
        
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/>
            
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="pc1" />
        </aop:aspect>
    
    </aop:config>
    
</beans>
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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-3.2.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd "
    >

    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan>
    
    <!-- proxy-target-class属性值决定是基于接口的仍是基于类的代理被建立 -->
    <aop:config proxy-target-class="true"> 
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* cn.tedu.service.UserServiceImple.addUser(..))" id="pc1"/>        
        
        <!-- 配置切入面 -->
        <aop:aspect ref="firstAspect">
            <!-- 前置通知 -->    
            <aop:before method="before" pointcut-ref="pc1"/>
            
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pc1"/>
            
            <!-- 后置通知 -->
            <!-- <aop:after-returning method="afterReturn" pointcut-ref="pc1"/> -->
            <aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/>
        
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/>
            
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="pc1" />
        </aop:aspect>
    
    </aop:config>
    
</beans>
复制代码
package cn.tedu.service;
/**
 * 接口
 */
public interface UserService {
    public String addUser(String name);
    public void updateUser();
    public void deleteUser();
    public void query();
}
复制代码
package cn.tedu.service;
/**
 * 接口
 */
public interface UserService {
    public String addUser(String name);
    public void updateUser();
    public void deleteUser();
    public void query();
}
复制代码
package cn.tedu.service;

import org.springframework.stereotype.Service;
/**
 * UserServiceImple:目标对象
 */
@Service("userService")
public class UserServiceImple implements UserService {

    @Override
    public  String addUser(String name) {
        // int i = 1/0;
        System.out.println("增长用户。。");
        return "cjj";
    }

    @Override
    public void updateUser() {
        System.out.println("修改用户。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户。。");
    }

    @Override
    public void query() {
        System.out.println("查询用户。。");
    }
}
复制代码
package cn.tedu.service;

import org.springframework.stereotype.Service;
/**
 * UserServiceImple:目标对象
 */
@Service("userService")
public class UserServiceImple implements UserService {

    @Override
    public  String addUser(String name) {
        // int i = 1/0;
        System.out.println("增长用户。。");
        return "cjj";
    }

    @Override
    public void updateUser() {
        System.out.println("修改用户。。");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除用户。。");
    }

    @Override
    public void query() {
        System.out.println("查询用户。。");
    }
}
复制代码
package cn.tedu.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
/**
 * FirstAspect:切面代码
 */
@Component
public class FirstAspect {
    public void before(JoinPoint jp){ // 能够选择额外的传入一个JoinPoint链接点对象,必须用方法的第一个参数接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 经过JoinPoint对象获取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...["+clz+"]...["+name+"]...");
    }
    
    public Object around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("1 -- around before...");
        Object obj = jp.proceed(); //--显式的调用目标方法
        System.out.println("1 -- around after...");
        return obj;
    }
    
    public void afterReturn(JoinPoint jp, Object msg){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]...");
    }
    
    public void afterThrow(JoinPoint jp,Throwable e){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- afterThrow..["+clz+"]..["+name+"].."+e.getMessage());
    }
    
    public void after(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- after..["+clz+"]..["+name+"]...");
    }
}
复制代码
package cn.tedu.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.springframework.stereotype.Component;
/**
 * FirstAspect:切面代码
 */
@Component
public class FirstAspect {
    public void before(JoinPoint jp){ // 能够选择额外的传入一个JoinPoint链接点对象,必须用方法的第一个参数接收。
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); // 经过JoinPoint对象获取更多信息
        String name = signature.getName();
        System.out.println("1 -- before...["+clz+"]...["+name+"]...");
    }
    
    public Object around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("1 -- around before...");
        Object obj = jp.proceed(); //--显式的调用目标方法
        System.out.println("1 -- around after...");
        return obj;
    }
    
    public void afterReturn(JoinPoint jp, Object msg){
        Class clz = jp.getTarget().getClass();
        Signature signature = jp.getSignature(); 
        String name = signature.getName();
        System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]...");
    }
    
    public void afterThrow(JoinPoint jp,Throwable e){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- afterThrow..["+clz+"]..["+name+"].."+e.getMessage());
    }
    
    public void after(JoinPoint jp){
        Class clz = jp.getTarget().getClass();
        String name = jp.getSignature().getName();
        System.out.println("1 -- after..["+clz+"]..["+name+"]...");
    }
}
复制代码
package cn.tedu.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.tedu.service.UserService;
/**
 * AOPTest:测试代码
 */
public class AOPTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        String result = userService.addUser("cjj"); // 一个链接点
        System.out.println(result);
    }
}
复制代码
package cn.tedu.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.tedu.service.UserService;
/**
 * AOPTest:测试代码
 */
public class AOPTest {
    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        String result = userService.addUser("cjj"); // 一个链接点
        System.out.println(result);
    }
}
复制代码

五种通知的执行顺序

1.在目标方法没有抛出异常的状况下

前置通知

环绕通知的调用目标方法以前的代码

目标方法

环绕通知的调用目标方法以后的代码

后置通知

最终通知

2.在目标方法抛出异常的状况下

前置通知

环绕通知的调用目标方法以前的代码

目标方法 抛出异常 异常通知

最终通知

3.若是存在多个切面

多切面执行时,采用了责任链设计模式。

切面的配置顺序决定了切面的执行顺序,多个切面执行的过程,相似于方法调用的过程,在环绕通知的proceed()执行时,去执行下一个切面或若是没有下一个切面执行目标方法,从而达成了以下的执行过程:

 

若是目标方法抛出异常:

五种通知的常见使用场景

环绕通知

控制事务 权限控制

后置通知

记录日志(方法已经成功调用)

异常通知

异常处理 控制事务

最终通知

记录日志(方法已经调用,但不必定成功)

 
 
posted on  2018
相关文章
相关标签/搜索