一、静态代理
静态代理要
- 要实现相同的接口;
- 要有原始对象;
- 要有额外的功能。
如下就是一个静态代理的实例。
代码语言:javascript复制public class UserServiceProxy implements UserService {
private UserServiceImpl userService = new UserServiceImpl();
@Override
public void addUser() {
userService.addUser();
System.out.println("UserServiceProxy.addUser");
}
@Override
public void deleteUser() {
userService.deleteUser();
System.out.println("UserServiceProxy.deleteUser");
}
}
静态代理最大的特点就是我们有一个原始类就要有一个代理类,静态的意思是代理类需要手工写出来一个源文件。
静态代理存在的问题:
- 类文件数量过多,不利于项目管理;
- 额外功能可维护性差,代理类中额外功能修改起来麻烦;
二、动态代理
Spring 动态代理
- 创建原始对象(目标对象); public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("UserServiceImpl.register 业务运算 DAO"); } @Override public boolean login() { System.out.println("UserServiceImpl.login"); return true; } } 将其添加到容器: <bean class="edu.lsu.service.impl.UserServiceImpl" id="userService"/>
- 提供额外功能;
Spring 提供了一个接口
MethodBeforeAdvice
,额外的功能书写在接口的实现中,会在原始的方法运行之前运行。 public class Before implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("Before.before---MethodBeforeAdvice"); } } <bean class="edu.lsu.dynamic.Before" id="before"/> - 定义切入点:额外功能加入的位置 目的:由程序员根据自己的需要,决定额外功能加入的位置。 <aop:config> <aop:pointcut id="pc" expression="execution(* *(..))"/> </aop:config>
- 组装 把切入点和额外的功能进行整合; <aop:config> <aop:pointcut id="pc" expression="execution(* *(..))"/> <!-- 所有的方法 都加入 before 的额外功能 --> <aop:advisor advice-ref="before" pointcut-ref="pc"/> </aop:config>
- 调用
目的:获得
Spring
工厂创建的动态代理对象,并进行调用;- Spring 的工厂通过原始对象的 id 值获得的是
代理对象
; - 可以使用接口类型存储代理对象。
- Spring 的工厂通过原始对象的 id 值获得的是
三、细节分析
Spring 创建的动态代理类在哪里?
Spring 框架在运行时,通过 动态字节码技术 ,在 JVM
创建时运行在 JVM
内部,等程序结束后会和 JVM
一起消失。
动态代理不需要定义类文件,都是 JVM 运行过程中动态创建的,所以不会造成 静态代理类文件数量过多影响项目管理 的问题。
动态代理的可维护性大大增强。
四、Spring 动态代理详解
MethodBeforeAdvice
我们通过实现 MethodBeforeAdvice
接口实现额外功能。
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("Before.before---MethodBeforeAdvice");
}
该接口有一个方法 before,他有 3 个参数:
- method:额外功能所增加给的那个原始方法;
- objects:原始方法的参数;
- o:代表额外功能所增加给的那个原始对象。
MethodInterceptor
它也叫作方法拦截器
这里使用的是 org.aopalliance.intercept.MethodInterceptor
, cjlib
包中也提供一个,但是我们不用那个。
该接口有一个 invoke
方法,重写该方法之后就能让额外功能执行在原始方法之前或者之后,或者之前和之后。
public class Around implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return null;
}
}
参数 MethodInvocation
:额外功能所增加给的那个原始方法,methodInvocation.proceed();
代表原始方法运行。
返回值代表原始方法执行后的返回值。
代码语言:javascript复制public class Around implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("Around.invoke----原始方法之前运行.");
Object res = methodInvocation.proceed();
System.out.println("Around.invoke----原始方法之后运行.");
return res;
}
}
代码语言:javascript复制<bean class="edu.lsu.service.impl.UserServiceImpl" id="userService"/>
<bean class="edu.lsu.dynamic.Around" id="around"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!-- 所有的方法 都加入 around 的额外功能 -->
<aop:advisor advice-ref="around" pointcut-ref="pc"/>
</aop:config>
输出:
代码语言:javascript复制Around.invoke----原始方法之前运行.
UserServiceImpl.login
Around.invoke----原始方法之后运行.
Around.invoke----原始方法之前运行.
UserServiceImpl.register 业务运算 DAO
Around.invoke----原始方法之后运行.
切入点表达式
代码语言:javascript复制* edu.lsu.service.*.*(..)
- 访问修饰符可以省略;
- 返回值可以使用通配符
*
表示任意返回值; - 包名可以是任意包,但是有几个包就写几个
*.
; *..
表示当前包及其子包。- 参数类型可以使用通配符表示任意类型,可以使用
..
表示有无参数都行。
切入点函数
用于执行切入点表达式。
-
execution
它是最重要的切入点函数,功能最全,但是写法复杂。 -
args
用于函数(方法)参数的匹配 execution(* *(String, String)) == args(String, String) -
within
用于进行类、包切入点表达式的匹配 execution(* *..UserServiceImpl.*(..)) == within(*..UserServiceImpl) execution(* top.wsuo.proxy..*.*(..)) == within(top.wsuo.proxy..*) -
@annotation
为具有特殊注解的方法加入额外功能。 <aop:pointcut id="pc" expression="@annotation(edu.lsu.Log)"/> 将注解加到指定的方法之上就可以实现功能了。 - 切入点函数的逻辑运算
指的是整合多个切入点函数一起配合工作,进而完成更为复杂的需求。
-
and
与操作: execution(* login(String, String)) == execution(* login(..)) and args(String, String) -
or
或操作: execution(* login(..)) or execution(* login(..))
-