Spring AOP教程(作者原创)

举报
摸鱼打酱油 发表于 2022/04/01 22:40:26 2022/04/01
【摘要】 个人简介作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门。@[toc] Spring5 Spring AOPAOP的很多操作都要结合IOC进行AOP是面向切面编程,比如我们要在一个计算器方法中增加一个日志功能:如果我们是用面向对象的话,我们就直接在计算器方法中直接添加代码。如果我们是用面向切面的话,我...

个人简介

作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门。
@[toc]

Spring5

Spring AOP

AOP的很多操作都要结合IOC进行

AOP是面向切面编程,比如我们要在一个计算器方法中增加一个日志功能:

  • 如果我们是用面向对象的话,我们就直接在计算器方法中直接添加代码。
  • 如果我们是用面向切面的话,我们就可以先把这个方法抽取出来,再进行更改

切面

其实就是公共功能的‘类’,“一个切面包含了一个至多个的通知”,例如上面例子的日志功能就是公共功能,我们把这个各个公共功能叫做’横切关注点‘,把这些横切关注点抽取到一个‘类’中这个类就是切面。。,切面的好处就是:‘不必修改源代码,就可以对这个方法进行增强或者更改,还有就是便于维护,业务模块更加简洁。。’

Aop使用场景

如果在不修改源代码的情况下,我们要对某个已存在的方式进行增强,我们可以用AOP

AOP底层原理

使用的是动态代理(Proxy)

  • 如果要增强的方法有接口(实现了接口)的话,就使用这个JDK动态代理 ====import java.lang.reflect.Proxy;
  • 如果要增强的方法没有接口的话,就使用CGLIB动态代理

JDK动态代理

  • 创建接口的‘’实现类‘’的代理对象
  • 通过这个代理对象去对实现类方法的增强,或者是一些改变

需要被增加的实现类

import java.lang.reflect.Proxy;

public interface UserDao {

    int add(int i,int j);

}
public class UserImpl implements UserDao {
    /**
     *如果要该类的add方法进行改动,而不修改这里的源代码,我们可以用动态代理
     * 又因为这个类实现了接口,我们可以用JDK动态代理
     * 反之 CGLIB动态代理
     *
     *
     */

    @Override
    public int add(int i, int j) {
        return i+j;
    }

}

代理类

public static void main(String[] args) throws ClassNotFoundException {
        UserImpl impl=new UserImpl();//创建目标类的对象,以供method.invoke()方法使用
        Class<?> cla = Class.forName("AOP底层实现.UserImpl");//用反射去获取要增强的类的所有信息
        UserDao user=(UserDao) Proxy.newProxyInstance(cla.getClassLoader(), cla.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * 这个类的方法是创建代理对象,对方法进行增强
                 */
                //******获取调用的方法,第一个参数是目标类的对象,第二个是args参数数组
                System.out.println("创建代理对象");
                Object result = method.invoke(impl, args);
                System.out.println("method.invoke调用了返回了结果="+result);//method.invoke就是控制 方法的执行
                return result;//返回结果
            }
        });
        int res = user.add(2, 3);
        System.out.println(res);


    }

CGLIB动态代理过程

  • 创建这个类的子类的代理对象
  • 通过这个子类的代理对象去对父类的方法进行增强

AOP术语

横切关注点

抽取出来的公共功能

切面

存储一个至多个通知的类

通知

横切关注点存储到切面的类,就变成了通知,和横切关注点的区别,只是在不同的位置,叫法不一样罢了

  • 5种通知类型
    • 前置通知 :方法执行之前执行的代码 ===@Before
    • 后置通知:方法执行之后执行的代码 ===@After
    • 返回通知:===@AfterReturning
    • 异常通知:其实就是当方法出现异常时才会调用的方法===@AfterThrowing
    • 环绕通知

目标

被通知的对象,也就是通知所作用的对象,目标被切面作用

代理

向目标对象通知之后创建的代理对象

连接点

其实就是Spring允许你使用通知的地方,或者说和方法有关的前前后后(包括抛出异常)都是连接点

切入点

切入点就是作用的连接点的条件 ,切面作用的点,一定要有切入点,不然切面不知道作用在哪里(切入点其实就是指定“切面要作用哪个方法,也就是切入点表达式 execution(xxx)”)

切面类

