三、Spring AOP面向切面编程

版权声明:未经本人同意,不得私自转载 https://blog.csdn.net/weixin_41866744/article/details/87892859

AOP( Aspect Oriented Programming ):面向切面编程

正常程序执行流程都是纵向执行流程,而AOP面向切面编程,是在原有纵向执行流程中添加横切面,不需要修改原有程序代码,实现高扩展性,原有功能相当于释放了部分逻辑.让职责更加明确.。

面向切面编程是什么?

       在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫做面向切面编程.

常用概念:

原有功能: 切点, pointcut     前置通知: 在切点之前执行的功能. before advice

后置通知: 在切点之后执行的功能,after advice    如果切点执行过程中出现异常,会触发异常通知.throws advice

所有功能总称叫做切面.    织入: 把切面嵌入到原有功能的过程叫做织入

spring 提供了 2 种 AOP 实现方式:

1.Schema-based:每个通知都需要实现接口或类;配置 spring 配置文件时在<aop:config>配置

2 AspectJ:每个通知不需要实现接口或类;配置 spring 配置文件是在<aop:config>的子标签<aop:aspect>中配置

Schema-based  实现步骤:

1. 导入 jar

2.新建前置通知、后置通知:

package com.tao.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;
/**
* 前置通知
*/
public class MyBeforeAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		System.out.println("切点方法对象"+arg0+",方法名:"+arg0.getName());
		System.out.println("切点方法参数"+arg1);
		System.out.println("对象"+arg2);
		System.out.println("执行前置通知。。。");
	}

}
package com.tao.advice;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
/**
 * 后置通知
 */
public class MyAfterAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
		System.out.println("执行后置通知。。。");
	}

}

3.配置Spring的配置文件:

<?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">
	       
	<!-- 配置 Demo 类,测试使用 -->      
	<bean id="demo" class="com.tao.testAop.Demo"></bean>
	
	<bean id="mybeforeAdvice" class="com.tao.advice.MyBeforeAdvice"></bean>
	<bean id="myafterAdvice" class="com.tao.advice.MyAfterAdvice"></bean>
	
	<!-- 配置切面 -->
	<aop:config>
	
		<!-- 切面配置表达式通配符写法:
			1.目前只拦截com.tao.testAop包下Demo类的demo2方法
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/>
			
			2.拦截com.tao.testAop包下Demo类的任意方法形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.*())" id="demo2point"/>
			
			3.拦截com.tao.testAop包下Demo类的任意方法及匹配方法的任意参数(不管几个不管什么类型)形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.*(..))" id="demo2point"/>
			
			4.拦截com.tao.testAop包下任意类的任意方法 形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.*.*(..))" id="demo2point"/>
				
			5.在分模块的时候,我们假设需要拦截无论哪个模块下service下任意实现类的任意方法 (下面是com.tao项目下任意模块任意实现类的任意方法)
				<aop:pointcut expression="execution(* com.tao.*.service.impl.*.*(..))" id="demo2point"/>
		 -->
	
		<!-- 配置切点 -->
		<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/>
		<!-- 切点方法多个参数时,表达式参考写法
		<aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String) and args(name))" id=""/>
		<aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String,int) and args(name,age))" id=""/> -->
		
		<!-- 通知 -->
		<aop:advisor advice-ref="mybeforeAdvice" pointcut-ref="demo2point"/>
		<!-- 通知 -->
		<aop:advisor advice-ref="myafterAdvice" pointcut-ref="demo2point"/>
	</aop:config>        
        
</beans>
package com.tao.testAop;

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

public class Demo {
	
	public void demo1(){
		System.out.println("demo1");
	}

	public void demo2(){
		System.out.println("demo2");
	}
	
	public void demo3(){
		System.out.println("demo3");
	}
	
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		Demo demo = ac.getBean("demo",Demo.class);
		demo.demo1();
		demo.demo2();
		demo.demo3();
	}
}

异常通知:

基于schema-base方式的实现:

1.新建一个类实现 throwsAdvice 接口: (1) 必须自己写方法,且必须叫 afterThrowing (2)有两种参数方式:必须是 1 个或 4 个

   (3)异常类型要与切点报的异常类型一致

异常通知类:

package com.tao.Throw;

