Spring系列课程--AOP编程

Spring系列课程–AOP编程

第一章 静态代理设计模式

1. 为什么需要代理设计模式

1.1 问题
  • 在JavaEE分层开发中,哪个层次对于我们来说最重要

    DAO --> Service --> Controller
    Service层最重要,业务逻辑都写在了Service层
    
  • Service层中包含了哪些代码?

    Service层 = 核心功能(几十行 上百行代码) + 额外功能(附加功能)
    1. 核心功能
    	业务运算
    	DAO调用
    2. 额外功能
    	1)不属于业务
    	2)可有可无
    	3)代码量很小
    	
    	事务、日志、性能......
    
    
  • 额外功能写在Service好不好?

    Service层的调用者的角度(Controller):需要在Service层书写额外功能
    						软件设计者: Service层不需要额外功能
    
  • 显示生活中解决方式

    image-20201108162319108

2. 代理设计模式

1.1 概念
通过代理类,为原始类(目标)增加额外功能
好处:利于原始类(目标)的维护
1.2 名次解释
1. 目标类 原始类
	指的是 业务类(核心功能 --> 业务运算 DAO调用)
2. 目标方法,原始方法
	目标类(原始类)中的方法,就是目标方法(原始方法)
3. 额外功能(附加功能)
	日志,事务,性能等
1.4 代理开发的核心要素
代理类 = 目标类(原始类) + 额外功能 + 目标类(原始类)实现相同的接口

房东 ---> public interface UserService {
			m1
			m2
		 }
		 UserServiceImpl implements UserService {
		 	m1 ---> 业务运算 DAO调用
		 	m2
		 }
		 UserServiceProxy implements UserService {
		 	m1
		 	m2
		 }
1.4 编码

静态代理:为每一个原始类都手工编写一个代理类

image-20201108164119908

1.5 静态代理的问题
1. 静态类文件数量过多,不利于项目管理
	UserServiceImpl --> UserServiceProxy
	OrderServiceImpl --> OrderServiceProxy
2. 	额外功能维护性很差
	代理类中 额外功能修改复杂(麻烦)

第二章 Spring的动态代理开发

1. Spring动态代理概念

概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类)的维护

2. 搭建开发环境

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.3</version>
</dependency>

3. Spring 动态代理的开发步骤

  1. 创建原始对象(目标对象)

    public class UserServiceImpl implements UserService {
    
        @Override
        public void register(User user) {
            System.out.println("UserServiceImpl.register 业务运算 + DAO操作");
        }
    
        @Override
        public boolean login(String name, String password) {
            System.out.println("UserServiceImpl.login");
            return true;
        }
    }
    
    <bean id="userService" class="com.jujuxiaer.proxy.UserServiceImpl"/>
    
  2. 额外功能

    MethodBeforeAdvice接口

    额外功能写在接口的实现中,运行在原始方法执行之前 运行额外功能。
    
    public class Before implements MethodBeforeAdvice {
        /*
            作用:需要运行在原始方法执行之前 运行的额外功能,写在before()方法中
         */
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("----------- method before advice log ---------------");
        }
    }
    
    <bean id="before" class="com.jujuxiaer.proxy.dynamic.Before"/>
    
  3. 定义切入点

    切入点:额外功能加入的位置
    
    目的:由程序员根据自己的需要,决定将额外功能加入给哪个原始方法
    
    简单测试:所有方法都作为切入点,都加入额外功能
    
    <aop:config>
        <!--为所有的方法,都作为切入点,加入额外功能-->
        <aop:pointcut id="before" expression="execution(* *(..))"/>
    </aop:config>
    
  4. 组装(2 3 整合)

    <aop:config>
        <!--为所有的方法,都作为切入点,加入额外功能-->
        <aop:pointcut id="before" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="before" pointcut-ref="before"/>
    </aop:config>
    
  5. 调用

    目的:获得Spring工厂创建的动态代理对象,并进行调用
    ApplicationContext ctx = new ClassPathApplicationXmlContext("/applicationContext.xml");
    注意:
    	1. Spring的工厂通过原始对象的id值获取的是代理对象, ctx.getBean("userService");
    	2. 获取代理对象后可以通过声明接口类型,进行对象的存储
            UserService userService = (UserService) ctx.getBean("userService");
    
    测试:
        userService.login();
    	userService.register();
    		
    

4. 动态代理细节分析

  1. Spring创建的代理类在哪里?

    Spring框架再运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。
    
    什么叫做动态字节码技术?
    通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。
    
    结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理类文件数量过多,影响项目管理的问题。
    

    image-20201109223155735

  2. 动态代理编程简化代理的开发

    在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。
    
  3. 动态代理的维护性大大增强

