1. 静态代理设计模式
1.1 为什么需要静态代理设计模式
-
在JavaEE分层开发中,哪个层次对于我们来讲最重要?
- Service层
-
Service层中包含了哪些代码?
-
**核心功能:**业务运算、DAO调用
-
**额外功能:**事务、日志、性能…
额外功能指的是:不属于业务、可有可无、代码量很小的那部分代码
-
-
额外功能写在Service层中好不好?
- 从Service层调用者的角度(Controller):需要在Service中写额外功能
- 从软件设计者的角度:Service层中不需要额外功能
-
以卖房为案例,房东向房屋中介代理自己的房子,自己只负责签合同和收钱(核心功能),其他的带看房屋、宣传(额外功能),引入房屋中介这个代理类。

2. 代理设计模式
2.1 概念
- 通过代理类,为原始类(目标)增加额外的功能
- 好处:有利于原始类(目标)的维护
2.2 名词解释
- **目标类(原始类):**指的是业务类(核心功能—>业务运算、DAO调用)
- **目标方法(原始方法):指的是目标类(原始类)**中的方法
- **额外功能(附加功能):**日志、事务、性能
2.3 代理开发的核心要素
代理类 = 目标类(原始类) + 额外功能 + 目标类(原始类)实现相同的接口
//房东(目标类)
public interface UserService{
m1();
m2();
}
//实现目标类中的方法(业务运算、DAO调用)
public class UserServiceIMpl implements UserService{
m1();
m2();
}
//房屋中介(代理类),实现与目标类相同的接口
public class UserServiceProxy implements UserService{
m1();
m2();
}
2.4 静态代理编码
**静态代理:**为每一个原始类,手工编写一个代理类
public interface UserService {
public void register(User user);
public User login(String username, String password);
}
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl register");
}
@Override
public User login(String username, String password) {
System.out.println("UserServiceImpl login");
return null;
}
}
public class UserServiceProxy implements UserService {
private UserService userService = new UserServiceImpl();
@Override
public void register(User user) {
System.out.println("---log---");
userService.register(user);
}
@Override
public User login(String username, String password) {
System.out.println("---log---");
return userService.login(username, password);
}
}
调用者调用的就是代理类UserServiceProxy,既有核心功能,又增加了额外功能,并且原来的UserServiceImpl并没有发生变化。
2.5 静态代理存在的问题
-
每一个原始类,都需要一个代理类,导致静态代理文件数量过多,不利于项目管理。
UserServiceImpl --> UserServiceProxy OrderServiceIMpl --> OrderServiceProxy -
代理类中的额外功能修改复杂,维护性差。
2. Spring的动态代理开发
概念:通过代理类为原始类(目标类)增加额外功能。
好处:有利于原始类(目标类)的维护。
2.1 搭建开发环境
<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>
2.2 Spring动态代理的开发步骤
-
创建原始对象(目标对象)
public class UserServiceImpl implements UserService { @Override public void register(User user) { System.out.println("UserServiceImpl register"); } @Override public User login(String username, String password) { System.out.println("UserServiceImpl login"); return null; } }<bean id="userService" class="edu.hzb.proxy.SpringDramaticProxy.UserServiceImpl"/> -
实现额外功能
MethodBeforeAdvice接口public class Before implements MethodBeforeAdvice { /** 把需要在原始方法执行之前的额外功能,卸载before接口中 */ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("---method before advice log---"); } }<bean id="before" class="edu.hzb.proxy.SpringDramaticProxy.Before"/> -
定义切入点
<aop:config> <!-- 在所有的方法中都添加切入点,都加入额外功能--> <aop:pointcut id="pc" expression="execution(* *(..))"/> </aop:config>切入点:额外功能在方法中加入的位置
由程序员根据自己的需要,决定将额外功能加入给哪个原始方法
-
将
2、3整合,将额外功能切入到方法中<aop:config> <!-- 在所有的方法中都添加切入点,都加入额外功能--> <aop:pointcut id="pc" expression="execution(* *(..))"/> <aop:advisor advice-ref="before" pointcut-ref="pc"/> </aop:config> -
调用
目的:获得Spring工厂创建的动态代理对象,并且进行调用
注意:
- Spring工厂通过原始对象的id值获得的是代理对象,而不是原始对象
- 获得代理对象后,可以通过声明接口类型,进行对象的存储
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml"); edu.hzb.proxy.SpringDramaticProxy.UserService userService = (edu.hzb.proxy.SpringDramaticProxy.UserService) context.getBean("userService"); //虽然没有创建代理类,但是此时userService的运行类型确实是代理类类型:class com.sun.proxy.$Proxy12 System.out.println(userService.getClass()); userService.register(new User()); userService.login("zhangsan", "zhangsan"); }2.3 动态代理细节分析
-
Spring创建的动态代理类在哪里?
Spring框架在运行时,通过动态字节码技术,在JVM中创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。 -
什么叫动态字节码技术?
通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束后,动态字节码也会跟着消失。 -
结论:
- 动态代理不需要定义类文件,都是JVM运行过程中创建的,所以不会造成静态代理出现类文件数量过多,影响项目管理的问题
- 动态代理编程简化代理的开发。在额外功能不改变的前提下,创建其他目标类(原始类)对象时,只需要指定原始(目标)对象即可
- 动态代理额外功能的维护性大大提高,当额外功能发生变化时,不用修改代码,重新实现
MethodBeforeAdvice接口即可,遵循"关闭修改,打开扩展"。
3. Spring动态代理详解
3.1 额外功能详解
MethodBeforeAdvice分析
MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作。
public class Before implements MethodBeforeAdvice {
/**
*作用:将运行在原始方法执行之前需要运行的额外功能,书写在Before方法中
*参数:
* Method:额外功能需要添加的原始方法
* login
* register
* Object[] : 额外功能增加给的那个原始方法的参数
* (String, String)
* (User)
* Object:额外功能增加的那个原始对象
* UserServiceImpl
*
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("---new method before advice log---");
}
}
Before方法在实战中,会根据需要进行使用,不一定会用到,也可能都用不到(大概率不会用)
MethodInterceptor方法拦截器
MethodInterceptor接口作用:额外功能可以根据需要运行在原始方法执行 前、后、前后。
public class Around implements MethodInterceptor {
/**
*作用:额外功能卸载invoke方法中
*参数:MethodInvocation(等价于Method):额外功能所增加给的那个原始方法
*原始方法的执行:methodInvocation.proceed();
*返回值:原始方法的返回值
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Object ret = methodInvocation.proceed();
return ret;
}
}
-
额外功能运行在原始方法执行之前
public class Around implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("额外功能执行在原始方法之前"); //这一行是执行原始方法,不写就不会执行原始方法 Object ret = methodInvocation.proceed(); return ret; } } -
额外功能运行在原始方法执行之后
public class Around implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { //这一行是执行原始方法,不写就不会执行原始方法 Object ret = methodInvocation.proceed(); System.out.println("额外功能执行在原始方法之后"); return ret; } } -
额外功能运行在原始方法执行前、后
public class Around implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("额外功能执行在原始方法之前"); //这一行是执行原始方法,不写就不会执行原始方法 Object ret = methodInvocation.proceed(); System.out.println("额外功能执行在原始方法之后"); return ret; } } -
额外功能运行在原始方法抛出异常的时候
public class Around implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object ret = null; try { ret = methodInvocation.proceed(); } catch (Throwable e) { System.out.println("额外功能运行在原始方法抛出异常的时候"); e.printStackTrace(); } return ret; } } -
MethodInterceptor接口的invoke可以影响原始方法的返回值public class Around implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object ret = methodInvocation.proceed(); //return ret;原始方法的返回结果是ret,但是我们可以认为影响方法的返回值,让它不返回ret return false; } }
3.2 切入点详解
切入点决定了额外功能加入的位置(主要是方法中)
<!-- 定义一个切入点pc,execution(* *(..))表示匹配所有的方法-->
<aop:pointcut id="pc" expression="execution(* *(..))"/>
exexution():切入点函数* *(..):切入点表达式
3.2.1 切入点表达式
-
方法切入点
**方法切入点:**指定方法加上额外功能
语法:
方法定义和切入点表达式定义类似: public void add(int i, int j); * *(..) * *(..) --> 表示所有方法 * --> 匹配方法的修饰符和返回值,比如(public void) * --> 方法名,比如add () --> 参数列表 .. --> 匹配所有参数,对参数没有要求(是否有参数,参数个数,参数类型都没有要求)<!-- 定义login方法为切入点--> <aop:pointcut id="pc" expression="exexution(* login(..))"/> <!-- 定义register方法为切入点--> <aop:pointcut id="pc" expression="exexution(* register(..))"/> <!-- 定义login方法并且login方法有两个字符串类型的参数作为切入点--> <aop:pointcut id="pc" expression="execution(* login(String, String))"/> <!-- 定义非java.lang包下面的类型为方法参数,必须要写全限定类型--> <aop:pointcut id="pc" expression="execution(* register(com.hzb.proxy.User))"/> <!-- 精准定义某个方法作为切入点--> <aop:pointcut id="pc" expression="exexution(* com.edu.hzb.proxy.UserServiceImpl.login(String, String))"/>总结:
- 非java.lang包下面的类型为方法参数,必须要写全限定类型
- 精准定义某个方法,需要写到
包名.类名.方法名
-
类切入点
**类切入点:**指定特定类作为切入点(额外功能加入的位置),这个类中的所有方法,都会加上对应的额外功能
语法:
<!-- 类中的所有方法都加入额外功能--> <aop:pointcut id="pc" expression="executiion(* com.edu.hzb.proxy.UserServiceImpl.*(..))"/><!-- 忽略包--> <!-- 类只存在一级包,例如com.UserServiceImpl,只有com是包名--> <aop:pointcut id="pc" expression="execution(* *.UserServiceImpl.*(..))"/> <!-- 类存在多级包,例如com.edu.hzb.proxy.UserServiceImpl,com.edu.hzb.proxy是包名--> <aop:pointcut id="pc" expression="exexution(* *..UserServiceImpl.*(..))"/> -
包切入点(实战较多)
**包切入点:**指定包作为额外功能加入的位置,包中的所有类及其方法都会加入额外的功能
语法:
<!-- 切入点是包中的所有类,必须在proxy中,不能在proxy包的子包中--> <aop:pointcut id="pc" expression="execution(* com.edu.hzb.proxy.*.*(..))"/> <!-- 切入点当前包及其子包都生效--> <aop:pointcut id="pc" expression="execution(* com.edu.hzb.proxy..*.*(..))"/>
3.2.2切入点函数
切入点函数类型
切入点函数:用于执行切入点表达式,包含以下几种:
-
executionexecution:最为重要的切入点函数,功能最全,可以执行方法切入点表达式、类切入点表达式、包切入点表达式。弊端:
execution执行切入点表达式,书写麻烦**注意:**其他的切入点函数,是为了简化
execution的书写复杂度,功能上完全一致 -
argsargs:主要用于方法参数的匹配<!-- 方法参数必须是两个字符串类型的参数--> <!--使用execution函数--> <aop:pointcut id="pc" expression="execution(* *(String,String))"/> <!--使用args函数--> <aop:pointcut id="pc" expression="args(String,String)"/> -
withinwithin:主要用于进行类、包切入点表达式匹配<!--切入点为UserServiceImpl这个类--> <!-- 使用execution函数--> <aop:pointcut id="pc" expression="execution(* *..UserServiceImpl.*)"/> <!--使用within函数--> <aop:pointcut id="pc" expression="with(*..UserServiceImpl)"<!-- 切入点为com.edu.hzb.proxy包--> <!--使用execution函数--> <aop:pointcut id="pc" expression="execution(* com.edu.hzb.proxy..*.*(..))"/> <!--使用within函数--> <aop:pointcut id="pc" expression="within(com.edu.hzb.proxy..*)"/> -
@annotation@annotation:为具有指定注解的方法加上额外功能//定义一个注解Log @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { }使用了
Log注解的方法加上额外功能<aop:pointcut id="pc" expression="@annotation(edu.hzb.proxy.Log)"/>
切入点函数的逻辑运算
切入点的逻辑运算指的是整合多个切入点函数一起配合工作,进而完成更为复杂的需求。
-
and与操作<!-- 切入点为login方法,并且参数为两个字符串类型--> <!-- 只使用execution函数--> <aop:pointcut id="pc" expression="execution(* login(String,String))"/> <!-- execution和args联合使用--> <aop:pointcut id="pc" expression="execution(* login(..)) and args(String,String)"/>**注意:**与操作不能用于同种类型的切入点函数
<!-- 不存在名称同时为register和login的方法,这种写法是错误的--> <aop:pointcut id="pc" expression="execution(* login(..)) and execution(* register(..))"/> -
or或操作<!-- 切入点是register方法和login方法--> <aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>注意:或操作是可以用于不同类型的切入点函数
学习哔哩哔哩《孙哥讲Spring5》视频所做的笔记
1207






