Spring与AOP的通知(Advice)详解

12 篇文章 0 订阅

通知(Advice),切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。 常用通知有:前置通知、后置通知、环绕通知、异常处理通知。

通知详解

通知(Advice)是 Spring 提供的一种切面(Aspect)。但其功能过于简单:只能将切面织入到目标类的所有目标方法中, 无法完成将切面织入到指定目标方法中。

前置通知

定义前置通知,需要实现 MethodBeforeAdvice 接口。该接口中有一个方法 before(),会在目标方法执行之前执行。 前置通知的特点:

  • 在目标方法执行之前先执行。
  • 不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行。
  • 不改变目标方法执行的结果。

(1) 定义业务接口与目标类

public interface ISomeService {
	//主业务方法
	void doFirst();
	//主业务方法
	void doSecond();

}
//目标类
public class SomeServiceImpl implements ISomeService {

	//目标方法
	@Override
	public void doFirst() {
		System.out.println("执行doFirst()方法");
	}

	//目标方法
	@Override
	public void doSecond() {
		System.out.println("执行doSecond()方法");
	}

}

(2) 定义前置通知

//前置通知
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {

	/**
	 *当前方法在目标方法执行之前执行
	 *method:目标方法
	 *args:目标方法的参数列表
	 *target:目标对象
	 */
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("执行前置通知方法");
	}
	
}

(3) 定义 Spring 配置文件

    <!-- 注册目标对象 -->
    <bean id="someService" class="com.huang.aop01.SomeServiceImpl"/>
    
    <!-- 注册切面:通知 -->
    <bean id="myAdvice" class="com.huang.aop01.MyMethodBeforeAdvice"/>
    
    <!-- 生成代理对象 -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- <property name="targetName" value="someService"/> -->
    	<!-- 指定目标对象 -->
    	<property name="target" ref="someService"/>
    	<!-- 指定切面 -->
    	<property name="interceptorNames" value="myAdvice"/>
    </bean>

(4) 定义测试类

public class MyTest {
	
	@Test
	public void test01() {
		String resource = "com/huang/aop01/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		//serviceProxy为代理对象,而非目标对象
		ISomeService service = (ISomeService) ac.getBean("serviceProxy");  
		service.doFirst();
		System.out.println("----------------------");
		service.doSecond();
	}

}

(5) 运行结果

{% asset_img aop01.jpg %}

使用 Debug 查看后台运行情况,可以看到代理生成使用的是 JDK 代理机制。

{% asset_img aop02.jpg %}

后置通知

定义后置通知,需要实现接口 AfterReturningAdvice。该接口中有一个方法 afterReturning(),会在目标方法执行之后执行。后置通知的特点:

  • 在目标方法执行之后执行。
  • 不改变目标方法的执行流程,后置通知代码不能阻止目标方法执行。
  • 不改变目标方法执行的结果。

(1) 修改业务接口与目标类

	String doSecond();
	//目标方法
	@Override
	public String doSecond() {
		System.out.println("执行doSecond()方法");
		return "abcde";
	}

(2) 定义后置通知

public class MyAfterReturningAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) 
			throws Throwable {
		System.out.println("执行后置通知方法 returnValue= " + returnValue);
		if (returnValue != null) {
			returnValue = ((String) returnValue).toUpperCase();
			System.out.println("修改过的结果 returnValue=" + returnValue);
			
		}
	}

}

(3) 修改 Spring 配置文件

   <!-- 注册切面:通知 -->
    <bean id="myAdvice" class="com.huang.aop02.MyAfterReturningAdvice"/>

(4) 修改测试类

	@Test
	public void test01() {
		String resource = "com/huang/aop02/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		//serviceProxy为代理对象,而非目标对象
		ISomeService service = (ISomeService) ac.getBean("serviceProxy");  
		service.doFirst();
		System.out.println("----------------------");
		String result = service.doSecond();
		System.out.println(result);
	}
环绕通知

定义环绕通知,需要实现 MethodInterceptor 接口。环绕通知,也叫方法拦截器,可以在目标方法调用之前及之后做处理,可以改变目标方法的返回值,也可以改变程序执行流程。

(1) 定义后置通知

public class MyMethodInterceptor implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("执行环绕通知:目标方法执行之前");
		Object result = invocation.proceed();
		System.out.println("执行环绕通知:目标方法执行之后");
		if (result != null) {
			result = ((String)result).toUpperCase();
			
		}
		return result;
	}

}

