spring的Aop

  • Post author:
  • Post category:其他


AOP概述

AOP定义

AOP:Aspect Oriented Programming(面向切面编程),是通过预编译和运行期动态代理来实现程序功能的统一维护的技术

不同的业务块有时会具有相同的操作,如图:


将这样相同的操作提取出来就是切面,aop则是面向这些多个业务块横向切取的公共片段编程,在维护期间,仅需要对切面进行修改即可,降低了耦合度,可维护性大大增强

将切面提取之后,原对象和切面就被分隔开:

此时aspect和pointcut互不相关,交由代理将其联系在一起,调用时直接通过代理获取目标对象即可

相关概念

Advice,通知:需要单独封装的功能,定义在类的方法中;通知定义Pointcut中的具体需要执行的操作

前置通知:在目标方法执行之前执行

环绕通知:在目标方法执行之前和之后都可以执行

后置通知:在目标方法执行之后执行

异常通知:在目标方法抛出异常时执行

最终通知:在目标方法执行之后执行,不同于后置通知的是,后置通知只有程序正常时执行,抛出异常时后置通知不会执行;而最终通知不论是否异常都会执行

JoinPoint,连接点:可以使用通知的地方,如事务控制中,使用到事务控制的方法;自身可嵌套其他的JoinPoint

Pointcut,切入点:定义使用通知的连接点的集合,与连接点是一对多的关系

Aspect,切面:通知和切入点的组合

Weaving,织入:把切面应用到应用程序中的过程

Target,目标:应用切面的对象

Introduction,引入:向现有的类添加新方法或新属性

应用场景

日志记录

事务管理

安全控制

异常处理

性能统计

SpringAOP实现方式(编码方式、xml方式、命名空间方式)

前置通知:实现MethodBeforeAdvice接口

后置通知:实现AfterReturningAdvice接口

环绕通知:实现MethodInterceptor接口;环绕通知返回结果为方法的返回值,简单的切面可以只需要用环绕通知就实现了前置、后置、异常通知的功能

异常通知:实现ThrowsAdvice接口;异常通知并没有任何方法,如果使用该接口则必须实现这样格式的方法void afterThrowing([method,args,target],throwable);其中出了异常对象,其他的都可以去掉

代码演示

目标接口

public interface Service {


public void add();

public void update() throws Exception;

}

1

2

3

4

目标实现类

public class MyBeforeAdvice implements MethodBeforeAdvice {

@Override

public void before(Method method, Object[] args, Object target) throws Throwable {


System.out.println(“MyBeforeAdvise before”);

}

}

前置通知

public class MyBeforeAdvice implements MethodBeforeAdvice {

@Override

public void before(Method method, Object[] args, Object target) throws Throwable {


System.out.println(“before”);

}

}

后置通知

public class MyAfterAdvice implements AfterReturningAdvice {


@Override

public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {


System.out.println(“after”);

}

}

环绕通知

public class MyRoundAdvice implements MethodInterceptor {


@Override

public Object invoke(MethodInvocation invocation) throws Throwable {


System.out.println(“开始”);

Object result = invocation.proceed();//执行目标对象的方法,并获取方法返回值

System.out.println(“结束”);

return result;

}

}

异常通知

public class MyThrowsAdvice implements ThrowsAdvice {

public void afterThrowing(Method method,Object[] args,Object target, Exception ex){


System.out.println(“Exception”);

}

}

编码方式

使用ProxyFacoty类的方法setTarget()和addAdvice(),代理使用getProxy获取

代理工厂类

public class MyProxyFactory {

public static <T>T getProxy(Class<T> c) throws Exception {


T t = c.newInstance();

MyBeforeAdvice myBeforeAdvise = new MyBeforeAdvice();

MyAfterAdvice myAfterAdvice = new MyAfterAdvice();

MyRoundAdvice myRoundAdvice = new MyRoundAdvice();

MyThrowsAdvice myThrowsAdvice = new MyThrowsAdvice();

//创建代理工厂

ProxyFactory proxyFactory = new ProxyFactory();

//设置目标与添加切面,将目标与切面联系在一起

//        proxyFactory.setInterfaces(Service.class);该语句为设置实现的接口,当代理接口时使用setInterfaces方法可以指定代理的接口,spring将使用jdk的代理;代理类或没有setInterfaces时spring将使用cglib的代理

proxyFactory.setTarget(t);

proxyFactory.addAdvice(myBeforeAdvise);

proxyFactory.addAdvice(myAfterAdvice);

proxyFactory.addAdvice(myRoundAdvice);

proxyFactory.addAdvice(myThrowsAdvice);

//获取代理对象

return (T) proxyFactory.getProxy();

}

}

测试

public class TestBycode {


@Test

public void test1() throws Exception {


Service proxy = MyProxyFactory.getProxy(ServiceImpl.class);

proxy.add();

proxy.update();

}

}

结果

before

开始

add

结束

after

15:07:45.857 [main] DEBUG o.s.a.f.a.ThrowsAdviceInterceptor – Found exception handler method on throws advice: public void com.aop.bycode.MyThrowsAdvice.afterThrowing(java.lang.reflect.Method,java.lang.Object[],java.lang.Object,java.lang.Exception)

before

开始

update

Exception

java.lang.Exception

