Spring框架学习(五):静态代理、Spring动态代理开发与详解

AOP编程

第一章、 静态代理设计模式

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

1.1 问题

  • 在javaEE分层开发中,哪个层对我们来讲最重要
DAO --> Service --> Controller

在javaEE分层开发中,最重要的是Service层
  • Service层包含了哪些代码
Service层 = 核心功能(几十行上百行) + 额外功能(附加功能)
1. 核心功能
   业务运算
   DAO调用
2. 额外功能
   1. 不属于业务
   2. 可有可无
   3. 代码量很小
   如:事务、性能、日志...
  • 额外功能书写在Service层中好不好
Service层的调用者角度来说(Controller):需要在Service层中书写这些功能(事务)
						   设计角度:Service层中不需要这些额外功能

  • 现实生活中解决案例
    在这里插入图片描述
2.代理设计模式
1.1 概念
通过代理类,为原始类(目标类)增加新功能
好处:利于原始类的维护
1.2 名词解释
1. 目标类,原始类:指的是业务类(核心功能 --> 业务运算、DAO调用)
2. 目标类(原始类)中的方法就是目标方法(原始方法)
3. 额外功能(附加功能),如:事务、日志、性能监控...
1.3 代理开发的核心要素
代理类 = 原始类(目标类) + 额外功能 + 与原始类实现相同接口

public intertface UserService(){
		m1
		m2
}

UserServiceImpl implements UserService{
		m1 ---> 完成核心功能(业务运算、DAO调用)
		m2
}
UserServiceproxy implements UserService{
		m1
		m2
}
1.4 编码

静态代理:为每一个原始类,需要手动编写代理类
在这里插入图片描述

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

第二章、Spring的动态代理开发

1.Spring动态代理的概念
概念:通过代理类为原始类(目标类)增加额外功能
好处:利于原始类(目标类)的维护
2.搭建开发环境

pom依赖

<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");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}
<bean id="userService" class="xxx.UserServiceImpl"/>
2. 额外功能

MethodBeforeAdvice接口

