IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring的AOP -> 正文阅读

[Java知识库]Spring的AOP

AOP概述

AOP定义

AOP:Aspect Oriented Programming(面向切面编程),是通过预编译和运行期动态代理来实现程序功能的统一维护的技术
不同的业务块有时会具有相同的操作,如图:
在这里插入图片描述

将这样相同的操作提取出来就是切面,aop则是面向这些多个业务块横向切取的公共片段编程,在维护期间,仅需要对切面进行修改即可,降低了耦合度,可维护性大大增强
将切面提取之后,原对象和切面就被分隔开:
在这里插入图片描述
此时aspect和pointcut互不相关,交由代理将其联系在一起,调用时直接通过代理获取目标对象即可

相关概念

  1. Advice,通知:需要单独封装的功能,定义在类的方法中;通知定义Pointcut中的具体需要执行的操作
  1. 前置通知:在目标方法执行之前执行
  2. 环绕通知:在目标方法执行之前和之后都可以执行
  3. 后置通知:在目标方法执行之后执行
  4. 异常通知:在目标方法抛出异常时执行
  5. 最终通知:在目标方法执行之后执行,不同于后置通知的是,后置通知只有程序正常时执行,抛出异常时后置通知不会执行;而最终通知不论是否异常都会执行
    通知执行顺序
  1. JoinPoint,连接点:可以使用通知的地方,如事务控制中,使用到事务控制的方法;自身可嵌套其他的JoinPoint
  2. Pointcut,切入点:定义使用通知的连接点的集合与连接点是一对多的关系
  3. Aspect,切面:通知和切入点的组合
  4. Weaving,织入:把切面应用到应用程序中的过程
  5. Target,目标:应用切面的对象
  6. Introduction,引入:向现有的类添加新方法或新属性

应用场景

  1. 日志记录
  2. 事务管理
  3. 安全控制
  4. 异常处理
  5. 性能统计

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

  1. 前置通知:实现MethodBeforeAdvice接口
  2. 后置通知:实现AfterReturningAdvice接口
  3. 环绕通知:实现MethodInterceptor接口;环绕通知返回结果为方法的返回值,简单的切面可以只需要用环绕通知就实现了前置、后置、异常通知的功能
  4. 异常通知:实现ThrowsAdvice接口;异常通知并没有任何方法,如果使用该接口则必须实现这样格式的方法void afterThrowing([method,args,target],throwable);其中出了异常对象,其他的都可以去掉

代码演示

目标接口

public interface Service {
    public void add();
    public void update() throws Exception;
}

目标实现类

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表达式(切点函数):定义切入点

权限修饰符返回类型 包名.类名.方法名.(参数)
其中,出参数外,其他部分用*表示所有的,参数用…表示所有的
常用的表达式:

  1. (* package.*.*(..))定义切点为package包中的所有类的任何参数的方法
  2. 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();
    }

}

使用命名空间的注意事项

  1. 代理实现类时的误操作

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

  1. 通知执行的顺序问题

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-06 23:58:32  更:2021-07-06 23:59:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 4:47:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码