@Component
@Aspect//切面的注解,说明这个类是切面
public class myLogAspect {
    /**
     *一个切面用来存储非业务逻辑、公共功能
     */
    //切入点表达式的格式:execution([可见性]返回类型[声明类型].包名.类名.方法名(参数)[异常])

/*
@Before:将方法标注为前置通知
*/
=====
    @Before(value = "execution(public int cglib动态代理.UserImpl.add(int ,int))")//前置通知注解,括号要有切入点表达式(也就是execution(访问修饰符,返回值类型,实现类的方法全名))
    public void beforeLog(){//前置'通知'
        System.out.println("方法执行之前===前置通知");
    }

=======
/**
 @After:将方法标注为后置通知
    作用于finally语句,即不管怎么样,有没有异常,这个方法都会执行
*/
    @After(value = "execution(public int cglib动态代理.UserImpl.add(int,int))")
    public void afterLog(){//后置'通知'
        System.out.println("方法执行之后===后置通知");
    }

}
======
/**
     * @AfterReturning:标注这个方法是返回通知
     * 作用是可以获取到返回值
     * @AfterReturning(execution(xxx),returning="yyy")
     * execution(xxx):和前置通知,后置通知是”一样”的写法
     * returning="yyy":是接受返回值,并将获取到的返回值赋值到yyy这个变量中
     * 此时我们必须在形参李定义一个Object yyy,用于接收returning="yyy"的值
     */
       @AfterReturning(value = "execution(*  aspectj.*.*(..))",returning = "res")
    public void Returning(JoinPoint joinPoint,Object res){
           System.out.println("方法"+joinPoint.getSignature().getName()+"返回通知"+"result="+res);
    }
======
/**
     * @AfterThrowing:就是标注一个方法为异常通知,当这个方法出现Exception时才会执行,否则不执行
     * throwing:用来获取异常信息,这点和返回通知的returning相似,都是用来获取信息,传递到
     * 方法形参中
     */
    @AfterThrowing(value = "execution(* aspectj.UserImpl.*(..))",throwing = "ex")
    public void throwing(Exception ex){
        System.out.println("有异常了,异常类型="+ex);
    }

获取方法信息

加入一个JoinPoint接口作为形参,通过这个JoinPoint接口的‘对象’可以获取到这个方法的信息

JoinPoint

java.lang.Object[] getArgs():获取连接点方法运行时的参数列表;
Signature getSignature():获取连接点的方法签名对象;
方法签名对象.getName()  可以得到方法名


java.lang.Object getTarget():获取连接点所在的目标对象;
java.lang.Object getThis():获取代理对象本身;

JoinPoint使用

@Before(value = "execution(public int aspectj.UserImpl.add(int ,int))")//前置通知注解,括号要有切入点表达式(也就是execution(访问修饰符,返回值类型,实现类的方法全名))

    public void beforeLog(JoinPoint joinPoint){//前置'通知'
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
//        System.out.println(signature.getName());//通过方法签名得到方法名字
        System.out.println(Arrays.toString(args));
        System.out.println(signature.getName()+"方法执行之前===前置通知");
    }

切入点表达式的改进

@Before(value = "execution(* aspectj.UserImpl.*(..))")
改进如下
1.我们可以把public int 也就是访问修饰符和返回值类型===改成   *  号,
这代表着不管是什么访问修饰符和返回值类型都能被作用到
2.我们可以把方法名改成*号,和方法参数改成(..),括号中间两个点。
3.包名也可以改成*号,代表所有的意思
=================================
公共切入点:简化操作
公共切入点需要的注解:@Pointcut   
总结:@Pointcut的操作和@Before、@After一样
==***************************
定义一个******公共切入点*******/**
     * 创建一个空方法,将这个方法加上====     @Pointcut注解   =====,其余写法和@Before、@After一样
     * 意思是将这个方法'=====变成一个公共的切入点=====',方便去调用
     *此时这个方法test()就变成了一个(切入点 ===  execution(* autoProxyTest.UserImpl.*(..)) )
     */
    @Pointcut("execution(* autoProxyTest.UserImpl.*(..))")
    public void test(){
    }
====此时这个test()就相当于execution(xxx),test()就是公共切入点,故调用操作如下:
//    @Before("execution(* autoProxyTest.UserImpl.*(..))")
    @Before("test()")//要加test()
    public void before(JoinPoint joinPoint){
        System.out.println("前置通知"+joinPoint.getSignature().getName()+"方法");
    }
====注意:不是test,而是test()
 @AfterReturning(value = "test()",returning = "res")
    public int returnRes(int res){
        System.out.println("返回通知,返回res="+res);
        return res;
    }

@Order注解

这个@Order是对bean执行顺序进行优先级设置,而不是bean的加载顺序,加载顺序是不能改变的。@Order(数字),数字越小,优先级越高,默认数字是Int的最大值

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。