• Spring:AOP面向切面编程



    说明:基于atguigu学习笔记。

    简介

    AOP(Aspect Oriented Programming)是一种面向切面的编程思想。不同于面向对象里的继承思想,当需要为多个不具有继承关系的对象引人同一个公共行为时,也就是把程序横向看,寻找切面,插入公共行为。

    AOP目的是为了些把影响了多个类的公共行为抽取到一个可重用模块里,不通过修改源代码方式,在主干功能里面添加新功能,降低模块间的耦合度,增强代码的可操作性和可维护性。

    例如,每次用户请求我们的服务接口,都要进行权限认证,看看是否登录,就可以在不改变原来接口代码的情况下,假如认证这个新功能。

    Spring AOP底层使用了代理模式。下面具体了解一下。

    AOP底层原理

    代理概念

    所谓代理,也就是让我们的代理对象持有原对象,在执行原对象目标方法的前后可以执行额外的增强代码。

    代理对象需要是原对象接口的实现或原对象的子类,这样就可以在对象引用处直接替换原对象。

    代理方式分静态代理和动态代理,区别在于代理对象生成方式不同

    静态代理:在编译期增强,生成可见的代理class,使用代理类替换原有类进行调用。
    动态代理:在运行期增强,内存中动态生成代理类,使用反射动态调用原对象方法。

    在spring中使用的是JDK、CGLIB动态代理对象。

    JDK动态代理:必须基于接口,即生成的代理对象是对原对象接口的实现,相当于替换了实现类,面向对象中接口可以替换实现类。

    CGLIB动态代:理基于继承,即生成的代理对象是原对象的子类,面向对象中子类可以替换父类。

    JDK动态代理实现

    使用 JDK 动态代理,使用反射包里 java.lang.refelft.Proxy 类的 newProxyInstance 方法创建代理对象。

    源码如下

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);
    
        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();
    
        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
    
        return newProxyInstance(caller, cons, h);
    }
    

    方法有三个参数:
    第一参数,类加载器
    第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
    第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分

    下面以JDK动态代理为例,具体步骤。

    1.创建接口,定义方法

    public interface UserDao {
    	public int add(int a,int b);
    	public String update(String id);
    }
    

    2.创建接口实现类,实现方法

    public class UserDaoImpl implements UserDao {
    	@Override
    	public int add(int a, int b) {
    		return a+b;
    	}
    	
    	@Override
    	public String update(String id) {
    		return id;
    	} 
    }
    

    3.使用 Proxy 类创建接口代理对象

    public class JDKProxy {
    	public static void main(String[] args) {
    		//创建接口实现类代理对象
    		Class[] interfaces = {UserDao.class};
    		// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
    		// @Override
    		// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		// return null;
    		// }
    		// });
    		UserDaoImpl userDao = new UserDaoImpl();
    		UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
    		int result = dao.add(1, 2);
    		System.out.println("result:"+result);
    		} 
    	}
    	
    	//创建代理对象代码
    	class UserDaoProxy implements InvocationHandler {
    		//1 把创建的是谁的代理对象,把谁传递过来
    		//有参数构造传递
    		private Object obj;
    		public UserDaoProxy(Object obj) {
    		this.obj = obj;
    		}
    		
    		//增强的逻辑
    		@Override
    		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		
    		//方法之前
    		System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));
    		
    		//被增强的方法执行
    		Object res = method.invoke(obj, args);
    		
    		//方法之后
    		System.out.println("方法之后执行...."+obj);
    		
    		return res;
    	} 
    }
    

    Spring中的AOP

    相关术语

    1.连接点(Join point): 类里面可以被增强的方法。

    2.切入点:真正被增强的方法。

    3.通知:实际增强处理的逻辑。
    AOP框架汇总通知分为以下几种:

    • 前置通知@Before
    • 后置通知@AfterReturning
    • 环绕通知@Around
    • 异常通知@AfterThrowing
    • 最终通知@After

    4.切面:把通知应用到切入点的过程,是一个动作。

    AspectJ

    AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使
    用,进行 AOP 操作。

    基于 AspectJ 实现 AOP 操作可以有两种方式:基于xml配置文件、基于注解。

    要使用AspectJ,首先要引入相关依赖:

    	 <dependency>
             <groupId>org.aspectjgroupId>
              <artifactId>aspectjweaverartifactId>
          dependency>
    
          <dependency>
              <groupId>org.springframeworkgroupId>
              <artifactId>spring-aopartifactId>
          dependency>
    

    使用AspectJ时,会寻找切入点,这时候会用到切入点表示,为了知道对哪个类里面的哪个方法进行增强。

    语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
    
    举例 1:对 com.example.dao.BookDao 类里面的 add 进行增强
    execution(* com.example.dao.BookDao.add(..))
    
    举例 2:对 com.example.dao.BookDao 类里面的所有的方法进行增强
    execution(* com.example.dao.BookDao.* (..))
    
    举例 3:对 com.example.dao 包里面所有类,类里面所有方法进行增强
    execution(* com.example.dao.*.* (..))
    

    实现AOP

    1.创建项目,引入依赖
    依赖如下:

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>org.examplegroupId>
        <artifactId>spring-demo02artifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>11maven.compiler.source>
            <maven.compiler.target>11maven.compiler.target>
        properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-coreartifactId>
                <version>5.2.6.RELEASEversion>
            dependency>
    
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-beansartifactId>
                <version>5.2.6.RELEASEversion>
            dependency>
    
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>5.2.6.RELEASEversion>
            dependency>
    
            <dependency>
                <groupId>org.aspectjgroupId>
                <artifactId>aspectjweaverartifactId>
                <version>1.8.1version>
            dependency>
    
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-aopartifactId>
                <version>5.2.6.RELEASEversion>
            dependency>
            
        dependencies>
    
    project>
    

    2.创建类

    创建一个自己的类,写一个要增强的方法,并使用注解管理bean

    package com.example;
    
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class User {
    
        public void add () {
            System.out.println("user add method...");
        }
    }
    

    3.创建代理增强类

    创建增强类,使用@Aspect注解。

    在增强类里面,创建方法,让不同方法代表不同通知类型,此例创建前置通知使用@Before

    package com.example;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    
    @Component
    @Aspect
    public class UserProxy {
        @Before(value = "execution(* com.example.User.add())")
        public void before () {
            System.out.println("proxy before...");
        }
    }
    

    4.xml配置
    开启注解扫描和Aspect 生成代理对象

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           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/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        <context:component-scan base-package="com.example">context:component-scan>
    
        
        <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
    
    beans>
    

    5.测试类

    package com.example;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    
    public class AopTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ap = new ClassPathXmlApplicationContext("bean1.xml");
            User user = ap.getBean("user", User.class);
            user.add();
        }
    }
    

    结果先输出proxy before…,再输出user add method…。说明我们的前置通知确实再被增强方法之前执行成功。

    不同通知类型实现

    下面把五种通知都实现看一下顺序,修改我们的代理类如下:

    package com.example;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    
    @Component
    @Aspect
    public class UserProxy {
    
        /**
         * 前置通知
         */
        @Before(value = "execution(* com.example.User.add())")
        public void before () {
            System.out.println("proxy before...");
        }
    
        /**
         * 后置通知
         */
        @AfterReturning(value = "execution(* com.example.User.add())")
        public void afterReturning() {
            System.out.println("proxy afterReturning...");
        }
    
        /**
         * 最终通知
         */
        @After(value = "execution(* com.example.User.add())")
        public void after() {
            System.out.println("proxy after...");
        }
    
        /**
         * 异常通知
         */
        @AfterThrowing(value = "execution(* com.example.User.add())")
        public void afterThrowing() {
            System.out.println("proxy afterThrowing...");
        }
    
        /**
         * 环绕通知
         */
        @Around(value = "execution(* com.example.User.add())")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            // 环绕之前
            System.out.println("proxy around before...");
            proceedingJoinPoint.proceed();
            // 环绕之后
            System.out.println("proxy around after...");
        }
    
    }
    

    执行结果如下:

    proxy around before...
    proxy before...
    user add method...
    proxy around after...
    proxy after...
    proxy afterReturning...
    

    相同的切入点抽取

    上面代码可以看到我们的通知value是相同的,这时候可以抽取出来公用,改写代理类如下代码如下:

    package com.example;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    
    @Component
    @Aspect
    public class UserProxy {
    
        @Pointcut(value = "execution(* com.example.User.add())")
        public void pointDemo() {}
    
        /**
         * 前置通知
         */
        @Before(value = "pointDemo()")
        public void before () {
            System.out.println("proxy before...");
        }
    
        /**
         * 后置通知
         */
        @AfterReturning(value = "pointDemo()")
        public void afterReturning() {
            System.out.println("proxy afterReturning...");
        }
    
        /**
         * 最终通知
         */
        @After(value = "pointDemo()")
        public void after() {
            System.out.println("proxy after...");
        }
    
        /**
         * 异常通知
         */
        @AfterThrowing(value = "pointDemo()")
        public void afterThrowing() {
            System.out.println("proxy afterThrowing...");
        }
    
        /**
         * 环绕通知
         */
        @Around(value = "pointDemo()")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            // 环绕之前
            System.out.println("proxy around before...");
            proceedingJoinPoint.proceed();
            // 环绕之后
            System.out.println("proxy around after...");
        }
    
    }
    

    增强类优先级

    有多个增强类多同一个方法进行增强,设置增强类优先级。

    在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高。

    @Component
    @Aspect
    @Order(1)
    public class UserProxy
    

    完全使用注解开发

    创建配置类,不需要创建 xml 配置文件。

    package com.example;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    
    @Configuration
    @ComponentScan(basePackages = {"com.example"})
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class AopConfig {
    }
    

    测试类:

    package com.example;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    
    public class AopTest {
        public static void main(String[] args) {
            ApplicationContext ap = new AnnotationConfigApplicationContext(AopConfig.class);
            User user = ap.getBean(User.class);
            user.add();
        }
    }
    
  • 相关阅读:
    暑假加餐|有钱人和你想的不一样(第13天)+基于多目标粒子群算法的微电网优化调度(Matlab代码实现)
    你真的了解 RSA 加密算法吗?
    分治算法(Divide&Conquer)
    chrome extensions mv3通过content scripts注入/获取原网站的window数据
    Coursera耶鲁大学金融课程:Financial Markets 笔记Week 01
    创建型设计模式
    axios进阶——取消已经发出去的请求
    HTML 标签简写及全称
    怎么安装一个简单的vue3.0框架。整个流程.::
    jmeter做压测
  • 原文地址:https://blog.csdn.net/qq_43745578/article/details/127097038