Spring_10 Spring中的AOP通知[掌握]

一、AOP入门

1、业务层

/**
 * 	 账户的实现类
 */
public class AccountServiceImpl implements IAccountService{

	public void saveAccount() {
		System.out.println("AccountService.saveAccount()");
	}
	
	public void updateAccount(int i) {
		System.out.println("AccountService.updateAccount()"+i);
	}
	
	public int deleteAccount() {
		System.out.println("AccountService.deleteAccount()");
		return 0;
	}

}

2、通知类

/***
 * 记录日志的工具类,它里面提供的公共的代码
 */ 
public class Logger {
	/** 
	 * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
	 */
	public void printLog() {
		System.out.println("Logger.printLog()开始记录日志了");
	}
}

3、配置文件

 <dependencies>
	<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-context</artifactId>
	    <version>5.0.2.RELEASE</version>
	</dependency>
	
	<dependency><!-- 为了解析切入点表达式 -->
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.8.7</version>
	</dependency>
  </dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置Spring的Ioc,把service对象配置进来 -->
	<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
	
	<!-- spring中基于XML的AOP配置步骤
		1、把通知Bean(log)也交给spring来管理
		2、使用aop:config标签表明开始AOP的配置
		3、使用aop:aspect标签表明(配置切面)
				id属性:是给切面提供一个唯一标识
				ref属性:是指定通知类(通知内容)bean的Id
		4、在aop:aspect标签的内部使用对应标签来配置通知的类型
				我们现在示例是让printLog方法在切入点方法执行之前执行:所以是前置通知。
				aop:before:标识配置前置通知
					method属性:用于指定Logger类中哪个方法是前置通知。
					pointCut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
			==================================================================
				切入点表达式的写法:
					关键字:execution(表达式)
					表达式:访问修饰符    返回值   路径 类名 方法名(参数)
						public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
					(1)访问修饰符可以省略
						void com.itheima.service.impl.AccountServiceImpl.saveAccount()
					(2)返回值可以使用通配符,表示任意返回值。
						* com.itheima.service.impl.AccountServiceImpl.saveAccount()
					(3)包名可以使用通配符,表示任意包。但是有几级包,就有几个*.
						* *.*.*.*.AccountServiceImpl.saveAccount()
						包名可以使用..表示当前包及其子包
							* *..AccountServiceImpl.saveAccount()
					(4)类名和方法名可以使用通配符
						* *..*.*()
					(5)参数
						a、可以使用*表示任意参数(不包括无参)	* *..*.*(*)
						a、可以使用..表示任意参数(包括无参)		* *..*.*(..)
						c、可以直接写数据类型:
							基本类型直接写名称		int
							引用类型写包.类名的方式	java.lang.String
					全统配写法:
						* *..*.*(..)
					实际开发中切入点表达式的通常写法:
						切换到业务层实现类下的所有方法
							* com.itheima.service.impl.*.*(..)
			==================================================================	
	 -->
	 
	 <!-- 配置Logger类 -->
	<bean id="logger" class="com.itheima.service.utils.Logger"></bean>
	
	<!-- 配置AOP -->
	<aop:config>
		<!-- 配置切面 -->
		<aop:aspect id="logAdvice" ref="logger">
			<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
			<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(*))"/>
		</aop:aspect>
	</aop:config>
</beans>

4、测试类

/**
 * 测试AOP的配置
 */
public class AOPTest { 

	public static void main(String[] args) {
		//1、获取容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2、获取对象
		IAccountService as = (IAccountService) ac.getBean("accountService");
		as.saveAccount(); 
		as.deleteAccount();
		as.updateAccount(3);
	}
}

5、执行结果

在这里插入图片描述

二、通知类型

1、业务层

/**
 * 	 账户的实现类
 * @author 
 */
public class AccountServiceImpl implements IAccountService{

	public void saveAccount() {
		//int i=1/0;
		System.out.println("AccountService.saveAccount()");
		
	}

	public void updateAccount(int i) {
		System.out.println("AccountService.updateAccount()"+i);
		
	}

	public int deleteAccount() {
		System.out.println("AccountService.deleteAccount()");
		return 0;
	}

}

2、通知类