额外功能书写在接口的实现中,会运行在原始方法运行之前执行
public class Before implements MethodBeforeAdvice {
    /**
     * 把要在原始方法运行之前执行的方法,写在before中
     * @param method
     * @param objects
     * @param o
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("method before advice log");
    }
}
<bean id="before" class="com.xxc.dynamic.Before"/>
3.定义切入点
切入点:额外功能加入的位置
目的:由程序员根据需要,决定额外功能加入给哪个原始方法

简单测试:让所有方法都做切入点,都加上额外功能
<aop:config>
	<aop:pointcut id="pc" expression="exection(* *(..))"/>
</aop:config>
4.组装(2、3整合)
将所有的方法都加入before的额外功能
<aop:advisor advice-ref="before" pointcut-ref="pc"/>
5.调用
获得Spring工厂创建的动态代理对象,并调用

ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml");
注意:
	1. Spring的工厂通过原始对象的id值获取的是代理对象
	2. 获得代理对象后,可以通过接口类型,进行对象存储
UserService userService = (UserService) ctx.getBean("userService");
4.动态代理细节分析
  1. Spring创建的代理类在哪?
Spring框架在运行时,通过动态字节码技术,在JVM中创建的,运行在JVM内部,等程序结束后,会和JVM一起消失

什么叫动态字节码技术?
通过第三方鼎泰字节码框架,在JVM创建对应的字节码,进而创建对象,当虚拟机结束,字节码也跟着消失
	
结论:动态代理不需要定义类文件,都是JVM运行过程中创建的,因而不会造成静态代理类文件过多,影响项目管理的问题

在这里插入图片描述

  1. 动态代理编程简化代理开发
在额外功能不改变的情况下,创建其他目标类的代理对象时,只需要指定原始对象即可
  1. 动态代理维护性大大增强

第三章、Spring动态代理详解

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

public class Before1 implements MethodBeforeAdvice {

    /**
     * 把要在原始方法运行之前执行的方法,写在before中
     * @param method 所增加给的原始方法
     * @param objects 额外功能所增加给的那个原始方法的参数
     * @param o 原始对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("----- new method before advice log -----");
    }
}

2. before方法三个参数在实战中,该如何使用
   before方法的参数在实战中,会根据需要进行使用,不一定都会用到,有时候也可能都不使用
  • MethodInterceptor(方法拦截器)
MethodBeforeAdvice ---> 原始方法执行之前
MethodInterceptor ---> 前 后 前后


public class Arround implements MethodInterceptor {
    /**
     * 作用:将额外功能书写在invoke中,额外功能可以运行在原始方法 前 后 前后
     *
     * @param invocation 额外功能所增加的那个方法
     * @return 原始方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("------- 额外功能 log ------");
        //运行原始方法
        Object ret = invocation.proceed();
        System.out.println("------- 额外功能 log ------");
        return ret;
    }
}

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

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

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

什么样的额外功能,运行在原始方法之前之后
如:事务

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

在这里插入图片描述

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

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

MethodInterceptor影响原始方法的返回值

直接将原始方法的返回值,直接作为invoke方法的返回值,那么就不会影响原始方法的返回值

MethodInterceptor影响原始方法的返回值
invoke方法的返回值,不返回原始方法的返回值即可
2.切入点详解
切入点决定额外功能加入的位置
<aop:pointcut id="pc" expression="execution(* *(..))"/>
execution(* *(..)) ---> 匹配所有方法

1. execution() ---> 切入点函数
2. * *(..)		---> 切入点表达式
2.1 切入点表达式
  1. 方法切入点表达式
* *(..)		---> 所有方法

*	--->	修饰符,返回值
*	--->	方法名
()	--->	参数表
..	--->	对于参数没有要求(类型、个数均无要求)
  • 定义login方法为切入点
* login(..)

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

# 注意:非java.lang包下的类型必须写全限定名
* register()

# ..可以和具体的类型连用
* login(String,..)
  • 精准的限定方法切入点
    在这里插入图片描述
* com.xxc.proxy.UserServiceImpl.login(String,..)
2. 类切入点
指定特定的类作为切入点,将这个类下的所有方法都加上了这写额外功能
  • 语法1
# 这个类下的所有方法都加上了额外功能
* com.xxc.proxy.UserServiceImpl.*(..)
  • 语法2
# 忽略包
1. 类中只存在一级包
* *.UserServiceImpl.*(..)
2. 类中存在多级包
* *..UserServiceImpl.*(..)
3. 包切入点
指定包作为额外功能加入的位置,包中的所有方法都会加入额外功能
  • 语法1
# 注意:切入包中的所有类,必须在xxc包中,不能再其子包中
* com.xxc.*.*(..)
  • 语法2
# 切入点为当前包及其子包
* com.xxc..*.*(..)
2.2 切入点函数

执行切入点表达式

  1. execution
最重要的切入点函数,功能最全
可以执行方法切入点表达式、类切入点表达式、包切入点表达式

弊端:execution执行切入点打表达式书写麻烦

注意:其他的切入点函数,只简化切入点函数,功能上完全一致
  1. args
作用:主要用于函数(方法)、参数的匹配

例:方法参数是两个String类型的
    execution(* *(String,String))
    args(String,String)
  1. within
作用:主要用于进行类、包切入点的匹配

例: UserServiceImpl这个类
	execution(* *..UserServiceImpl.*(..))
	within(*..UserServiceImpl)
  1. @annotation
作用:为具有特殊注解的方法加入额外功能

/**
 * @author xiangcheng
 * @version 2022/1/6 20:42
 * @since JDK8
 *
 * 标志要添加切入点的方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}


<aop:pointcut id="pc" exprossion="@annotation(com.xxc.Log)"/>
  1. 切入点函数的逻辑运算
指的是整合多个切入点函数,一起进行配合工作,进而完成更为复杂的功能
  • and 操作
例:login 并 参数为两个
execution(* login(..)) and args(String,String)
注意:与操作不能用于同种类型的切入点函数
  • or 操作
例:login或region方法
execution(* login(..)) or execution(* region(..))
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值