栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring AOP介绍与使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring AOP介绍与使用

AOP:Aspect Oriented Programming 面向切面编程
OOP:Object Oriented Programming 面向对象编程

面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对 象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等 方面有非常
重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖 AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。通俗点说的话就是 在程序运
行期间。

在不修改原有代码的情况下 增强跟主要业务没有关系的公共功能代码到 之 前写好的方法中的指定位置 这种编程的方式叫AOP

1、AOP的概念

为什么要引入AOP?

@Service  
public class RoleServiceImpl implements RoleService { 
  
   public Role get(Integer id) { 
       System.out.println("查询Role"); 
       return new Role(); 
   } 
 
   public void add(Role role) { 
     System.out.println("添加Role"); 
   } 

   public void delete(Integer id) { 
     // 日志 
     System.out.println("删除Role"); 

   } 

   public void update(Role role) { 
     System.out.println("修改Role"); 
   } 
}
此代码非常简单,就是基础的三层CRUD的代码实现,此时如果需要 添加日志功能应该怎么做呢,只能在每个方法中添加日志输出,同时如果需要修 改的话会变得非常麻烦。 

按照上述方式抽象之后,代码确实简单很多,但是大家应该已经发现 在输出的信息中并不包含具体的方法名称,我们更多的是想要在程序运行过程中 动态的获取方法的名称及参数、结果等相关信息,此时可以通过使用代理的方式 来进行实现。 

AOP的底层用的代理,代理是一种设计模式 静态代理 弊端:需要为每一个被代理的类创建一个“代理类”,虽然这种方式可以实现,但 是成本太高 动态代理(AOP的底层是用的动态) 
jdk动态代理 :必须保证被代理的类实现了接口, 
cglib动态代理 :不需要接口,

CalculatorProxy.java

package cn.tulingxueyuan.proxy; 

import cn.tulingxueyuan.inter.Calculator; 

import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 
import java.util.Arrays; 

 
public class CalculatorProxy {

	  
 		public static Calculator getProxy(final Calculator calculator){ 


			 //被代理对象的类加载器 
			 ClassLoader loader = calculator.getClass().getClassLoader(); 
			 //被代理对象的接口 
			 Class[] interfaces = calculator.getClass().getInterfaces(); 
			 //方法执行器,执行被代理对象的目标方法 
			 InvocationHandler h = new InvocationHandler() { 
					
					  
				 	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  //利用反射执行目标方法,目标方法执行后的返回值 
	 							// System.out.println("这是动态代理执行的方法"); 
	 							Object result = null; 
	 					try { 
	 							System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asLi st(args)); 	 	result = method.invoke(calculator, args); 
	 							System.out.println(method.getName()+"方法执行完成,结果是:"+ result); 
			 			} catch (Exception e) { 
								System.out.println(method.getName()+"方法出现异常:"+ e.getMessage()); 
	 					} finally { 
				 				System.out.println(method.getName()+"方法执行结束了......"); 
	 					} 
				 		 //将结果返回回去 
	 					 return result;
		 				} 
	 				}; 
				
					Object proxy = Proxy.newProxyInstance(loader, interfaces, h); 
 			 		return (Calculator) proxy; 
 			} 
 }

我们可以看到这种方式更加灵活,而且不需要在业务方法中添加额外 的代码,这才是常用的方式。如果想追求完美的同学,还可以使用上述的日志工 具类来完善。

LogUtil.java

package cn.tulingxueyuan.util; 
import java.lang.reflect.Method; 
import java.util.Arrays; 

public class LogUtil { 

		public static void start(Method method, Object ... objects){ 
 			// System.out.println("XXX方法开始执行,使用的参数是:"+ Arrays.asList(objec ts)); 
 			System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asLi st(objects)); 
 		} 

 		public static void stop(Method method,Object ... objects){ 
 			// System.out.println("XXX方法执行结束,结果是:"+ Arrays.asList(object s)); 
 			System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asLi st(objects)); 

		} 

 		public static void logException(Method method,Exception e){ 
 			System.out.println(method.getName()+"方法出现异常:"+ e.getMessage()); 
 		} 

 		public static void end(Method method){ 
 			System.out.println(method.getName()+"方法执行结束了......"); 
 		} 
}

CalculatorProxy.java

package cn.tulingxueyuan.proxy;
import cn.tulingxueyuan.inter.Calculator; 
import cn.tulingxueyuan.util.LogUtil; 
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.lang.reflect.Proxy; 
import java.util.Arrays; 

 
public class CalculatorProxy { 

	  
 	public static Calculator getProxy(final Calculator calculator){ 


			 //被代理对象的类加载器 
			 ClassLoader loader = calculator.getClass().getClassLoader(); 
			 //被代理对象的接口
			 Class[] interfaces = calculator.getClass().getInterfaces(); 
			 //方法执行器,执行被代理对象的目标方法 
			 InvocationHandler h = new InvocationHandler() { 
			 
				 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  	
					 		//利用反射执行目标方法,目标方法执行后的返回值 
					 		// System.out.println("这是动态代理执行的方法"); 
							 Object result = null; 
							 try { 
								 LogUtil.start(method,args); 
							 	 result = method.invoke(calculator, args); 
								 LogUtil.stop(method,args); 
							 } catch (Exception e) { 
								 LogUtil.logException(method,e); 
							 } finally { 
								 LogUtil.end(method); 
							 } 
							 //将结果返回回去 
							 	return result; 
					} 
				}; 
				Object proxy = Proxy.newProxyInstance(loader, interfaces, h); 
				return (Calculator) proxy; 
		} 
 }