/**
 * 记录日志的工具类,它里面提供的公共的代码
 */ 
public class Logger {
	/** 
	 * 前置通知
	 */
	public void beforePrintLog() {
		System.out.println("前置通知Logger.beforePrintLog()");
	}
	
	/** 
	 * 后置通知
	 */
	public void afterReturningPrintLog() {
		System.out.println("后置通知Logger.afterReturningPrintLog()");
	}
	
	/** 
	 * 异常通知
	 */
	public void afterThrowingPrintLog() {
		System.out.println("异常通知Logger.afterThrowingPrintLog()");
	}
	
	/** 
	 * 最终通知
	 */
	public void afterPrintLog() {
		System.out.println("最终通知Logger.afterPrintLog()");
	}
	/**
	 * 	环绕通知
	 * 	问题:
	 * 		当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
	 * 	分析:
	 * 		通过对比动态代理中的环绕通知代码
	 * 		发现动态代理的环绕通知有明确的切入点方法调用,而我们代码中没有。
	 * 	解决:
	 * 		spring框架为我们提供一个接口:ProceedingJoinPoint.
	 * 		该接口有一个方法processd(),此方法就相当于明确调用切入点方法。
	 * 
	 * 		该接口可以作为环绕通知的方法参数,在程序执行时,
	 * 		spring框架会为我们提供该接口的实现类供我们使用
	 * 
	 * 	spring中的环绕通知:
	 * 		它是spring框架为我们提供的一种可以在代码中手动控制增强的方法何时执行的方式。
	 */
	public Object aroundPringLog(ProceedingJoinPoint pjp ) {
		Object rtValue = null;
		System.out.println("环绕通知 Logger.aroundPringLog()。。。前置");
		try {
			//Object[] args = pjp.getArgs();//得到方法执行所需的参数
			//System.out.println("123:"+args.toString());
			
			//明确:调用业务层方法(切入点方法)
			rtValue =  pjp.proceed();
			
			System.out.println("环绕通知 Logger.aroundPringLog()。。。后置");
			return rtValue;
		} catch (Throwable e) {
			System.out.println("环绕通知 Logger.aroundPringLog()...异常");
			throw new RuntimeException();
		}finally {
			System.out.println("环绕通知 Logger.aroundPringLog()。。。最终");
		}
		
		
	}
}

3、配置文件

<dependencies>
	<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-context</artifactId>
	    <version>5.0.2.RELEASE</version>
	</dependency>
	
	<dependency><!-- 为了解析切入点表达式 -->
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.8.7</version>
	</dependency>
  </dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置Spring的Ioc,把service对象配置进来 -->
	<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
	 
	 <!-- 配置Logger类 -->
	<bean id="logger" class="com.itheima.service.utils.Logger"></bean>
	
	<!-- 配置AOP -->
	<aop:config>
	<!-- ================================================================================= -->
		<!-- 配置切入点表达式 id属性:指定表达式唯一标志,expression属性:表示内容。
				此标签写在aop:aspect标签内部只能当前切面使用。
				它还可以写在aop:aspect外边,此时就变成了所有切面可用。(此时需要定义在切面之前)
			 -->
		<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))" />
		
	<!-- ================================================================================= -->	
		<!-- 配置切面 -->
		<aop:aspect id="logAdvice" ref="logger">
			<!-- 配置前置通知:(在切入点方法执行之前执行)并且建立通知方法和切入点方法的关联-->
			<aop:before method="beforePrintLog" pointcut-ref="pt1"/>
			
			<!-- 配置后置通知(在切入点方法正常执行之后执行)它和异常只能出现一个-->
			<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
			
			<!-- 配置异常通知(在切入点方法执行产生异常之后执行)它和后置只能出现一个 -->
			<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
			
			<!-- 配置最终通知(无论切入点是否正常执行它都会再其后面执行)-->
			<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
			<!-- <aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"/> -->
			
			<!-- 配置环绕通知:详细的注释请看Logger类中
			<aop:around method="aroundPringLog" pointcut-ref="pt1"/> -->
			
		</aop:aspect>
		
	</aop:config>
</beans>

4、测试类

/**
 * 测试AOP的配置
 * @author 
 */
public class AOPTest { 

