目录
一、静态代理设计模式
1、为什么需要代理设计模式
在 JavaEE 分层开发中,哪个层次对于我们来说是最重要的?
Service 中包含了哪些代码?
额外功能 书写在 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 创建的动态代理类在哪里?
(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 的区别:
注意:在这里我们选择 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
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(..))