(2) 修改 Spring 配置文件

    <!-- 注册切面:通知 -->
    <bean id="myAdvice" class="com.huang.aop03.MyMethodInterceptor"/>
异常通知

定义异常通知,需要实现 ThrowsAdvice 接口。该接口的主要作用是,在目标方法抛出异常后,根据异常的不同做出相应的处理。当该接口处理完异常后,会简单地将异常再次抛出给目标方法。

不过,这个接口较为特殊,从形式上看,该接口中没有必须要实现的方法。但,这个接口却确实有必须要实现的方法 afterThrowing()。这个方法重载了四种形式。由于使用时,一般只使用其中一种,该接口定义为标识接口(没有方法的接口)。

{% asset_img aop03.jpg %}

这四种方法中,常用的形式如下:

public void afterThrowing(自定义的异常类 e)

这里的参数 e 为,与具体业务相关的用户自定义的异常类对象。容器会根据异常类型的不同,自动选择不同的该方法执行。这些方法的执行是在目标方法执行结束后执行的。

(1) 定义异常类的父类

public class UserException extends Exception {
	
	public UserException() {
		super();
	}
	
	public UserException(String message) {
		super(message);
	}

}

(2) 定义两个异常类的子类

public class UsernameException extends UserException {

	public UsernameException() {
		super();
	}
	
	public UsernameException(String message) {
		super(message);
	}
}
public class PasswordException extends UserException {
	
	public PasswordException() {
		super();
	}
	
	public PasswordException(String message) {
		super(message);
	}

}

(3) 定义业务接口。要抛出异常父类

//主业务借口
public interface ISomeService {
	//目标方法
	boolean login(String username,String password)
			throws UserException;
}

(4) 定义目标类

//目标类
public class SomeServiceImpl implements ISomeService {

	@Override
	public boolean login(String username, String password) throws UserException {
		if (!"huang".equals(username)) {
			throw new UsernameException("用户名输错了");
			
		}
		if (!"111".equals(password)) {
			throw new PasswordException("密码输错了");
			
		}
//		double a = 3 / 0;
		return true;
	}


}

(5) 定义异常通知

public class MyThrowsAdvice implements ThrowsAdvice {
	
	// 当目标方法抛出UsernameException异常时,执行当前方法
	public void afterThrowing(UserException ex) {
		System.out.println("发生用户名异常 ex = " + ex.getMessage());
		
	}
	
	// 当目标方法抛出PasswordException异常时,执行当前方法
	public void afterThrowing(PasswordException ex) {
		System.out.println("发生密码异常 ex = " + ex.getMessage());
		
	}
	
	// 当目标方法抛出其它异常时,执行当前方法
	public void afterThrowing(Exception ex) {
		System.out.println("发生异常 ex = " + ex.getMessage());
		
	}

}

(6) 定义测试类

public class MyTest {
	
	@Test
	public void test01() throws UserException {
		String resource = "com/huang/aop05/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		//serviceProxy为代理对象,而非目标对象
		ISomeService service = (ISomeService) ac.getBean("serviceProxy");  
		service.login("huang", "111");
	}

}

通知的其它用法

给目标方法织入多个切面

若要给目标方法织入多个切面,则需要在配置代理对象的切面属性时,设定为 array。

    	<property name="interceptorNames">
    		<array>
    			<value>myBeforeAdvice</value>
    			<value>myAfterAdvice</value>
    		</array>
    	</property>
无接口的 CGLIB 代理生成

若不存在接口,则 ProxyFactoryBean 会自动采用 CGLIB 方式生成动态代理(有接口使用 JDK 的 Proxy 动态代理)。

    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<property name="target" ref="someService"/>
    	<property name="interceptorNames" value="myAdvice"/>
    </bean>

查看后台运行情况,可以看到代理生成使用的是 CGLIB 代理机制。

{% asset_img aop04.jpg %}

有接口的 CGLIB 代理生成

若存在接口,但又需要使用 CGLIB 生成代理对象,此时,只需要在配置文件中增加一个 proxyTargetClass 属性设置,用于指定强制使用 CGLIB 代理机制。

    	<property name="proxyTargetClass" value="true"/>

也可指定 optimize(优化)的值为 true,强制使用 CGLIB 代理机制。

    	<property name="optimize" value="true"/>

查看后台运行情况,可以看到代理生成使用的是 CGLIB 代理机制。

{% asset_img aop05.jpg %}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值