这波 SpringFramework5.x 我先干了(AOP编程)

一、静态代理设计模式

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

  • 在JavaEE分层开发过程中,哪个层次对于我们来讲最重要

    DAO—>Service–>Controller
    JavaEE分层开发中,最为重要的是Service Service业务层

  • Service层中包含了哪些代码?

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

    Service层的调用者(controller):需要在Service层书写额外功能
                        软件设计者:Service层不需要额外功能
    
  • 现实生活中 的解决方式
    在这里插入图片描述
    2.代理设计模式
    2.1概念

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

    2.2名词解释

    	1.目标类 原始类
    	   指的是 业务类(核心功能 --->业务运算 DAO调用)
    	2.目标方法  原始方法
    		目标类(原始类)中的方法 就是目标方法(原始方法)
    	3.额外功能(附加功能)	
    		日志,事务,性能
    

    2.3代理开发的核心要素

    代理类=目标类(原始类)+额外功能+原始类(目标类)实现相同的接口
    房东---->public interface UserService{
    			m1(){}
    			m2(){}
    		}
    		UserServiceImpl implements UserService{
    			m1(){}
    			m2(){}
    		}
    

    2.4编码

    静态代理: 为每一个原始类,手工编写一个代理类(.java .class) 在这里插入图片描述

    2.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动态代理的开发步骤
①创建原始对目标对象

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======核心业务代码登录 调用DAO");
        return false;
    }
}
<bean id="userService" class="com.myspring.proxy.UserServiceImpl" />

②、额外功能
MethodBeforeAdvice接口

额外的功能书写在接口的实现中,运行在原始方法之前运行额外功能

	public class Before implements MethodBeforeAdvice {
    /*
        作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("======额外功能=====");
    }
}
<bean id="before" class="com.myspring.dynamic.Before" />

③、定义切入点

切入点:额外功能加入的位置

目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法
registe / longin
简单的测试:所有方法都作为切入点都加入额外的功能。
    <aop:config>
    	<!--定义切入的方式-->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
    </aop:config>

④、组装(②、③整合)

	表达的含义:所有方法 都加入before的额外功能
	<aop:advisor advice-ref="before" pointcut-ref="pc"/>
这是④的完整配置
   <aop:config>
    	<!--定义切入的方式-->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    </aop:config>

⑤、调用

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

4.动态代理细节分析
①、Spring创建的动态代理类在那里?

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

②、动态代理编程简化代理的开发

在额外功能不变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。

③动态代理额外功能的维护性大大增强

三、Spring动态代理详解

1.额外功能的详解

  • MethodBeforeAdvice分析
   1.MethodBeforeAdvice接口作用:额外功能运行在原始方法之前,进行额外功能操作。

   public class Before implements MethodBeforeAdvice {
   /*
       作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
       方法参数介绍:
       Method method: 额外功能所增加给的那个原始方法
       Object[] objects: 额外功能所增加给的那个原始方法的参数。
       Object o:额外功能所增加给的那个原始对象
    */
   @Override
   public void before(Method method, Object[] objects, Object o) throws Throwable {
       System.out.println("======额外功能=====");
   }
}
——————————————————————————————————————————————————————————————————————————————————————
   2.before方法中的三个参数在实战中该如何使用
   before方法中的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用。
   
  • MethodInterceptor(方法拦截器)
	methodinterceptor接口:额外功能可以根据需要运行在原始方法执行 前、后、前后。
	-------------------------------------------------------------------
	public class Around  implements MethodInterceptor {
	    /*
	        invoke方法的作用:额外功能书写在invoke
	                        额外功能可以加在  原始方法之前
	                                       原始方法之后
	                                       原始方法之前and之后
	
	        确定:原始方法怎么执行
	        methodeINvocation.proceed();
	        参数:MethodInvocation methodInvocation:额外功能所增加给的那个原始方法
	        返回值:原始方法的返回值
	     */
	
	    @Override
	    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
	        System.out.println("+++++++++++额外功能运行在之前+++++++++");
	        Object proceed = methodInvocation.proceed();
	        return proceed;
	    }
	}

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

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

