代理模式
1.基础知识
定义∶
为其他对象提供一种代理,以控制对这个对象的访问
代理对象在客户端和目标对象之间起到中介的作用
适用场景
保护目标对象
增强目标对象
优点
代理模式能将代理对象与真实被调用的目标对象分离
一定程度上降低了系统的耦合度,扩展性好
保护目标对象
增强目标对象
缺点
代理模式会造成系统设计中类的数目增加
在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
增加系统的复杂度
扩展
静态代理
动态代理
CGLib代理
Spring代理选择
当Bean有实现接口时,Spring就会用JDK的动态代理
当Bean没有实现接口时,Spring使用CGlib
可以强制使用Cglib
在spring配置中加入<aop∶aspectj-autoproxy proxy-target-class="true"/>)
2.实战
在常用的保存用户信息的接口上,统计接口耗时,分别用静态代理和动态代理实现
静态代理方式:
/**
* 用户类
* @Author LYS
* @Date 2022/1/16 15:21
* @Version 1.0
*/
@Data
public class User {
private String name;
private String sex;
}
/**
* @Author LYS
* @Date 2022/1/16 15:21
* @Version 1.0
*/
public interface UserService {
int saveUserInfo(User user) throws InterruptedException;
}
/**
* @Author LYS
* @Date 2022/1/16 15:22
* @Version 1.0
*/
public class UserServiceImpl implements UserService{
@Override
public int saveUserInfo(User user) throws InterruptedException {
System.out.println("保存成功!");
Thread.sleep(100);
return 1;
}
}
/**
* 静态代理
*
* @Author LYS
* @Date 2022/1/16 15:23
* @Version 1.0
*/
public class UserServiceStaticProxy {
private UserService userService;
long start;
public int saveUser(User user) throws InterruptedException {
userService = new UserServiceImpl();
beforeMethod();
int result = userService.saveUserInfo(user);
afterMethod();
return result;
}
public void print() {
System.out.println("打印数据:");
}
private void beforeMethod() {
System.out.println("静态代理 before code");
start = System.currentTimeMillis();
}
private void afterMethod() {
System.out.println("静态代理 after code");
long time = System.currentTimeMillis() - start;
System.out.println("调用此接口花费了【" + time + "】毫秒");
}
}
/**
* @Author LYS
* @Date 2022/1/16 15:28
* @Version 1.0
*/
public class StaticTest {
public static void main(String[] args) throws InterruptedException {
User user = new User();
UserServiceStaticProxy userServiceStaticProxy = new UserServiceStaticProxy();
userServiceStaticProxy.saveUser(user);
}
}
控制台输出:
动态代理方式:使用反射invoke调用真实方法
/**
* @Author LYS
* @Date 2022/1/16 15:41
* @Version 1.0
*/
public class UserServiceDynamicProxy implements InvocationHandler {
private Object target;
long start;
public UserServiceDynamicProxy(Object target) {
this.target = target;
}
/**
* 返回动态代理类
*
* @return
*/
public Object bind() {
Class cls = target.getClass();
return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), this);
}
/**
* 动态代理类实际执行的方法
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object argObject = args[0];
beforeMethod(argObject);
Object object = method.invoke(target, args);
afterMethod();
return object;
}
private void beforeMethod(Object obj) {
System.out.println("动态代理 before code");
start = System.currentTimeMillis();
}
private void afterMethod() {
System.out.println("动态代理 after code");
long time = System.currentTimeMillis() - start;
System.out.println("调用此接口花费了【" + time + "】毫秒");
}
}
public class DynamicTest {
public static void main(String[] args) throws InterruptedException {
User user = new User();
user.setName("小李");
UserService userServiceStaticProxy = (UserService) new UserServiceDynamicProxy(new UserServiceImpl()).bind();
userServiceStaticProxy.saveUserInfo(user);
}
}
控制台输出
3.spring源码分析
spring 动态代理实现AOP
我们在使用 Spring AOP 时,需要先配置好 ProxyFactoryBean,然后通过 ac.getBean(bean id) 来获取 ProxyFactoryBean 代理的对象。而 ProxyFactoryBean 类使用 ProxyFactoryBean.getObject() 方法获取返回的对象,即代理对象。
下面先看 ProxyFactoryBean 类中的核心方法 getObject(),源码如下:
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
在 getObject() 方法中,主要调用 getSingletonInstance() 和 newPrototypeInstance() 方法。
在 Spring 的配置中,如果不做任何设置,则 Spring 代理生成的 Bean 都是单例对象。如果修改 scope,则每次都创建一个新的原型对象。newPrototypeInstance() 里的逻辑比较复杂,教程后面会详细讲解,这里简单了解即可。
Spring 使用动态代理实现 AOP 时有两个非常重要的类,即 JdkDynamicAopProxy 类和 CglibAopProxy 类,其类图如下:
Spring 中的代理选择如下:
- 当 Bean 有实现接口时,Spring 会用 JDK 动态代理方式
- 当 Bean 没有实现接口时,Spring 会选择 CGLib 动态代理方式
Spring 可以通过配置强制使用 CGLib 动态代理,只需在 Spring 的配置文件中加入如下代码即可。
<aop:aspectj-autoproxy proxy-target-class="true"/>