Spring-AOP 代理,增强
目标类+额外功能=代理类
** 一、AOP的作用**
常常通过 AOP 来处理一些具有横切性质的系统性服务,如事物管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案。
- 通过spring AOP的代理功能,给代码增强额外的通用功能
- 业务逻辑就专心处理实际需求,通用的增强功能就能独立出来
AOP概念
AOP概念
-
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是Java应用程序中一个关于横切关注点的很好的例子。在SpringAOP中,切面可以使用通过类(基于模式(XML)的风格)或者在普通类中以@Aspect注解(AspectJ风格)来实现。
-
连接点(Join point):程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在SpringAOP中一个连接点总是代表一个方法的执行。个人理解:AOP拦截到的方法就是一个连接点。通过声明一个org.aspectj.lang.JoinPoint类型参数我们可以在通知(Advice)中获得连接点的信息。
-
通知(Advice):在切面(Aspect)的某个特定连接点上(Join
point)执行的动作。通知的类型包括"around",“before”,"after"等等。通知的类型将在后面进行讨论。许多AOP框架,包括Spring都是以拦截器作为通知的模型,并维护一个以连接点为中心的拦截器链。总之就是AOP对连接点的处理通过通知来执行。个人理解:Advice指当一个方法被AOP拦截到的时候要执行的代码。 -
切入点(Pointcut):匹配连接点(Joinpoint)的断言。通知(Advice)跟切入点表达式关联,并在与切入点匹配的任何连接点上面运行。切入点表达式如何跟连接点匹配是AOP的核心,Spring默认使用AspectJ作为切入点语法。个人理解:通过切入点的表达式来确定哪些方法要被AOP拦截,之后这些被拦截的方法会执行相对应的Advice代码。
-
引入(Introduction):声明额外的方法或字段。Spring AOP允许你向任何被通知(Advice)对象引入一个新的接口(及其实现类)。个人理解:AOP允许在运行时动态的向代理对象实现新的接口来完成一些额外的功能并且不影响现有对象的功能。
-
目标对象(Target object):被一个或多个切面(Aspect)所通知(Advice)的对象,也称作被通知对象。由于Spring AOP是通过运行时代理实现的,所以这个对象永远是被代理对象。个人理解:所有的对象在AOP中都会生成一个代理类,AOP整个过程都是针对代理类在进行处理。
-
织入(Weaving):把切面(aspect)连接到其他的应用程序类型或者对象上,并创建一个被通知对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯AOP框架一样,在运行时完成织入。个人理解:把切面跟对象关联并创建该对象的代理对象的过程。
Spring中AOP的实现
spring中基于xml的aop配置步骤
- 把通知bean也交给spring管理
- 使用aop:config 标签表明开始AOP的配置
- 使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id - 在aop:aspect标签的内部使用对应标签来配置通知的类型
aop:before: 表示配置前置通知
method:属性用于指定Logger(代理类)中那个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式含义指的是对业务层中哪些方法增强
切入点表达式写法
关键字:execetion(表达式)
表达式:访问修饰符 返回值 包名.包名.包名 . . 类名.方法名.(参数列表)
标准表达式写法:
public void com.gxy.service.impl.ProductServiceImpl.insert()
通知(Advice)的类型:
-
前置通知(Before advice):在某个连接点(Join
point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。 -
返回后通知(After returning advice):在某个连接点(Join
point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。 -
抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
-
后置通知(After(finally)advice):当某个连接点(Join
point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。 -
环绕通知(Around advice):包围一个连接点(Join
point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
基于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">
<bean id="productService" class="com.service.impl.ProductServiceImpl"/>
<!--增强功能 IOC容器 -->
<!--前置通知 -->
<bean id="myBeforeAdvice" class="com.aop.MyBeforeAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.service.ProductService.*(..))" id="myPointcut"/><!--切入点-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="myPointcut"/>
</aop:config>
<!--后置通知 -->
<bean id="myAfterAdvice" class="com.aop.MyAfterAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.service.ProductService.*(..))" id="myPointcut"/><!--切入点-->
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="myPointcut"/>
</aop:config>
<!--异常通知 -->
<bean id="myThrowsAdvice" class="com.aop.MyThrowsAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.service.ProductService.*(..))" id="myPointcut"/><!--切入点-->
<aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="myPointcut"/>
</aop:config>
<!--环绕增强 -->
<bean id="myInterceptor" class="com.aop.MyInterceptor"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.service.ProductService.*(..))" id="myPointcut"/><!--切入点-->
<aop:advisor advice-ref="myInterceptor" pointcut-ref="myPointcut"/>
</aop:config>
<!--省略个别bean配置 -->
后置通知
public class MyAfterAdvice implements AfterReturningAdvice{
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("this is AfterReturnAdvice");
//获取反参参数类型
System.out.println("the arg0:"+arg0.getClass());
//获取方法名
System.out.println("the arg1:"+arg1.getName());
//获取目标方法参数的参数列表
for(int i=0;i<arg2.length;i++) {
System.out.println("the arg2:"+arg2[i]);
}
//获取目标实现类的类路径
System.out.println("the arg3: "+arg3.getClass());
}
}
前置通知
public class MyBeforeAdvice implements MethodBeforeAdvice{
/**
* 前置通知
*/
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("this is before MyBeforeAdvice");
//获取目标方法名称
String method = arg0.getName();
System.out.println("the method:"+method);
//获取目标方法参数的实参
for(int i=0;i<arg1.length;i++) {
System.out.println("the arg1:"+arg1[i]);
}
//获取目标类
System.out.println("the arg2:"+arg2.getClass());
}
}
异常通知
public class MyThrowsAdvice implements ThrowsAdvice{
/**
* 异常通知
* @param method
* @param args
* @param target
* @param ex
*/
public void afterThrowing(Method method,Object[] args,Object target,Exception ex ) {
System.out.println(target+"里面的"+method.getName()+"产生了"+ex.getMessage());
}
}
注解形式的环绕通知
扫描器 和开启注解支持
<!--配置spring容器要扫描的包 -->
<context:component-scan base-package="com"></context:component-scan>
<!--配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
注解形式的连接点
@Pointcut("execution(* com.service.ProductService.*(..))")
public void pt() {}
注解形式环绕通知的实现
@Around("pt()")
public Object myAround(ProceedingJoinPoint pjp) {
try {
//获取到目标实现类对象
String actionClass = pjp.getThis().getClass().toString();
//获取到目标方法名称
String methodName = pjp.getSignature().getName();
//获取方法参数
String args="";
Object[] arguments = pjp.getArgs();
for(int i=0;i<arguments.length;i++) {
//用连接符连接参数
args=args+"-"+arguments[i];
}
//将前面的连接符替换成““
args=args.replaceFirst("-", "");
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("bean.xml");
ProductLogService productLogService = (ProductLogService) applicationContext.getBean("productLogService");
ProductLog productLog=new ProductLog();
//获取到pjp得到的参数,将参数以日志的形式输出在数据库中
productLog.setAction_class(actionClass);
productLog.setMethod_name(methodName);
productLog.setArgs(args);
productLogService.insertLog(productLog);
Object object = pjp.proceed();
return object;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}