SpringAOPAOP面向切面编程

Posted 1373i

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringAOPAOP面向切面编程相关的知识,希望对你有一定的参考价值。

目录

一、什么是AOP

二、为什么有AOP

三、AOP相关概念

四、SpringAOP代码案例

1、添加SpringAOP依赖

2、定义切面

3、定义切点

4、实现通知

一、什么是AOP

AOP:AOP—Aspect Oriented Programming(面向切面编程),是对OOP(面向对象编程)的补充与完善。AOP就类似是把登录校验这个一个业务逻辑功能抽取出来,然后动态把这个功能切入到需要的接口(或行为)中,从而减少系统对登录权限校验的重复代码,降低模块之间的耦合度。常用到AOP的就是登录权限校验、日志操作、统一异常格式返回、统一数据格式的返回。

二、为什么有AOP

AOP是一种思想,对OOP思想进行补充与完善,其次他与IoC类似具有解耦合的作用,SpringAOP是对AOP思想的实现

三、AOP相关概念

Aspect(切面)Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的Advice 将要发生的地方。 Advice(通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

四、SpringAOP代码案例

1、添加SpringAOP依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
			<version>2.7.9</version>
		</dependency>

2、定义切面

通过Aspect注解定义切面 

3、定义切点

通过pointcut注解配置拦截规则即要在哪里插入通知

* com.example.demo.*.*(..)

* 返回类型,可以是string、void等 *表示任意类型
com.example.demo.* 要拦截的路径*代表着包底下的全部类
*(..)  方法名(参数列表) *表示任意方法  ..表示任意参数

4、实现通知

通过注解来实现是哪一类通知

注解说明
@After在拦截的目标方法执行后执行该通知
@Before在拦截的目标方法执行前执行该通知
@Around在拦截的目标方法执行前后执行该通知
@AfterReturning在拦截的目标方法返回返回值后执行该通知
@AfterThrowing在拦截的目标方法出现异常后执行该通知

 

AOPSpring大法有多好约定编程



Spring

AOP


AOP(面向切面编程)

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。

AOP技术它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

约定编程

初学者接触AOP可能一脸懵逼,JAVA不是面向对象编程。我们完全抛开AOP,先来看一个约定编程的实例,当看弄明白这个实例后,相信AOP的概念也就那么容易理解了。这个还是需要自己多去实践。

服务方法

先定义一个简单的接口

接口HelloService

package com.pine.aop.service;
       public interface HelloService {  
          public void sayHello(String name);
        }

接口定义了一个sayHello方法,参数是name,这样它就有了一个sayHello方法。

实现类HelloServiceImpl

package com.pine.aop.service.impl;

import com.pine.aop.service.HelloService;

public class HelloServiceImpl implements HelloService{

    @Override
    public void sayHello(String name) {        if(name==null&&name.trim()=="") {            throw new RuntimeException("name is null!!!");
        }
        System.out.println("hello   "+name);
    }
}

实现类将sayHello方法进行实现,对方法的参数name判断是否为空。如果为空,则抛出异常。如果不为空,则打印sayHello

我们直接调用这个方法并且参数不为null和空,控制台将会打印hello和你的参数。但是现在我们想对sayHello方法进行一个拦截,再方法的前后再增加一些方法,这些增加的方法和原方法sayHello组成一套约定的流程。对sayHello方法拦截后会返回一个HelloService类的代理对象proxy,再使用代理对象proxy对方法sayHello进行调用时,程序会根据这套流程去走。


下面我们将分别定义约定的流程和如何将服务类和拦截的方法织入到我们约定的流程并返回代理对象。

拦截器

在约定流程之前,我们先定义一个拦截器的接口,接口定义了一些方法

内容接口Interceptor

package com.pine.aop.intercept;

import java.lang.reflect.InvocationTargetException;

import com.pine.aop.invoke.Invocation;

public interface Interceptor {    //前方法
    public  boolean before();    //后方法
    public void after();    /**     * 取代原有事件的方法     * @param invocation 回调参数,可以通过它的proceed方法,回调原有事件     * @return 原有事件的返回对象     * @throws InvocationTargetException     * @throws IllegalAccessException     */
    public Object around(Invocation invocation) throws InvocationTargetException,IllegalAccessException;    //是否返回方法。事件没有发生异常执行
    public void afterReturning();    //事后异常方法,当事件发生异常时执行
    public void afterThrowing();    //是否使用around()方法取代原有方法
    boolean useAround();
}

其中around方法里有个参数invocation,主要用于回调原有的方法。

Invocation

package com.pine.aop.invoke;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Invocation {
    private Object[] params;
    private Method method;
    private Object target;

    public Invocation(Object target,Method method,Object[] params) {        this.target=target;        this.method=method;        this.params=params;
    }    //反射方法
    public Object proceed() throws InvocationTargetException,IllegalAccessException{        return method.invoke(target, params);
    }

    public Object[] getParams() {        return params;
    }

    public void setParams(Object[] params) {        this.params = params;
    }

    public Method getMethod() {        return method;
    }

    public void setMethod(Method method) {        this.method = method;
    }

    public Object getTarget() {        return target;
    }

    public void setTarget(Object target) {        this.target = target;
    }

}

其中的proceed方法,它会以反射的形式去调用原有的方法。

接着我们去实现内容接口MyInterceptor

实现类MyInterceptor

package com.pine.aop.intercept.impl;

import java.lang.reflect.InvocationTargetException;

import com.pine.aop.intercept.Interceptor;
import com.pine.aop.invoke.Invocation;

public class MyInterceptor implements Interceptor{

    @Override
    public boolean before() {
        System.out.println("before--------------");        return true;
    }

    @Override
    public void after() {
        System.out.println("after--------------");

    }

    @Override
    public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("around before--------------");        Object obj=invocation.proceed();
        System.out.println("around after--------------");        return obj;
    }

    @Override
    public void afterReturning() {
        System.out.println("afterReturning--------------");
    }

    @Override
    public void afterThrowing() {
        System.out.println("afterThrowing--------------");
    }

    @Override
    public boolean useAround() {        return true;
    }

}

