Spring学习(6)AOP之通知

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SDDDLLL/article/details/86622163

在前面我们提到过,通知其实就是一种切面,这种切面可以完成简单的织入功能。常见的通知有四种:

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知

一、使用通知之前的准备

1、导入jar包

2、定义目标类bean

package com.fdd.aop01;
public class SomeServiceImpl implements ISomeService {

	public void doFirst() {
		System.out.println("doFirst");
	}
	public void doSecond() {
		System.out.println("doSecond");
	}

}

3、定义通知类

在这里先保留,因为通知分了好几种,使用到的时候再写,在这里先给出整个项目的结构:

4、注册目标类在applicationContext

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd">
     
     <!-- 注册 目标对象-->
     <bean id="someService" class="com.fdd.aop01.SomeServiceImpl"></bean>

</beans>

5、注册通知

在上面的基础之上,添加通知

6、注册代理

使用代理来完成目标对象和通知之间的链接

    <!-- 生成代理对象 -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 指定目标对象 -->
    	<property name="target" ref="someService"></property>
   	<!-- 指定切面 -->
   	<property name="interceptorNames" value="myAdvice"></property>
    </bean>

在这里我们使用的是ProxyFactoryBean类。

里面有三部分:目标类、接口和切面。如果接口不指出来,那么默认的就是CGLB动态代理

7、整体的applicationContext

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd">
     
     <!-- 注册 目标对象-->
     <bean id="someService" class="com.fdd.aop01.SomeServiceImpl">
    </bean>
     
     <!-- 注册切面:通知 -->
    <bean id="myAdvice" class="com.fdd.aop01.MyMethodBefore">	
    </bean>
    
    <!-- 生成代理对象 -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 指定目标对象 -->
    	<property name="target" ref="someService"></property>
   		<!-- 指定切面 -->
   		<property name="interceptorNames" value="myAdvice"></property>
    </bean>

</beans>

二、前置通知

前置通知的特点:

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

首先看前置通知类

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

	//前置方法在目标方法之前执行
	public void before(Method arg0, Object[] arg1, Object arg2)
			throws Throwable {
		System.out.println("执行前置通知方法");
	}

}

接下来注册通知和代理,然后就可以开始测试了

	@Test
	public void test01(){
		String resource = "com/fdd/aop01/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		ISomeService service =(ISomeService) ac.getBean("serviceProxy");
		service.doFirst();
		System.out.println("=====");
		service.doSecond();
	}

看结果:

三、后置通知

后置通知的特点:

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

在这里我们修改SomeServiceImpl。这是为了验证后置通知的特点

public class SomeServiceImpl implements ISomeService {

	public String doFirst() {
		System.out.println("doFirst");
		return "abcde";
	}

	public void doSecond() {
		System.out.println("doSecond");
	}
}

先看后置通知类:

//后置通知:可以获取到目标方法的返回结果,但是无法改变其值
public class MyAfterReturningAdvice implements AfterReturningAdvice {
	//在目标方法执行之后
	//returnValue:目标方法的返回值
	public void afterReturning(Object returnValue, Method arg1, Object[] arg2,
			Object arg3) throws Throwable {
		System.out.println("后置通知内部获取参数值:returnValue:"+returnValue);
		if(returnValue!=null){
			returnValue=((String)(returnValue)).toUpperCase();
			System.out.println("后置通知内部修改之后:returnValue:"+returnValue);
		}
		
	}
}

接下来注册通知和代理,然后开始测试

@Test
	public void test01(){
		String resource = "com/fdd/aop02/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		ISomeService service =(ISomeService) ac.getBean("serviceProxy");
		String a=service.doFirst();
		System.out.println("执行完后置通知之后的返回值:"+a);
		
		System.out.println("=============================");
		service.doSecond();
	}

看结果:

四、环绕通知

环绕通知也叫作方法拦截器。可以在目标方法调用之前和之后进行调用,注意:可以改变目标方法的返回值,也可以改变程序的执行流程

