0


Spring 的代理开发设计

一、静态代理设计模式

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

在 JavaEE 分层开发中,哪个层次对于我们来说是最重要的?

Java 的开发思路: DAO ---> Service ---> Controller

**其中最重要的是 Service **,因为做任何一个项目,写任何一段代码,最主要的目的都是为了满足用户的需求

Service 中包含了哪些代码?

Service 中 = 核心功能(几十行 上百行代码) + 额外功能(附加功能)

1、核心功能

    业务运算

    DAO 调用

2、额外功能

    (1)不属于业务

    (2)可有可无

    (3)代码量小

事务、日志、性能....

额外功能 书写在 Service 中到底好不好呢?

Service 层调用者的角度(Controller):需要在 Service 中书写额外功能

软件设计者的角度:Service 中不需要额外功能(会造成代码不好维护)

现实生活中的解决方式:

我们把房东当成一个类(业务类 Service),房东提供了出租房屋的业务方法, 出租房屋的核心功能就是签合同和收钱,但是出租房屋光有核心功能是不够的,还得有一些额外功能:广告,看房。(类似于事务,日志....)

站在房东的角度来讲,它不愿意做这些额外功能了,但是房客不允许房东不做这些额外功能,要想解决这个问题,就得引入一个新的角色:中介(代理类)

中介就代替房东提供了这些额外功能:广告和看房,在这些方法中,首要的职责就是把房东曾经不干的额外功能由中介来干

但是最核心的功能,还是由房东自身来做的,这个时候对于房客来讲,既能享受到房东提供的核心功能,又能享受到中介提供的额外功能,诉求就满足了

如果有朝一日对额外功能不满意了,不需要修改原来的代码,可以直接换一个新的中介公司,让它提供一个新的额外方法,代码的维护性也就大大提高了


2、代理设计模式

(1)概念

通过代理类,为原始类增加额外的功能

好处:利于原始类的维护


(2)名词解释

1、目标类 / 原始类:也就是房东,指的是业务类(核心功能 --> 业务运算 --> DAO 的调用)

2、目标方法 / 原始方法:目标类(原始类)中的方法,就是目标方法(原始方法)

3、额外功能(附加功能):以日志,事务,性能...为代表


(3)代理开发的核心要素

代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口

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

(4)编码

//代理类的开发
public class UserServiceProxy implements UserService{
    //获得原始对象
    private UserServiceImpl userService = new UserServiceImpl();

    @Override
    public void register(User user) {
        System.out.println("------log--------");
        userService.register(user);
    }

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


(5)静态代理存在的问题

1、静态代理文件数量过多,会不利于项目管理

有一个 UserServiceImpl 的原始类,就要提供一个 UserServiceproxy 的代理类,与之对应的,类的数量就会成倍的增长

2、额外功能维护性差

当我们想换一个日志的实现方式的时候,很多代码都得跟着修改,所以代码的维护性非常差


二、Spring 的动态代理开发

1、Spring 动态代理的概念

概念:通过代理类,为原始类(目标类)增加额外功能

优点:利于原始类(目标类)的维护

2、搭建开发环境

引入 Spring 动态代理相关的 jar 包

<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="proxy.OrderServiceImpl"></bean>

(2)提供额外功能

MethodBeforeAdvice 是一个接口

我们需要把额外功能写在接口的实现中,额外功能会在原始方法执行之前运行额外功能

public class Before implements MethodBeforeAdvice {

    //作用:需要把运行在原始方法运行之前运行的额外功能,书写在 beofre 方法中

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("------method before advice-------");
    }
}
<bean id="before" class="dynamic.Before"></bean>

(3)定义切入点

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

Spring 引入切入点的目的:由程序员根据自己的需要,来决定额外功能加入给哪个原始方法

简单的测试:所有方法都作为切入点,都加入额外的功能

通过 Spring 的配置文件完成

expression :切入点表达式,要根据自己的需求来写

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

(4)组装

把 第二步 和 第三步 进行整合

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

<!--        组装:把切入点和额外功能进行整合 -->
        <aop:advisor advice-ref="before" pointcut-ref = "pc"/>
    </aop:config>

表达的含义:所有的方法都加入 before 的额外功能


(5)调用

目的:获得 Spring 工厂创建的动态代理对象,并进行调用

ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");

//注意:
//    1、Spring 的工厂,通过原始对象的 id 值获得的是代理对象
//    2、获得代理对象之后,可以通过声明接口类型,进行对象的存储
    
UserService userService = (UserService) ctx.getBean("userService");

userService.login();
userService.registere();

注意:
1、Spring 的工厂,通过原始对象的 id 值获得的是代理对象
2、获得代理对象之后,可以通过声明接口类型,进行对象的存储


4、动态代理细节分析

(1)Spring 创建的动态代理类在哪里?

动态代理类是 Spring 框架在运行时,通过动态字节码技术在虚拟机中创建的,运行在虚拟机内部,等程序结束后,会和虚拟机一起消失

动态字节码技术:通过第三方动态字节码框架在虚拟机中创建对应的类的字节码,进而创建对象,当虚拟机关闭,动态字节码也跟着消失