第三章 Spring动态代理详解

1. 额外功能的详解

  • MethodBeforeAdvice分析

    1. MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作。
    
    public class Before implements MethodBeforeAdvice {
        /*
            作用:需要运行在原始方法执行之前 运行的额外功能,写在before()方法中
    
           Method: 额外功能所增加给的那个方法
                    login(String name, String password)方法
                    register(User user)方法
                    showOrder()方法
    
            Object[]: 额外功能所增加给的那个原始方法的参数。String name, String password
                                                      User
            Object target: 额外功能所增加给的那个原始对象。UserServiceImpl
                                                      OrderServiceImpl
         */
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("----------- method before advice log ---------------");
        }
    }
        
    2. before方法的3个参数在实战中个,该如何使用。
        before方法的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用。
    
  • MethodInterceptor(方法拦截器)

    MethodInterceptor接口:额外功能可以根据需要运行在原始方法 前、后、前后。
    
    public class Around implements MethodInterceptor {
        /*
            invoke方法的作用: 额外功能书写在invoke
                              额外功能 原始方法之前
                                      原始方法之前
                                      原始方法之前 之后
            确定:原始方法怎么运行
            
            参数:MethodInvocation (Method):额外功能所增加给的那个原始方法
                                            login
                                            register
                   methodInvocation.proceed()  ---> login运行
                                                    register运行
            返回值: Object: 原始方法的返回值                                                                                                        
         */
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println("----------额外功能 log------------");
            Object ret = methodInvocation.proceed();
            return ret;
        }
    }
    

    额外功能运行在原始方法之后

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = methodInvocation.proceed();
        System.out.println("----------额外功能运行在原始方法之后------------");
        return ret;
    }
    

    额外功能运行在原始方法之前,之后

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("----------额外功能运行在原始方法之前------------");
        Object ret = methodInvocation.proceed();
        System.out.println("----------额外功能运行在原始方法之后------------");
        return ret;
    }
    

    额外功能运行在原始方法抛异常的时候

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = null;
        try {
            ret = methodInvocation.proceed();
        } catch (Throwable throwable) {
            System.out.println("----------原始方法抛出异常,额外功能执行------------");
            throwable.printStackTrace();
        }
        return ret;
    }
    

    MethodInterceptor影响原始方法的返回值

    原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值
    
    若要MethodInterceptor影响原始方法的返回值,则Invoke方法的返回值,不要直接返回原始方法的运行结果即可。
    
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("----------log------------");
        Object ret = methodInvocation.proceed();
        return false;
    }
    

2. 切入点详解

切入点决定额外国功能加入位置(方法)

<aop:pointcut id="pc" expression="execution(* *(..))"/>

1. execution() 切入点函数
2. * *(..) 切入点表达式
2.1 切入点表达式

image-20201110195947225

* *(..)  匹配所有方法  

* ---> 修饰符 返回值
* ---> 方法名
() ---> 参数表
.. ---> 对于参数没有要求(参数有没有,参数有几个都行,参数是什么类型的都行)
  • 定义login方法作为切入点

    * login(..)
    
    # 定义register作为切入点
    * register(..)
    
  • 定义login方法且login方法有两个字符串类型的参数,作为切入点

    * login(String, String)
    
    # 注意: 非java.lang包中的类型,必须要写全限定名
    * register(com.jujuxiaer.proxy.User)
    
    # .. 可以和具体的参数类型连用
    * login(String, ..)  --> login(String),login(String, String),login(String, com.jujuxiaer.proxy.User)
    
  • 精准方法切入点限定

    image-20201110201143651

    image-20201110201202179

    修饰符 返回值			包.类.方法(参数)
    	*				com.jujuxiaer.proxy.UserServiceImpl.login(..)
    	*				com.jujuxiaer.proxy.UserServiceImpl.login(String, String)
    
  1. 类切入点
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所以方法,都会加上对应的额外功能
  • 语法1

    # 类中的所有方法加入了额外功能
    * com.jujuxiaer.proxy.UserServiceImpl.*(..))
    
  • 语法2

    # 忽略包
    1. 只存在一级包 com.UserServiceImpl
    	* *.UserServiceImpl.*(..)
    	
    2. 类存在多级包 com.jujuxiaer.proxy.UserServiceImpl
    	* *..UserServiceImpl.*(..)
    
  1. 包切入点表达式

image-20201110202751717