约定流程

当调用proxy代理对象方法后(后面会讲获取代理对象proxy,这里主要看流程的定义,我们先假设拿到了代理对象,意味者对方法拦截成功),我们定义流程如下。


(1)使用proxy调用方法时会先执行拦截器的before方法。


(2)如果拦截器的useAround方法返回true,则执行拦截器的around方法,而不调用target原对象对应的方法,但around方法的参数invoaiton对象存在一个proceed方法,用target对象对应的方法;如果useAround方法返回false,则直接调用target对象的事件方法。


(3)无论怎么样,在完成之前的事情后,都会执行拦截器的after方法。


(4)在执行around方法或者回调target的事件方法时,可能发生异常,也可能不发生异常。如果发生异常,就执行拦截器的afterThrowing方法,否则就执行afterReturning方法。

具体流程图如下:

【AOP】Spring大法有多好(三)约定编程

代理逻辑实现

我们先理解一下什么是动态代理。例如,当你需要采访一名儿童时,首先需要经过他父母的同意,在一些问题上父母会替他回答,而另外一些问题,父母觉得不太适合这个小孩会替孩子拒绝掉,显然父母就是这名儿童的代理(proxy)。通过代理可以增强或者控制对真实对象(target)的访问。


JDK中提供了类Proxy的静态方法newPorxyInstance来生成一个代理对象

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

它有三个参数:

ClassLoader loader: 类加载器

Class<?>[] interfaces:绑定的接口,也就是把代理对象绑定到哪些接口下,可以时多个。

InvocationHandler h:绑定代理对象逻辑实现

这里InvocationHandler 时一个接口,它定义了一个invoke方法,这个方法就是实现代理对象的逻辑实现的,其源码如下:

public interface InvocationHandler {       public Object invoke(Object proxy, Method method, Object[] args)        throws Throwable;}

这里通过目标对象(target)、方法(method)和参数(args)就可以反射方法运行了。