import org.springframework.aop.ThrowsAdvice;

public class MyThrow implements ThrowsAdvice{

	public void afterThrowing(Exception ex) throws Throwable{
		System.out.println("执行异常通知,通过-schema-base 方式");
	}
}

在demo2方法中制造一个异常:

package com.tao.testAop;

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

public class Demo {
	
	public void demo1(){
		System.out.println("demo1");
	}

	public void demo2(){
		int i =5/0;
		System.out.println("demo2");
	}
	
	public void demo3(){
		System.out.println("demo3");
	}
	
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		Demo demo = ac.getBean("demo",Demo.class);
		demo.demo1();
		demo.demo2();
		demo.demo3();
	}
}

applicationContext.xml异常通知配置:

<?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">
	       
	<!-- 配置 Demo 类,测试使用 -->      
	<bean id="demo" class="com.tao.testAop.Demo"></bean>
	
	<bean id="mybeforeAdvice" class="com.tao.advice.MyBeforeAdvice"></bean>
	<bean id="myafterAdvice" class="com.tao.advice.MyAfterAdvice"></bean>
	
	<!-- 配置切面 -->
	<aop:config>
	
		<!-- 切面配置表达式通配符写法:
			1.目前只拦截com.tao.testAop包下Demo类的demo2方法
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/>
			
			2.拦截com.tao.testAop包下Demo类的任意方法形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.*())" id="demo2point"/>
			
			3.拦截com.tao.testAop包下Demo类的任意方法及匹配方法的任意参数(不管几个不管什么类型)形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.*(..))" id="demo2point"/>
			
			4.拦截com.tao.testAop包下任意类的任意方法 形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.*.*(..))" id="demo2point"/>
				
			5.在分模块的时候,我们假设需要拦截无论哪个模块下service下任意实现类的任意方法 (下面是com.tao项目下任意模块任意实现类的任意方法)
				<aop:pointcut expression="execution(* com.tao.*.service.impl.*.*(..))" id="demo2point"/>
		 -->
	
		<!-- 配置切点 -->
		<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/>
		<!-- 切点方法多个参数时,表达式参考写法
		<aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String) and args(name))" id=""/>
		<aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String,int) and args(name,age))" id=""/> -->
		
		<!-- 前置通知 -->
		<aop:advisor advice-ref="mybeforeAdvice" pointcut-ref="demo2point"/>
		
		<!-- 后置通知 -->
		<aop:advisor advice-ref="myafterAdvice" pointcut-ref="demo2point"/>
		
		<!-- 异常通知 -->
		<aop:advisor advice-ref="mythrow" pointcut-ref="demo2point"/>
	</aop:config>       
	
	<bean id="mythrow" class="com.tao.Throw.MyThrow"></bean> 
	
        
</beans>

配置完成。

基于AspectJ 方式的实现:

package com.tao.Throw;
/**
 * Aspect方式 实现异常通知
 */
public class MyAspectJThrow {
	
	public void myexception(Exception ex){
		System.out.println("执行异常通知,通过-Aspect 方式"+ex.getMessage());
	}
	
}
<?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">
	       
	<!-- 配置 Demo 类,测试使用 -->      
	<bean id="demo" class="com.tao.testAop.Demo"></bean>
	
	<bean id="mybeforeAdvice" class="com.tao.advice.MyBeforeAdvice"></bean>
	<bean id="myafterAdvice" class="com.tao.advice.MyAfterAdvice"></bean>
	
	<!-- 配置切面 -->
	<aop:config>
	
		<!-- 切面配置表达式通配符写法:
			1.目前只拦截com.tao.testAop包下Demo类的demo2方法
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/>
			
			2.拦截com.tao.testAop包下Demo类的任意方法形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.*())" id="demo2point"/>
			
			3.拦截com.tao.testAop包下Demo类的任意方法及匹配方法的任意参数(不管几个不管什么类型)形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.Demo.*(..))" id="demo2point"/>
			
			4.拦截com.tao.testAop包下任意类的任意方法 形成切面
				<aop:pointcut expression="execution(* com.tao.testAop.*.*(..))" id="demo2point"/>
				
			5.在分模块的时候,我们假设需要拦截无论哪个模块下service下任意实现类的任意方法 (下面是com.tao项目下任意模块任意实现类的任意方法)
				<aop:pointcut expression="execution(* com.tao.*.service.impl.*.*(..))" id="demo2point"/>
		 -->
	
		<!-- 配置切点 -->
		<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo2())" id="demo2point"/>
		<!-- 切点方法多个参数时,表达式参考写法
		<aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String) and args(name))" id=""/>
		<aop:pointcut expression="execution(* com.tap.testAop.Demo.demo2(String,int) and args(name,age))" id=""/> -->
		
		<!-- 前置通知 -->
		<aop:advisor advice-ref="mybeforeAdvice" pointcut-ref="demo2point"/>
		
		<!-- 后置通知 -->
		<aop:advisor advice-ref="myafterAdvice" pointcut-ref="demo2point"/>
		
		
		<!-- 异常通知  AspectJ  -->
		<aop:aspect ref="myAspectJThrow">
			<aop:after-throwing method="myexception" pointcut-ref="demo2point" throwing="ex"/>
		</aop:aspect>
	</aop:config>       
	
	<!-- 异常通知 Aspect 方式实体 -->
	<bean id="myAspectJThrow" class="com.tao.Throw.MyAspectJThrow"></bean>
        
