Spring 的代理开发设计

目录

​编辑一、静态代理设计模式

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

2、代理设计模式

(1)概念

(2)名词解释

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

(4)编码

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

二、Spring 的动态代理开发

1、Spring 动态代理的概念

2、搭建开发环境

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

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

(2)提供额外功能

(3)定义切入点

(4)组装

(5)调用

4、动态代理细节分析

三、Spring 动态代理详解

1、额外功能的详解

(1)MethodbeforeAdvice

(2)MethodInterceptor(方法拦截器)

2、切入点详解

(1)切入点表达式

1、方法切入点表达式

2、类切入点

3、包切入点

(2)切入点函数

1、execution

2、args

 3、within

4、@annotation

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


一、静态代理设计模式

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(..))

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值