Spring 动态代理
一、静态代理
静态代理要
- 要实现相同的接口;
- 要有原始对象;
- 要有额外的功能。
如下就是一个静态代理的实例。
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();
代表原始方法运行。
返回值代表原始方法执行后的返回值。
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;
}
}
<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>
输出:
Around.invoke----原始方法之前运行.
UserServiceImpl.login
Around.invoke----原始方法之后运行.
Around.invoke----原始方法之前运行.
UserServiceImpl.register 业务运算 + DAO
Around.invoke----原始方法之后运行.
切入点表达式
* 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(..))
还没有评论,来说两句吧...