	public static void main(String[] args) {
		//1、获取容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2、获取对象
		IAccountService as = (IAccountService) ac.getBean("accountService");
		
		as.saveAccount(); 

	}
}

5、执行结果

在这里插入图片描述

三、基于注解的AOP

1、业务层

/**
 * 	 账户的实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService{

	public void saveAccount() {
		//int i=1/0;
		System.out.println("AccountService.saveAccount()");
		
	}

	public void updateAccount(int i) {
		System.out.println("AccountService.updateAccount()"+i);
		
	}

	public int deleteAccount() {
		System.out.println("AccountService.deleteAccount()");
		return 0;
	}

}

2、通知类

/***
 * 记录日志的工具类,它里面提供的公共的代码
 */ 
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
	/**
	 * 配置切入点表达式
	 */
	@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
	private void pt1() {}
	
	/** 
	 * 前置通知
	 */
	@Before("pt1()")
	public void beforePrintLog() {
		System.out.println("前置通知Logger.beforePrintLog()");
	}
	
	/** 
	 * 后置通知
	 */
	@AfterReturning("pt1()")
	public void afterReturningPrintLog() {
		System.out.println("后置通知Logger.afterReturningPrintLog()");
	}
	
	/** 
	 * 异常通知
	 */
	@AfterThrowing("pt1()")
	public void afterThrowingPrintLog() {
		System.out.println("异常通知Logger.afterThrowingPrintLog()");
	}
	
	/** 
	 * 最终通知
	 */
	@After("pt1()")
	public void afterPrintLog() {
		System.out.println("最终通知Logger.afterPrintLog()");
	}
	/**
	 * 	环绕通知
	 * 	问题:
	 * 		当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
	 * 	分析:
	 * 		通过对比动态代理中的环绕通知代码
	 * 		发现动态代理的环绕通知有明确的切入点方法调用,而我们代码中没有。
	 * 	解决:
	 * 		spring框架为我们提供一个接口:ProceedingJoinPoint.该接口有一个方法processd(),此方法就相当于明确调用切入点方法。
	 * 		该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
	 * 	spring中的环绕通知:
	 * 		它是spring框架为我们提供的一种可以在代码中手动控制增强的方法何时执行的方式。
	 */
//	@Around("pt1()")
//	public Object aroundPringLog(ProceedingJoinPoint pjp ) {
//		Object rtValue = null;
//		System.out.println("环绕通知 Logger.aroundPringLog()。。。前置");
//		try {
//			Object[] args = pjp.getArgs();//得到方法执行所需的参数
//			rtValue =  pjp.proceed();//明确调用业务层方法(切入点方法)
//			System.out.println("环绕通知 Logger.aroundPringLog()。。。后置");
//			return rtValue;
//		} catch (Throwable e) {
//			System.out.println("环绕通知 Logger.aroundPringLog()...异常");
//			throw new RuntimeException();
//		}finally {
//			System.out.println("环绕通知 Logger.aroundPringLog()。。。最终");
//		}
//	}
}

3、配置文件

 <dependencies>
	<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-context</artifactId>
	    <version>5.0.2.RELEASE</version>
	</dependency>
	
	<dependency><!-- 为了解析切入点表达式 -->
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.8.7</version>
	</dependency>
  </dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
         <!-- 
        	1.配置业务层@Service("accountService")
        	2.配置Logger类	@Component("logger")
        	
        	配置AOP
        	
        	1.配置切面类 @Aspect//表示当前类是一个切面类
        	2.在相应的方法上配置
        		 配置切入点表达式
	       			@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
					private void pt1() {}
				配置相应的注解
        			@After @Before @Around @AfterThrowing @AfterReturning("pt()")
         -->
        
        <!-- 配置spring创建容器时所要扫描的包  -->
        <context:component-scan base-package="com.itheima"></context:component-scan>
       
        <!-- 配置spring开启注解AOP的支持 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

4、测试类

/**
 * 测试AOP的配置
 */
public class AOPTest { 

	public static void main(String[] args) {
		//1、获取容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2、获取对象
		IAccountService as = (IAccountService) ac.getBean("accountService");
		
		as.saveAccount(); 
	}
}

5、执行结果

在这里插入图片描述

6、注意:使用纯注解

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值