额外功能运行在原始代码方法执行之前、之后。

	什么样的额外功能,运行在原始方法之前,之后都要添加?  例:事务
    @Override
	    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
	    	System.out.println("+++++++++++额外功能运行在之前+++++++++");
	        Object proceed = methodInvocation.proceed();
	        System.out.println("+++++++++++额外功能运行在之后+++++++++");
	        return proceed;
	    }

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

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object proceed = null;
        try {
            proceed = methodInvocation.proceed();
        } catch (Throwable throwable) {
            System.out.println("+++++++++++ 抛出异常时的 额外功能+++++++++");
            throwable.printStackTrace();
        }
        return proceed;
    }

MethodInterceptor影响原始方法的返回值

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

2.切入点详解

切入点决定额外功能加入位置(方法)
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution(* *(..))  --->匹配了所有方法 a b c

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

2.1切入点表达式
①、方法切入点表达式
在这里插入图片描述

* *(..) -->所有方法
*  --->修饰符 返回值
*  --->方法名
() --->参数表
.. --->对于参数没有要求(参数有没有,参数有几个都行,参数是什么类型的都行)
  • 定义login方法的切入点
    * login(..)
    #定义register作为切入点
    * register(..)
    
  • 定义long方法且login方法有两个字符串类型的参数 作为切入点
    * login(String,String)
    #注意:非Java.lang包中的类型,必须要写全限定名
    * register(com.xxx.xxx.User)
    # ..可以和具体的参数类型连用
    * login(String,..) -> login(String)/login(String,String)/login(String,com.xxx.User)
    
  • 精准方法切入点限定
    修饰符 返回值 包.类.方法(参数)
    *			 com.xxx.xxx.login()
    *			 com.xxx.xxx.login(String,String)	
    

②、类切入点

指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上额外的功能

  • 语法1
    #类中所有的方法加入了额外功能
    * com.xx.xx.UserServiceImpl.*(..)
    
  • 语法2
     #忽略包
     1.类只存在一级包 com.UserServiceImpl
     * *.UserServiceImpl.*(..)
     2.类存在多级包 com.xxx.xxx.xxx.UserServiceImpl
     * *..UserServiceImpl.*(..) 
    

③、包切入点 实战

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

  • 语法1

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

    #切入点当前包及其子包都生效
    * com.a.xxx..*.*(..)
    

2.2切入点函数

切入点函数:用于执行切入点表达式

①、execution

最为重要的切入点函数,功能最全
执行 方法切入点表达式 类切入点表达式 包切入点表达式

弊端:execution执行切入点表达式,书写麻烦
	 execution(* com.xxx.xxx..*.*(..))
注意:	   其他的切入点函数 简化execution书写复杂度,功能上完全一致

②、args

作用:主要用于函数(方法)参数的匹配
切入点:例如---方法参数必须得是2个字符串类型的参数
	execution(* *(String,String))
	args(String,String)

③、within

作用:主要用于进行类、包切入点表达式的匹配
切入点:UserServiceImpl这个类
	execution(* *..UserServiceImpl.*(..))
	within(*..UserServiceImpl)
	
	execution(* com.xx.xx..*.*(..))
	within(com.xx.xx..*)

④、@annotation

作用:为具有特殊注解的方法加入额外功能
<aop:pointcut id="pc" expression="@annotation(com.xx.Log)"/>

⑤、切入点函数的逻辑运算

指的是整合多个切入点函数一起配合工作,进而完成更为复杂的需求

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

四、AOP编程

1.AOP编程

AOP(Aspect Oriented Programming) 面向切面编程 = Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点+额外功能

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

POP(Procedure Oriented Programming) 面向过程(方法、函数)编程 c语言
以过程为单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建。

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

2.AOP编程的开发步骤

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

3.切面的名词解释

切面 = 切入点+额外功能
几何学:面=点+相同性质
在这里插入图片描述

五、AOP底层实现原理

1.核心问题

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

2.动态代理类的创建
2.1、JDK的动态代理

  • Proxy.newProxyInstance方法参数详解