</beans>

AspectJ 方式 配置完成。

环绕通知:把前置通知和后置通知都写到一个通知中,组成了环绕通知

步骤:编写环绕通知实体类,实现MethodInterceptor 接口;配置applicationContext.xml

package com.tao.around;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * 环绕通知
 */
public class MyAroundAdvisor implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		System.out.println("环绕-前置");
		Object resutl = methodInvocation.proceed();
		System.out.println("环绕后置");
		return resutl;
	}

}
<?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">
	       
        <!-- 配置 Demo 类,测试使用 -->      
	<bean id="demo" class="com.tao.testAop.Demo"></bean>	

	<aop:config>
		<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo1())" id="demo1point"/>
		<aop:advisor advice-ref="myAroundAdvisor" pointcut-ref="demo1point"/>
	</aop:config>

	<!-- 环绕通知 -->
	<bean id="myAroundAdvisor" class="com.tao.around.MyAroundAdvisor"></bean>
	
        
</beans>

AspectJ 最简单实现通知(也能同schema-base一样获得方法参数):

<aop:after/> 后置通知,是否出现异常都执行   <aop:after-returing/> 后置通知,只有当切点正确执行时执行

<aop:after/> 和 <aop:after-returing/> 和<aop:after-throwing/>执行顺序和配置顺序有关

execution() 括号不能扩上 args  中间使用 and 不能使用&& 由 spring 把 and 解析成&&

 args(名称) 名称自定义的.顺序和 demo1(参数,参数)对应

 <aop:before/> arg-names=” 名 称 ” 名 称 来 源 于expression=”” 中 args(),名称必须一样

args() 有几个参数,arg-names 里面必须有几个参数 arg-names=”” 里面名称必须和通知方法参数名对应

package com.tao.advice;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 使用AspectJ实现简单通知
 */
public class CommonAdvice {
	
	public void mybefore(String name1,int age1){
		System.out.println("前置"+name1+age1 );
	}
	
	public void mybefore1(String name1){
		System.out.println("前置:"+name1);
	}
	
	//后置 出异常不执行
	public void myaftering(){
		System.out.println("后置 2");
	}
	
	//后置出异常正常执行 类似于finally
	public void myafter(){
		System.out.println("后置 1");
	}
	
	public void mythrow(){
		System.out.println("异常通知");
	}
	
	public Object myarround(ProceedingJoinPoint p) throws Throwable{
		System.out.println("执行环绕");
		System.out.println("环绕-前置");
		Object result = p.proceed();
		System.out.println("环绕后置");
		return result;
	}
	
	

}
package com.tao.testAop;

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

public class Demo {
	
	public void demo1(){
		System.out.println("demo1");
	}

	public void demo2(){
		int i =5/0;
		System.out.println("demo2");
	}

	public void demo3(){
		System.out.println();
	}
	
	public void demo3(String name){
		System.out.println("demo3"+name);
	}
	