为了将服务类和拦截方法织入对应的流程,并且返回代理对象。这里编写了一个ProxyBean类,

ProxyBean

package com.pine.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.pine.aop.intercept.Interceptor;
import com.pine.aop.invoke.Invocation;

public class ProxyBean implements InvocationHandler{
    private Object target=null;
    private Interceptor interceptor=null;    /**     * 绑定代理对象     * @param target 被代理对象       * @param interceptor 拦截器     * @return 代理对象     */
    public static Object getProxyBean(Object target,Interceptor interceptor) {
        ProxyBean proxyBean=new ProxyBean();        //保存被代理的对象
        proxyBean.target=target;        //保存拦截器
        proxyBean.interceptor=interceptor;        //生成代理对象
        Object proxy=Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),proxyBean);        //Object proxy=Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getClasses(), proxyBean);
        //返回代理对象
        return proxy;
    }    /**     * 处理代理对象方法逻辑     * @param proxy 代理对象       * @param method 当前方法     * @param args 运行参数     * @return 方法调用结果     * @throws Throwable 异常     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //异常标识
        boolean exceptionFlag=false;
        Invocation invocation=new Invocation(target, method, args);        Object retObj=null;        try {            if(this.interceptor.before()) {
                retObj=this.interceptor.around(invocation);
            }else {
                retObj=method.invoke(target, args);
            }
        }catch(Exception e){            //异常
            exceptionFlag=true;
        }        this.interceptor.after();        if(exceptionFlag) {            this.interceptor.afterThrowing();
        }else {            this.interceptor.afterReturning();            return retObj;
        }        return null;
    }


}

首先,这个ProxyBean实现了接口InvocationHandler,因此就可以定义invoke方法了。其中在getBean方法中,我们让其盛恒了一个代理对象,并且创建了一个proxyBean实例保存目标对象(target)和拦截器(interceptor),为后面的调用做好准备。其次,生成了一个代理对象,而这个代理对象挂在target实现的接口下,所以可以用target对象实现的接口对这个代理对象实现强制类型转换,并且将这个代理对象的逻辑挂在ProxyBean实例下,这样就完成了目标对象target和代理对象proxy的绑定。最后,将代理对象返回给调用者。

测试代码


测试

package com.pine.aop.test;

public class Test {

    public static void main(String[] args) {
        testProxy();
    }

    private static void testProxy() {

        HelloService helloService=new HelloServiceImpl();        //按照约定获取proxy
        HelloService proxy=(HelloService)ProxyBean.getProxyBean(helloService,new MyInterceptor());
        System.out.println("\n################ invoke method sayHello ##########################\n");
        proxy.sayHello("Pine");
        System.out.println("\n################ invoke method sayHello param name is null!##########################\n");
        proxy.sayHello(null);
    }

}


结果

################ invoke method sayHello ##########################

before--------------
around before--------------
hello   Pine
around after--------------
after--------------
afterReturning--------------

################ invoke method sayHello param name is null!##########################

before--------------
around before--------------
after--------------
afterThrowing--------------

总结





上学的机会是受人控制的,但读书与实践才是获取知识的主要课堂,在这个学校中学习的权力只掌握在你自己手中,是任何人都剥夺不了的。让学习成为一种生活的习惯,这比任何名牌大学的校徽重要得多!



【AOP】Spring大法有多好(三)约定编程
【AOP】Spring大法有多好(三)约定编程
【AOP】Spring大法有多好(三)约定编程

2296SKR

【AOP】Spring大法有多好(三)约定编程

关注 2296SKR 不迷路

菜鸟进阶|中级磨炼|高级修仙



以上是关于SpringAOPAOP面向切面编程的主要内容,如果未能解决你的问题,请参考以下文章

图文并茂!!!一文搞懂SpringAOP(面向切面编程)

图文并茂!!!一文搞懂SpringAOP(面向切面编程)

java怎么运用切面编程生成日志

java aop面向切面编程

Spring常见面试题

spring入门-AOP 面向切面编程