代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,
一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
要说代理模式,离我们最近的、也是面试问到最多的当属Spring-AOP
如果使用过 @point,@befer, @after 系列的注解就可以很简单的理解,就是在调用方法时,在其前后额外做一些事情,而你只需要关注被调用点的逻辑实现,其他操作都是通过代理的方式帮你进行处理,而你则不需要关注他帮你做的那些事情
而代理模式又可以分为以下:
1. 静态代理
首先我们需要明白的是几个概念:
1. 真实对象(角色):上边提到的我们自己来实现的事情
2. 代理对象(角色):帮我们隐藏做的一些附加事情
3. 抽象对象(角色):通过接口或抽象类声明真实角色实现的业务方法。
同一个接口(抽象角色)有真实对象和代理对象两个实现类,当我们需要调用真实角色的时候,可以直接调用代理对象,
如果需要代理角色帮我们做一些事情的时候, 把真实对象通过构造函数放进代理角色就可以了,
这些代码都是我们写好的,编译后直接可以运行的,不需要jdk帮我们,这种就叫做静态代理,需要jdk帮我们做反射,则为动态代理
请查收代码:
// 同一个接口
public interface UserService {
void registerUser();
}
@Service
@Transactional
// 实现的真实对象
public class UserImpl implements UserService {
@Override
public void registerUser() {
System.out.println("数据库插入成功。。。");
}
}
// 实现的代理对象
public class UserImplProxy implements UserService {
private UserService userService;
// 通过构造函数将真实对象放进来
public UserImplProxy(UserService userService) {
this.userService = userService;
}
@Override
public void registerUser() {
System.out.println("校验用户名。。。");
userService.registerUser();
System.out.println("创建用户成功。。。");
}
@SpringBootTest
class DemoApplicationTests {
@Test
void staticProxy() {
// 测试
// 将真实对象放进代理对象中
UserImplProxy userImplProxy = new UserImplProxy(new UserImpl());
userImplProxy.registerUser();
}
}
2. jdk动态代理(利用反射机制帮我们生成一个实现代理接口的匿名类)
在静态代理的基础上,我们可以认为,jdk帮我们动态组合一个代理对象(通过Proxy.newProxyInstance()动态生成字节码文件)
就可以称为动态代理
请查收代码:
public class UserJdkProxy {
// 用来接受真实的User对象
private Class<?> userImpl;
// 同样使用构造函数将真实对象传进来
public UserJdkProxy(Class<?> userImpl) {
this.userImpl = userImpl;
}
// 让jdk帮我们动态生成一个代理实例(对象)
public Object getProxyInstance() {
return Proxy.newProxyInstance(userImpl.getClassLoader(),
userImpl.getInterfaces(), (proxy, method, args) -> {
System.out.println("校验用户名。。。");
//the object the underlying method is invoked from
Object invoke = method.invoke(userImpl.newInstance(), args);
System.out.println("创建用户成功。。。");
return invoke;
});
}
}
@SpringBootTest
class DemoApplicationTests {
@Test
void jdkProxy() {
UserJdkProxy userJdkProxy = new UserJdkProxy(UserImpl.class);
UserService user = (UserService) userJdkProxy.getProxyInstance();
// 代理在真正调用的时候才会去初始化,此点可以通过debug进行验证
user.registerUser();
}
}
3. cglib动态代理
CGLIB 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有
父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
public class UserCgLibProxy implements MethodInterceptor {
private Object target;
public Object getProxyInstance(Object target) {
this.target = target;
// Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
Enhancer enhancer = new Enhancer();
// 将被代理的对象设置成父类
enhancer.setSuperclass(this.target.getClass());
// 回调方法,设置拦截器
enhancer.setCallback(this);
// 动态创建一个代理类
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("校验用户名。。。");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("创建用户成功。。。");
return o1;
}
}
@SpringBootTest
class DemoApplicationTests {
@Test
void cglibProxy() {
UserService user = (UserService) new UserCgLibProxy().getProxyInstance(new UserImpl());
user.registerUser();
}
}
那我们在使用过程中应该如何进行选择?
总结以下:
cglib创建动态代理对象比jdk创建动态代理效率要高
cglib创建代理对象比jdk创建对象花费时间更长(因为cglib需要动态创建子类嘛)
所以对于单例对象,因为不需要频繁创建对象,使用cglib比较合适,否则 使用jdk
同时还要注意:
jdk实现动态代理需要实现类通过接口定义业务方法
cglib由于动态创建子类,对final修饰的方法无法进行代理
那么 spring 是如何进行选择的?
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 配置了使用CGLIB进行动态代理或者目标类没有接口,那么使用CGLIB的方式创建代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面的三个方法没有一个为true,那使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
//其他方法略……
}
可以看出, Spring AOP 中的代理使用逻辑:
- 如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;
- 如果目标对象没有实现接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。
AOP 的日常使用场景
- Authentication 权限
- Transactions 事务
- Error handling 错误处理
- Synchronization 同步
- logging, tracing, profiling and monitoring 记录跟踪 优化 校准
- Caching 缓存
- Context passing 内容传递
- Lazy loading 懒加载
- Debugging 调试
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池