很多同学看到上述代码之后可能感觉已经非常完美了,但是要说明的是,这种动态代理的实现方式调用的是jdk的基本实现,如果需要代理的目标对 象没有实现任何接口,那么是无法为他创建代理对象的,这也是致命的缺陷。而 在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻 松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。

AOP的核心概念及术语 切面(Aspect):

指关注点模块化,这个关注点可能会横切多个对象。 事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP 中,切面可以使用通用类基于模式的方式(schema­based approach)或 者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方 法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总 是代表一个方法的执行。

. **

通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多 种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的 章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。**


切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并 在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法 时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用 AspectJ切点语义。


引入(Introduction): 声明额外的方法或者某个类型的字段。Spring 允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例 如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在 AspectJ社区,引入也被称为内部类型声明(inter))。


目标对象(Target object): 被一个或者多个切面所通知的对象。也被 称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现 的,那么这个对象永远是一个被代理(proxied)的对象。*


AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约 (aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代 理可以是JDK动态代理或CGLIB代理。


织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并 创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用 AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP 框架一样,是在运行时完成织入的。 

AOP的通知类型:

前置通知(Before advice): 在连接点之前运行但无法阻止执行流程 进入连接点的通知(除非它引发异常)。 

后置返回通知(After returning advice):在连接点正常完成后执行的 通知(例如,当方法没有抛出任何异常并正常返回时)。 

后置异常通知(After throwing advice): 在方法抛出异常退出时执行 的通知。

后置通知(总会执行)(After (finally) advice): 当连接点退出的时候 执行的通知(无论是正常返回还是异常退出)。

环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这 是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的 行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛 出异常将执行结束。 

AOP的应用场景

  1. 日志管理
  2. 权限认证
  3. 安全检查
  4. 事务控制

2、Spring AOP的简单配置 在上述代码中我们是通过动态代理的方式实现日志功能的,但是比较 麻烦,现在我们将要使用spring aop的功能实现此需求,其实通俗点说的话,就 是把LogUtil的工具类换成另外一种实现方式。

1、在ioc的基础上添加pom依赖
 		 
 			org.aspectj 
 			aspectjweaver
  			1.9.5 
 		 
  		 
  			org.springframework 
  			spring‐aspects 
  			5.2.3.RELEASE 
 		
2、编写配置将目标类和切面类加入到IOC容器中,

在对应的类上添加组件注解 

给LogUtil添加@Component注解 

给MyCalculator添加@Service注解 

添加自动扫描的配置
  
 

设置程序中的切面类

在LogUtil.java中添加@Aspect注解

设置切面类中的方法是什么时候在哪里执行
在增强模块的类上面标记
	声明为切面
	将切面交给spring去管理

@Aspect
@Component

package cn.tulingxueyuan.util; 

import org.aspectj.lang.annotation.*; 
import org.springframework.stereotype.Component; 
import java.lang.reflect.Method; 
import java.util.Arrays; 

 @Component 
 @Aspect 
 public class LogUtil { 

		  
		// 前置通知 
		 @Before("execution(* cn.tulingxueyuan.service..*.*(..))") 
		 public static void before(){ 
			  
			 System.out.println("方法前"); 
 		 } 

		 // 后置通知 
		 @After("execution(* cn.tulingxueyuan.service..*.*(..))") 
		 public static void after(){ 
		 	 
		 	System.out.println("方法后"); 
		 } 

		 // 后置异常通知 
		 @AfterThrowing("execution(* cn.tulingxueyuan.service..*.*(..))") 
		 public static void afterException(){ 
		 	// System.out.println("方法报错了:"+ex.getMessage()); 
		 	System.out.println("方法异常"); 
		 } 

		 // 后置返回通知 
		 @AfterReturning("execution(* cn.tulingxueyuan.service..*.*(..))") 
		 public static void afterEnd(){ 
		 	 //System.out.println("方法结束,返回值是:"+returnValue); 
		 	 System.out.println("方法返回"); 
		
		 } 
}

开启基于注解的aop的功能

 


 
 


测试

MyTest.java

import cn.tulingxueyuan.inter.Calculator; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContex t;
public class MyTest { 
 
 	public static void main(String[] args){ 
 			ApplicationContext context = new ClassPathXmlApplicationContext("aop.xm l"); 
 			Calculator bean = context.getBean(Calculator.class); 
 			bean.add(1,1); 
 	} 
}

spring AOP的动态代理方式是jdk自带的方式,容器中保存的组件是代 理对象com.sun.proxy.$Proxy对象

4、通过cglib来创建代理对象

package cn.tulingxueyuan.inter; 

import org.springframework.stereotype.Service; 

@Service 
public class MyCalculator { 
 
 	public int add(int i, int j) { 
 		int result = i + j; 
 		return result; 
 	} 

 	public int sub(int i, int j) { 
 		int result = i ‐ j; 
 		return result; 
	} 

 	public int mult(int i, int j) { 
 		int result = i * j;
		return result; 
 	} 

	public int div(int i, int j) { 
 		int result = i / j; 
 		return result; 
 	} 
}
MyTest.java
public class MyTest { 
	public static void main(String[] args){ 
		ApplicationContext context = new ClassPathXmlApplicationContext("aop.xm l"); 
 		MyCalculator bean = context.getBean(MyCalculator.class); 
 		bean.add(1,1); 
 		System.out.println(bean); 
 		System.out.println(bean.getClass()); 
 	} 
}

可以通过cglib的方式来创建代理对象,此时不需要实现任何接口,
代理对象是 class cn.tulingxueyuan.inter.MyCalculatorEnhancerBySpringCGLIB类型

综上所述:

在spring容器中,如果有接口,那么会使用jdk自带的动态 代理,如果没有接口,那么会使用cglib的动态代理。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/601064.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号