指定包作为额外功能加入的位置,自然包中的所有类及其方法都会 加上额外的功能
  • 语法1

    # 切入点包中的所有类,必须在proxy中,不能再proxy包的子包中
    * com.jujuxiaer.proxy.*.*(..)
    
  • 语法2

    # 切入点当前包及其子包都生效
    * com.jujuxiaer.proxy..*.*(..)
    
2.2 切入点函数
切入点函数:用于执行切入点表达式
  1. execution

    最为重要的切入点函数,功能最全
    
    执行 方法切入点表达式,类切入点表达式,包切入点表达式
    
    弊端:execution执行切入点表达式,书写麻烦
    
    注意: 其他切入点函数,是简化execution书写复杂度,功能上完全一致
    
  2. args

    作用:只要勇于函数(方法)参数的匹配
    切入点:方法参数必须是2个字符串类型的参数
    execution(* *(String, String))
    
    args(String, String)
    
  3. within

    作用:主要用于进行类、包切入点表达式的匹配
    切入点:UserServiceImpl这个类
    execution(* *..UserServiceImpl.*(..))  等价于 within(*..UserServiceImpl)
    
    execution(* com.jujuxiaer.proxy..*.*(..))  等价于 within(com.jujuxiaer.proxy..*)
    
  4. @annotation

    作用:为具有特殊注解的方法加入额外功能
    
    <aop:pointcut id="pc" expression="@annotation(com.jujuxiaer.Log)"/>
    
  5. 切入点函数的逻辑运算

    值得是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求
    
    • and与操作

      案例: login 同时 参数 2个字符串
      
      1. execution(* login(String, String))
      2. execution(* login(..)) and args(String, String)
      
      注意:与操作 不能用于 同种类型的切入点函数
      
      案例: register方法 和 login方法作为切入点
      
      execution(* login(..)) and execution(* register(..))  该写法错误
      应将and 改为 or
      
    • or或操作

      案例:register方法 和 login方法作为切入点
      execution(* login(..)) or execution(* register(..))
      

第四章 AOP编程

1. AOP编程

AOP (Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建

OOP (Object Oriented Programing) 面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建

AOP (Producer Oriented Programing) 面向过程编程 C语言
以过程为基本单的的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建

切面 = 切入点 + 额外功能
AOP的概念:
	本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能
	好处:利于原始类的维护
	
注意: AOP编程不可能取代OOP,OOP编程有意补充。

2. AOP编程开发步骤

1. 原始对象
2. 额外功能
3. 切入点
4. 组装切面(额外功能 + 切入点)

3. 切面的名次解释

切面 = 切入点 + 额外功能

几何学
	面 = 点 + 相同性质

image-20201110212959119

第五章 AOP的底层实现原理

1. 核心问题

1. AOP 如何创建动态代理类(动态字节码技术)
2. Spring工厂如何加工创建代理对象
	通过原始对象的id值,获得的是代理对象

2. 动态代理类的创建

2.1 JDK 的动态代理
  • Proxy.newInstance()方法参数详解

image-20201110220348202

image-20201110220435819

  • 编码

    public class TestJdkProxy {
        public static void main(String[] args) {
            // 1. 创建原始对象
            UserService userService = new UserServiceImpl();
    
            // 借用类加载器,可以是TestJdkProxy,也可以是UserServiceImpl
            UserService proxy = (UserService) Proxy.newProxyInstance(TestJdkProxy.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("----------- proxy log ---------------");
                    Object ret = method.invoke(userService, args);
                    return ret;
                }
            });
    
            proxy.login("jujuxiaer","123456");
            proxy.register(new User("juzhihua", "123456"));
        }
    }
    
2.2 CGlib的动态代理
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者的方法一致,同时在代理类中提供新的实现(额外功能 + 原始方法)。

image-20201110221835189

  • 编码

    public class TestCglibProxy {
    
        public static void main(String[] args) {
            // 1. 创建原始对象
            UserService userService = new UserService();
    
            /*
                2. 通过cglib方式创建动态代理对象
                    Proxy.newInstance(classloader, interfaces, invocationHandler)
    
                    Enhancer.setClassLoader()
                    Enhancer.setSuperClass()
                    Enhancer.setCallback()  --> MethodInterceptor(cglib)
                    Enhancer.create() --> 代理
             */
    
            Enhancer enhancer = new Enhancer();
            enhancer.setClassLoader(TestCglibProxy.class.getClassLoader());
            enhancer.setSuperclass(UserService.class);
            MethodInterceptor interceptor = new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    System.out.println("------------ cglib proxy log ------------");
                    Object ret = method.invoke(userService, args);
                    return ret;
                }
            };
            enhancer.setCallback(interceptor);
    
            UserService proxy = (UserService) enhancer.create();
    
            proxy.login("jujuxiaer","123456");
            proxy.register(new User("juzhihua", "123456"));
        }
    }
    
  • 总结

    1. JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
    2. Cglib动态代理 Enhancer  				通过继承父类创建的代理类
    

