spring的Aop

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值