at com.aop.bycode.ServiceImpl.update(ServiceImpl.java:20)

at com.aop.bycode.ServiceImpl$$FastClassBySpringCGLIB$$9c5bb4a4.invoke(<generated>)

at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:113)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at com.aop.bycode.MyRoundAdvice.invoke(MyRoundAdvice.java:18)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:57)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)

at com.aop.bycode.ServiceImpl$$EnhancerBySpringCGLIB$$3796a5e3.update(<generated>)

at aop.bycode.TestBycode.test1(TestBycode.java:41)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)

at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)

at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)

at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)

at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)

at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)

at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)

at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)

at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)

at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)

at org.junit.runners.ParentRunner.run(ParentRunner.java:363)

at org.junit.runner.JUnitCore.run(JUnitCore.java:137)

at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)

at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)

at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)

at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)


Process finished with exit code -1


代理类时,spring将使用cglib的代理

代理的类实现了接口时,需要指定代理的接口类型,即使用proxyFactory.setIntegerfaces方法,否则还是使用cglib代理的实现类,指定接口之spring将使用jdk代理接口

xml方式

在spring容器中配置需要代理的目标对象的bean以及通知的bean,然后配置ProxyFactoryBean,指定属性target和interceptorNames,配置好的ProxyFactoryBean就是代理的bean直接注入即可

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”

xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd”>

<bean id=”serviceImpl” class=”com.aop.ServiceImpl”/>

<bean id=”myAfterAdvice” class=”com.aop.MyAfterAdvice”/>

<bean id=”myBeforeAdvice” class=”com.aop.MyBeforeAdvice”/>

<bean id=”myRoundAdvice” class=”com.aop.MyRoundAdvice”/>

<bean id=”myThrowsAdvice” class=”com.aop.MyThrowsAdvice”/>

<!–在配置文件中配置一个ProxyFactoryBean,可以得到一个对应代理的Bean–>

<bean id=”serviceProxy” class=”org.springframework.aop.framework.ProxyFactoryBean”>

<!–设置代理的目标–>

<property name=”target” ref=”serviceImpl”/>

<property name=”interceptorNames”>

<array>

<value>myAfterAdvice</value>

<value>myBeforeAdvice</value>

<value>myRoundAdvice</value>

<value>myThrowsAdvice</value>

</array>

</property>

</bean>

</beans>

使用xml方式不需要指定接口类型,如果代理的类实现了接口,则会使用jdk代理,否则会使用cglib动态代理

命名空间方式(最简洁方便)

execution表达式(切点函数):定义切入点

权限修饰符返回类型 包名.类名.方法名.(参数)

其中,出参数外,其他部分用*表示所有的,参数用…表示所有的

常用的表达式:

(* package.*.*(..))定义切点为package包中的所有类的任何参数的方法

public* package.*.*(..)定义切点为package包中的所有public类的任何参数的方法

通过spring配置文件,引入aop命名空间,通过aop-config标签来配置目标和切面之间的联系

配置文件

<?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=”serviceImpl” class=”com.aop.target.ServiceImpl”/>

<bean id=”myAfterAdvice” class=”com.aop.aspect.MyAfterAdvice”/>

<bean id=”myBeforeAdvice” class=”com.aop.aspect.MyBeforeAdvice”/>

<bean id=”myRoundAdvice” class=”com.aop.aspect.MyRoundAdvice”/>

<bean id=”myThrowsAdvice” class=”com.aop.aspect.MyThrowsAdvice”/>

<aop:config>

<aop:pointcut id=”myPointcut” expression=”execution(* com.aop.target.*.*(..))”/>

<aop:advisor advice-ref=”myThrowsAdvice” pointcut-ref=”myPointcut”/>

<aop:advisor advice-ref=”myBeforeAdvice” pointcut-ref=”myPointcut”/>

<aop:advisor advice-ref=”myAfterAdvice” pointcut-ref=”myPointcut”/>

<aop:advisor advice-ref=”myRoundAdvice” pointcut-ref=”myPointcut”/>

</aop:config>

<aop:aspectj-autoproxy proxy-target-class=”true”/>

</beans>

测试

public class TestByNamespace {


@Test

public void test1() throws Exception {


ApplicationContext context = new ClassPathXmlApplicationContext(“aopNamespace.xml”);

ServiceImpl proxy =  context.getBean(“serviceImpl”,ServiceImpl.class);

proxy.add();

}

}

使用命名空间的注意事项

代理实现类时的误操作

当代理的是实现类时,spring默认使用的是jdk实现动态代理,在通过getbean获取实例时,如果使用getbean(name,classType)方式的话,会报错,因为从spring容器中得到的实例是jdk的Proxy类型,此时则需要使用getBean(name)的方式,然后将获取到的对象强转;或者使用<aop:aspectj-autoproxy proxy-target-class=”true”/>将代理主动设置为cglib的形式,由于cglib代理是通过创建子类的方式,所以传入的目标类的class和获取到的类型是符合的

通知执行的顺序问题

使用命名空间时,定义通知的标签会重新排列通知执行的顺序,当前置通知、后置通知、异常通知生民在环绕通知之前时,会按照原顺序执行;当任一个通知定义在环绕通知之后时,该通知会和环绕通知的前置环绕或后置环绕顺序调换

https://blog.csdn.net/m0_48468380/article/details/118480932