结论:动态代理,不需要定义类文件,都是 JVM 运行过程当中,动态创建的,所以不会造成静态代理中类文件数量过多影响项目管理的问题

(2)动态代理编程会简化代理的开发

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

(3)动态代理的维护性大大增强了

对额外功能不满意的情况下,不用进行修改,直接创建一个新的额外功能即可


三、Spring 动态代理详解

1、额外功能的详解

(1)MethodbeforeAdvice

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

** 2、before 方法的 3 个参数在实战中 ,该如何使用?**

before 方法的参数在实战中,会根据需要来进行使用,不一定都会用到,也有可能都不用

 Servlet{
 service(HttpRequest request,HttpResponse response){
     request.getParameter("name") -->
 
     response.getWriter() --->
 
     }  
 }

(2)MethodInterceptor(方法拦截器)

和 MethodBeforeAdvice 的区别:

MethodBeforeAdvice 只能运行在原始方法执行之前,相对来讲,功能比较单一

MethodInterceptor,可以运行在原始方法执行之前,也可以运行在原始方法执行之后,灵活性更高

**注意:在这里我们选择 aopllicance 的包提供的接口 **

public class Arround implements MethodInterceptor {

    /*
     invoke 方法的作用: 额外功能书写在 invoke
     额外功能可以运行在原始方法之前,原始方法之后,原始方法的之前和之后

    要先提前确定原始方法如何运行:

    参数:MethodInvocation (类似前面提到的的Method)代表额外功能所增加给的原始方法
    invocation.proceed() 就代表对应的方法的执行

    返回值:Object:原始方法的返回值
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        System.out.println("-----额外功能 log----");
        Object ret = invocation.proceed();

        return ret;
    }
}
    <bean id="arround" class="dynamic.Arround"></bean>

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

        <!--        组装:把切入点和额外功能进行整合 -->
        <aop:advisor advice-ref="arround" pointcut-ref = "pc"/>
    </aop:config>

2、切入点详解

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

<aop:pointcut id="pc" expression="execution(* *(..))"/>
exection(* *(..)) ---> 匹配了所有⽅法

1、execution : 切入点函数

2、* *(..) 切入点表达式


(1)切入点表达式

1、方法切入点表达式
* *(..) ---> 所有方法

* ---> 修饰符 返回值
* ---> ⽅法名
()---> 参数表
..---> 对于参数没有要求 (参数有没有,参数有⼏个都⾏,参数是什么类型的都⾏)

定义 login 方法作为切入点

* login(..)

定义 login 方法,且 login 方法有两个字符串类型的参数,作为切入点

* login(String,String)

注意:如果参数的类型不是 java.lang 包当中的,那必须写类型的权限定名

* regidter(proxy.User)

2、类切入点

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

    <aop:config>
        <!--       类的方法,都作为切入点,加入额外功能 -->
        <aop:pointcut id = "pc" expression = "execution(* proxy.UserServiceImpl.*(..))"/>

        <!--        组装:把切入点和额外功能进行整合 -->
        <aop:advisor advice-ref="arround" pointcut-ref = "pc"/>
    </aop:config>

类中所有方法加入额外功能:

## 类中所有的方法都加入了额外功能
* proxy.userService.*(..)

忽略包:

## 类只存在一层包
* *.UserServiceImpl.*(..)

## 类存在多层包
* *..UserServiceImpl.*(..)

3、包切入点

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

# 切入点包中的所有类,必须在 proxy 中,不能在 proxy 包的子包中

* proxy.*.*(..)
# 切入点当前包及当前包的子包都生效

* proxy..*.*(..)

(2)切入点函数

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

1、execution

execution 是最为重要的切入点函数,功能最全

可以执行:方法切入点表达式,类切入点表达式,包切入点表达式

弊端:execution 在执行切入点表达式的时候,书写麻烦

**注意:其它的切入点函数,仅仅简化的是 execution 书写复杂度,功能上完全一致 **


2、args

作用:主要用于函数(方法)参数的匹配

切入点:方法的参数必须得是两个字符串类型的参数

execution( * *(String,String) )

args(String,String)

3、within

作用:主要用于进行类、包切入点表达式的匹配

切入点: UserServiceImpl

execution( * *..UserService.*(..) )

within( * .. UserServiceImpl )

execution(* proxy..*.*(..) )

within( proxy.. )

4、@annotation

**作用:为具有特殊注解的方法加入额外功能 **

< aop: pointcut id = "" expression = "@annotation(Log)"/>

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

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

and 与操作

案例:方法名:login 参数:String ,String

execution ( * login(String,String) )

execution ( * login(..) )  and  args( String,String )

注意:与操作,不能用于同种类型的切入点函数

例如: execution and execution

or 或操作

案例:register⽅法 和 login⽅法作为切⼊点
execution(* login(..)) or execution(* register(..))
标签: spring java 后端

本文转载自: https://blog.csdn.net/weixin_73616913/article/details/132837412
版权归原作者 馒头警告 所有, 如有侵权,请联系我们删除。

“Spring 的代理开发设计”的评论:

还没有评论