3. Spring工厂如何加工原始对象

  • 思路分析

    image-20201111235850737

  • 编码

    public class ProxyBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("------- new log ----------");
                    Object ret = method.invoke(bean, args);
                    return ret;
                }
            };
            return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
        }
    }
    
    <bean id="userService" class="com.jujuxiaer.factory.UserServiceImpl"/>
    
    <!--
       1. 实现BeanPostProcessor 进行加工
       2. 配置文件中对BeanPostProcessor 进行配置
    -->
    <bean id="proxyBeanPostProcessor" class="com.jujuxiaer.factory.ProxyBeanPostProcessor"/>
    

第六章 基于注解的AOP编程

1. 基于注解的AOP编程的开发步骤

  1. 原始对象

  2. 额外功能

  3. 切入点

  4. 组装功能

    # 通过切面类,定义了 额外功能 @Around
        		定义了 切入点	@Around(execution(* login(..)))
        		@Aspect 切面类
    
    package com.jujuxiaer.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    /**
     * 切面类
     * @author Jujuxiaer
     * @date 2020-11-12 00:25
     * 1. 额外功能
     *          public class MyAround implements MethodInterceptor {
     *              @Override
     *              public Object invoke(MethodInvocation invocation) {
     *                  Object ret = invocation.proceed();
     *                  return ret;
     *              }
     *          }
     *
     * 2. 切入点
     *          <aop:config
     *              <aop:pointcut id="" expression="execution(* lgoin(..))"
     */
    @Aspect
    public class MyAspect {
    
        @Around("execution(* login(..))")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("----------- aspect log ------------");
            Object ret = joinPoint.proceed();
            return ret;
        }
    }
    
    
    <bean id="userService" class="com.jujuxiaer.aspect.UserServiceImpl"/>
    
    <!--
            切面
            1. 额外功能
            2. 切入点
            3. 组装切面
        -->
    <bean id="around" class="com.jujuxiaer.aspect.MyAspect"/>
    
    <!--告知Spring基于注解进行AOP编程-->
    <aop:aspectj-autoproxy/>
    

2. 细节

  1. 切入点复用

    # 切入点复用:在切面类中定义一个函数,上面加上@Pointcut注解。通过这种方式,定义切入点表达式,后续更加有利于切入点复用。
    @Aspect
    public class MyAspect {
    
        @Pointcut("execution(* login(..))")
        public void myPointcut(){
    
        }
    
        @Around(value = "myPointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("----------- aspect log ------------");
            Object ret = joinPoint.proceed();
            return ret;
        }
    
        @Around(value = "myPointcut()")
        public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("----------- aspect tx ------------");
            Object ret = joinPoint.proceed();
            return ret;
        }
    }
    
  2. 动态代理的创建方式

    AOP底层实现 2中代理创建方法
    1. JDK 通过实现接口,做新的实现类方式 创建代理对象
    2. Cglib通过继承父类 做新的子类      创建代理对象
    
    默认情况下,AOP编程底层应用JDK动态代理创建方式
    如果想要切换成Cglib
    	1. 基于注解AOP开发
    		<aop:aspectj-autoproxy proxy-target-class="true"/>
        2. 传统的AOP开发
        	<aop:config proxy-target-class="true">
        	</aop>
    

第七章 AOP开发中的坑

坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部方法通过普通的方式调用,都是调用原始方法)。如果想让内层的方法也调用代理对象的方法,就要通过实现ApplicationContextAware获取Spring工厂,进而获得代理对象,通过代理对象调用对象的方法,才能加上额外功能。

public class UserServiceImpl implements UserService, ApplicationContextAware {

    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register");

        /*
            现在this.login("jujuxiaer", "123456");并没有加上额外功能,原因是通过原始对象调用的login方法,而不是代理对象
            想要获取代理对象去调用login方法,那么就可以加上额外功能。
            测试类中获取代理对象是如下方式:
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.login();

            但是ctx(Spring工厂)是重量级资源,一个应用中 只应该 创建一个工厂
         */
        // this.login("jujuxiaer", "123456");
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("jujuxiaer", "123456");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}

第八章 AOP阶段只是总结

image-20201112190037483

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值