在这里插入图片描述
在这里插入图片描述

  • 编码
   public class TestJDKProxy {
   public static void main(String[] args) {
       //1.创建原始对象
       UserServiceImpl userService = new UserServiceImpl();
       InvocationHandler invocationHandler = new InvocationHandler() {
           @Override
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               System.out.println("====log====");
               //原始方法运行
               Object invoke = method.invoke(userService, args);
               return invoke;
           }
       };
       //2.JDK创建动态代理
       UserService  userService1 =(UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),userService.getClass().getInterfaces(),invocationHandler);
       userService1.login("bugz","123");
       userService1.register(new User("bugz","123"));

       /*
       细节问题:
           1.借用类加载器  UserService/TestJDKProxy
           2.JDK8.x前  需要这样写 final UserServiceImpl userService = new UserServiceImpl();
        */
   }

2.2 CGlib动态代理

CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)

在这里插入图片描述

  • 编码
import com.myspring.proxy.User;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {
        //1.创建原始对象
        UserService userService = new UserService();
        /*2.通过cglib的方式创建动态代理对象
        JDK创建代理方式 Proxy.newProxyInstance(classloader,interfaces,invocationhandler)

        Enhancer.setClassLoader();
        Enhancer.setSuperClass();
        Enhancer.setCallback();---->MethodInterceptor(cglib)
        Enhancer.create()--->代理
         */
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("------cglib new log--------");
                Object invoke = method.invoke(userService, args);
                return invoke;
            }
        };
        enhancer.setCallback(methodInterceptor);
        UserService userServiceProxy=(UserService) enhancer.create();
        userServiceProxy.login("bugz","123456");
        userServiceProxy.register(new User());
    }
}

  • 总结

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

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

  • 思路分析
    在这里插入图片描述
  • 编码
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return null;
    }
    //使用jdk代理模拟工厂的代理
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            InvocationHandler invocationHandler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("---模拟代理的额外功能---");
                    System.out.println(beanName);
                    Object invoke = method.invoke(bean, args);
                    return invoke;
                }
            };

        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),invocationHandler);
    }
}

    <!--原始对象-->
    <bean id="userService"  class="com.myspring.factory.UserServiceImpl"/>
    <!--模拟代理
            1.实现BeanPostProcessor进行加工
            2.配置文件中对BeanPostProcessor进行配置
    -->
    <bean id="beanPostProcessor" class="com.myspring.factory.ProxyBeanPostProcessor"/>

六、基于注解的AOP编程

1.基于注解的AOP开发步骤
1.原始对象
2.额外功能
3.切入点
4.组装切面

	#通过切面类  定义了 额外功能@Around
				定义了切入点@Around("execution(* login(..))")
				@Aspect 切面类
	/*
    1.额外功能
     public class MyAround implements MethodInterceptor{
                    public Object invoke(MethodInvocation invocation){
                       Object ret = invocation.proceed();
                       return ret;
                    }
              }
    2.切入点
        <aop:config
            <aop:pointcut id="" expression="execution(* login(..))"/>
 */
@Aspect
public class MyAspect {
    @Around("execution(* login(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---切面类额外功能");
        Object proceed = joinPoint.proceed();
        return proceed;
    }
}
<!--原始对象-->
    <bean id="userService"  class="com.myspring.aspectproxy.UserServiceImpl"/>
    <!--
        切面
            1.额外功能
            2.切入点
            3.组装切面
    -->
    <bean id="arround" class="com.myspring.aspectproxy.MyAspect"/>
    <!--告知Spring基于注解进行AOP编程-->
    <aop:aspectj-autoproxy/>

2.切入点复用
(1)切入点复用

#切入点复用:在切面类中定义一个函数 上面加@Pointcut注解 通过这种方式,定义切入点表达式,后续更加有利于切入点复用。
@Aspect
public class MyAspect {	
    @Pointcut("execution(* login(..))")
    public void  pointcut(){

    }
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---切面类额外功能111");
        Object proceed = joinPoint.proceed();
        return proceed;
    }
    @Around(value = "pointcut()")
    public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---切面类额外功能222");
        Object proceed = joinPoint.proceed();
        return proceed;
    }
}

(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:config>

七、AOP开发中的一个坑

坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,就要ApplicationContextAware获得工厂,进而获得代理对象。
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");
               /*
            调用的是原始方法的login方法--->核心功能
            设计的目的:代理对象的log方法 ——————>额外功能+核心功能
             ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("/applicationContext2.xml");
             UserService userService =(UserService) classPathXmlApplicationContext.getBean("userService");
             userService.login();

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

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

八、AOP阶段知识总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值