	public void demo3(String name,int age){
		System.out.println("demo3"+name+" "+age);
	}
	
	
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		Demo demo = ac.getBean("demo",Demo.class);
		//demo.demo1();
		//.demo2();
		demo.demo3();
		demo.demo3("两个参数",1);
		demo.demo3("一个参数");
		
	}
}

applicationContext.xml配置 关键:

<?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">
	       
	<!-- 配置 Demo 类,测试使用 -->      
	<bean id="demo" class="com.tao.testAop.Demo"></bean>
	<bean id="commonAdvice" class="com.tao.advice.CommonAdvice"></bean>
	
	<!-- 使用AspectJ实现简单通知 -->
	<aop:config>
		<aop:aspect ref="commonAdvice">
			<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo3())" id="demo3pointnull"/>
			<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo3(String,int)) and args(name1,age1)" id="demo3point"/>
			<aop:pointcut expression="execution(* com.tao.testAop.Demo.demo3(String)) and args(name1)" id="demo3point1"/>
			<!-- 前置通知 并获得参数值 -->
			<aop:before method="mybefore" pointcut-ref="demo3point" arg-names="name1,age1" />	
			<aop:before method="mybefore1" pointcut-ref="demo3point1" arg-names="name1" />
			
			<!-- 后置 遇到异常继续执行 -->
			<aop:after method="myafter" pointcut-ref="demo3pointnull"/>
			<!-- 后置 遇到异常不执行 -->
			<aop:after-returning method="myaftering" pointcut-ref="demo3pointnull"/>
			<!-- 异常通知 -->
			<aop:after-throwing method="mythrow" pointcut-ref="demo3pointnull"/>
			<!-- 环绕通知 -->
			<aop:around method="myarround" pointcut-ref="demo3pointnull"/>
		</aop:aspect>
	
	</aop:config>
	
        
</beans>

基于注解实现Spring Aop:

1.开启注解扫描,以及aspectj-autoproxy切面自动织入

<?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">
	 
	<!-- 指定Spring注解扫描路径 -->
	<context:component-scan base-package="com.tao.advice,com.tao.testAop"></context:component-scan>       
	
	<!-- proxy-target-class="true"  表示使用cglib动态代理  注解这块都是基于cglib做的
		 proxy-target-class="false" 表示使用jdk动态代理
	 -->
	<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>	
	
</beans>

2.新建一个要指定实现Aop的切点类:

@Component 注解:相当于<bean>; 如果没有参数,把类名首字母变小写,相当于<bean id=" demo"/>

也可以自定义名称:@Component(“自定义名称”)  例如下面 也可以 @component("demo123") 

@Pointcut注解: 切点注解,指定方位为切点 @Pointcut(execution(* 路径)) 可参考上述xml配置中的切点表达式的写法

package com.tao.testAop;

import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class Demo {
	
	@Pointcut("execution(* com.tao.testAop.Demo.demo1())")
	public void demo1(){
		System.out.println("demo1");
	}
	
	
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		Demo demo = ac.getBean("demo",Demo.class);
		demo.demo1();
		
		
	}
}

3.配置切面,及指定具体的通知处理方法:

@Aspect 指定切面类

package com.tao.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 使用AspectJ实现简单通知
 */

@Component
@Aspect
public class CommonAdvice {

	
	@Before("com.tao.testAop.Demo.demo1()")
	public void before(){
		System.out.println("前置 通知");
	}
	
	//后置 出异常不执行
	@AfterReturning
	public void myaftering(){
		System.out.println("后置 通知,异常不执行");
	}
	
	//后置出异常正常执行 类似于finally
	@After("com.tao.testAop.Demo.demo1()")
	public void myafter(){
		System.out.println("后置 通知 异常执行");
	}
	
	@AfterThrowing("com.tao.testAop.Demo.demo1()")
	public void mythrow(){
		System.out.println("异常通知");
	}
	
	@Around("com.tao.testAop.Demo.demo1()")
	public Object myarround(ProceedingJoinPoint p) throws Throwable{
		System.out.println("执行环绕");
		System.out.println("环绕-前置");
		Object result = p.proceed();
		System.out.println("环绕后置");
		return result;
	}
	

}

测试,运行Demo.java main函数:

至此Aop常见配置方式完成。

猜你喜欢

转载自blog.csdn.net/weixin_41866744/article/details/87892859
今日推荐