Spring-代理设计模式
将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。
一、基础思想
DAO层仅提供核心方法,在代理类中进行辅助功能,实现时因为代理继承了DAO,则为其子类,可继承父类的核心方法。实现时则用的代理类中添加辅助功能后的delete,编译看左边,执行看右边。
1.1 静态代理设计模式
通过代理类的对象,为原始类的对象(目标的对象),添加辅助功能,更容易更换代理实现类,利于维护。
存在问题:
- 代理类数量过多,不利于项目的管理
- 多个代理类的辅助代码冗余,修改时,维护性差。
二、动态代理模式
动态创建代理类的对象,为原始类的对象添加辅助功能。
动态代理能够在不改变原有业务逻辑的情况下实现对原有业务的增强。
2.1 JDK动态代理
JDK动态代理采用字节重组,重新生成新对象来代替原始对象,以达到动态代理的目的。
- 获取被代理对象的引用,获取它的所有接口,通过反射获得。
- JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口
- 动态生成java代码,新加的业务逻辑方法有一定的逻辑代码调用
- 编译新生成的Java代码.class文件
- 重新加载到JVM运行
2.1.1 创建文件夹proxy,新建JDKDynamicProxy类继承
InvocationHandler。
public class JDKDynamicProxy implements InvocationHandler {
//创建被代理对象
private Object obj;
public JDKDynamicProxy(Object obj){
this.obj = obj;
}
//产生代理对象
public Object getProxy(){
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] interfaces = obj.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader,interfaces,this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---before");
Object invoke = method.invoke(obj, args);
System.out.println("---after");
return invoke;
}
}
2.1.2 使用JDK动态代理类
IDAO proxyObj = (IDAO) new JDKDynamicProxy(new UserDAO()).getProxy();
proxyObj.delete();//this=="JDKDynamicProxy"
2.2 CGLib动态代理
CGLib动态代理是通过产生被代理类的子类产生代理对象的,因此CGLib不能为final修饰的类产生代理对象。
动态集成目标对象实现动态代理,CGLib代理执行代理方法效率比JDK高,因为CGLib采用了FastClass机制:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个index(int类型);这个index当作一个入参,FastClass就可以直接定位要调用的方法并进行调用,省去了反射调用 ,所以调用效率比JDK代理反射调用高,FastClass并不是跟代理类一起生成的,而是在第一次执行MethodProxy的invoke()或invokeSuper()方法时产生并放在缓存中的。
2.2.1 创建CGLib动态代理
public class CGLibDynamicProxy implements MethodInterceptor {
private Object obj;
public CGLibDynamicProxy(Object obj){
this.obj = obj;
}
public Object getProxy(){
Enhancer en = new Enhancer();
en.setSuperclass(obj.getClass());
en.setCallback(this);
return en.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("===begin");
Object invoke = method.invoke(obj, objects);
System.out.println("===commit");
return invoke;
}
}
2.2.2 引入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
三、CGLib和JDK代理对比
- JDK动态代理实现被代理对象的接口,CGLib代理继承了被代理对象
- JDK动态代码和CGLib代理在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架(字节码 操控框架)写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低。
- JDK动态代理调用方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。
四、代理模式与Spring
ProxyFactoryBean核心方法getObject()
public Object getObject() throws BeansException {
this.initializeAdvisorChain();
if (this.isSingleton()) {
return this.getSingletonInstance();
} else {
if (this.targetName == null) {
this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");
}
return this.newPrototypeInstance();
}
}
getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance()。在Spring的配置中如果不做任何配置,那么Spring代理生成的Bean都是单例对象。如果修改scope,则每次创建新原型对象。
4.1 Spring选择机制
判断Bean有没有实现接口,实现接口就采用JDK动态代理
没有实现接口,Spring选择CGLib处理
Spring可以通过配置强制使用CGLib代理,只需要在配置中加入<aop:aspectj-autoproxy roxy-target-clas=“true”>
五、代理模式优缺点
优点:
- 代理模式将代理对象与真是被调用目标对象分离。
- 在一定程度上降低了系统的耦合性。扩展性好。
- 可以起到保护目标对象的作用。
- 可以增强目标对象的功能
缺点:
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢。
- 增加了系统的复杂度。