现在看环绕通知类:

//环绕通知:可以修改目标方法的返回结果
public class MyMethodInterceptor implements MethodInterceptor {

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

}

接下来注册通知和代理,然后测试

@Test
	public void test01(){
		String resource = "com/fdd/aop03/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		ISomeService service =(ISomeService) ac.getBean("serviceProxy");
		String a=service.doFirst();
		System.out.println("执行完环绕通知之后的返回值:"+a);
		System.out.println("================================");
		service.doSecond();
	}

看结果:

五、异常通知

异常通知,是在方法抛出异常之后才会执行。当通知处理完异常之后,会将异常再次抛给这个目标方法。

我们验证用户身份的不合理。当用户名不正确的时候,抛出用户名异常,当密码不正确时,抛出密码异常。

1、定义UserException

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

	public UserException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}
}

2、定义两个子类

首先是用户名异常

public class UserNameException extends UserException {

	public UserNameException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public UserNameException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}
}

然后密码异常

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(!"beijing".equals(username)){
			throw new UserNameException("用户名输错了");
		}
		if(!"111".equals(password)){
			throw new PasswordException("密码输错了");
		}
		return true;
	}

}

5、定义异常通知:

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

6、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd">
     
     <!-- 注册 目标对象-->
     <bean id="someService" class="com.fdd.aop05.SomeServiceImpl">
    </bean>
     
     <!-- 注册切面:通知 -->
    <bean id="myAdvice" class="com.fdd.aop05.MyThrowsAdvice">	
    </bean>
    
    <!-- 生成代理对象 -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 指定目标对象 -->
    	<property name="target" ref="someService"></property>
   		<!-- 指定切面 -->
   		<property name="interceptorNames" value="myAdvice"></property>
    </bean>

</beans>

7、测试

(1)用户名错误

public class MyTest {
	
	//两种异常抛出有区别
	/*
	 * 第一:去掉throws UserException,在内部处理异常(铝条)
	 * 第二:不去掉,抛出异常,外部出现错误(红条)
	 */
	@Test
	public void test01() throws UserException{
		String resource = "com/fdd/aop05/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		ISomeService service =(ISomeService) ac.getBean("serviceProxy");
		service.login("西安", "111");
	}

}

看结果:

(2)密码错误

看结果:

(3)用户密码都错误

看结果:

(4)用户名密码都正确

后台没输出

六、给目标织入多个切面

若要给目标方法织入多个切面,则需要在配置代理对象的切面时候,使用list

比如说我们现在把前置通知和后置通知织入同一个目标方法中;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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.xsd">
     
     <!-- 注册 目标对象-->
     <bean id="someService" class="com.fdd.aop06.SomeServiceImpl"></bean>
     
     <!-- 注册切面:前置通知 -->
    <bean id="mybeforeAdvice" class="com.fdd.aop06.MyMethodBefore">	</bean>
    <!-- 注册切面:后置通知 -->
    <bean id="myafterAdvice" class="com.fdd.aop06.MyAfterReturningAdvice">	</bean>
    
    <!-- 生成代码对象 -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<property name="target" ref="someService"></property>
   		
   		<!-- 指定切面 -->
   		<property name="interceptorNames" >
   			<array>
   				<value>mybeforeAdvice</value>
   				<value>myafterAdvice</value>
   			</array>
   		</property>
   		
   	<!-- 	<property name="interceptorNames" value="mybeforeAdvice,myafterAdvice" ></property> -->
   		
    </bean>

</beans>

然后看测试类:

	@Test
	public void test01(){
		String resource = "com/fdd/aop06/applicationContext.xml";
		ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
		ISomeService service =(ISomeService) ac.getBean("serviceProxy");
		
		service.doFirst();
		System.out.println("================");
		service.doSecond();
	}
	

看结果:

猜你喜欢

转载自blog.csdn.net/SDDDLLL/article/details/86622163