携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
我们知道Spring aop的底层原理就是动态代理, 而且同时使用了两种代理实现, 一种是jdk动态代理, 一种是cglib动态代理, 他们直接的区别在于:
- jdk动态代理通过对目标类的接口的实现, 返回的是一个目标类的兄弟类
- cglib动态代理是对目标类的继承, 返回的代理对象是目标类的子类
而spring的底层两种都采用了, 这是考虑到如果一个目标类没有实现任何接口, 那么他就不能使用jdk动态代理, 而只能用cglib动态代理, 又因为jdk动态代理由jdk底层实现, 效率相对更高, 所以如果目标类有接口, 就优先选择jdk动态代理.
提出aop这一理念是为了解决这样的需求: 程序中大量类需要进行事务管理, 或者日志记录时, 不应当破坏类中方法原本的业务代码, 而是采用将这些额外的功能分离出来, 这时候考虑动态代理给他们添加额外的功能就很合适. 又考虑到需要这些额外功能的类数量众多, 为每个类都实现代理的编程方式过于笨拙, 于是提出一种aop的思想, 即切面编程, 实现在代码层面上为众多的类统一提供额外功能, 但底层还是用动态代理来实现的.
我们使用jdk动态代理模拟aop:
- 先有一些目标类Dog, Tiger, 他们实现了共同的接口Animal, 他们都已经注册为组件.
public interface Animal {
public void shout();
public void eat();
}
@Component
public class Dog implements Animal {
@Override
public void shout() {
System.out.println("dog shout wangwang!");
}
@Override
public void eat() {
System.out.println("dog eat anything!");
}
}
@Component
public class Tiger implements Animal {
@Override
public void shout() {
System.out.println("tiger shout aaou!");
}
@Override
public void eat() {
System.out.println("tiger eat meat");
}
}
- 提供javaConfig, 用配置类注册组件
@Configuration
@ComponentScan("com.linlin")
public class CustomConfig {
}
- 使用BeanPostProcessor, 在注册到容器中前将所有组件替换为他们的代理类
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?>[] interfaces = bean.getClass().getInterfaces();
if (interfaces != null && interfaces.length > 0) {
// 使用jdk动态代理
Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在Animal接口的shout方法前后加东西
if (bean instanceof Animal && "shout".equals(method.getName())){
System.out.println("hello");
Object invoke = method.invoke(bean, args);
System.out.println("world");
return invoke;
}
// 不是就直接调用
Object invoke = method.invoke(bean, args);
return invoke;
}
});
return proxy;
}
// 没有接口, 就使用cglib动态代理
Object proxy = Enhancer.create(bean.getClass(), new org.springframework.cglib.proxy.InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 在Animal接口的shout方法前后加东西
if (bean instanceof Animal && "shout".equals(method.getName())){
System.out.println("hello");
Object invoke = method.invoke(bean, objects);
System.out.println("world");
return invoke;
}
// 不是就直接调用
Object invoke = method.invoke(bean, objects);
return invoke;
}
});
return proxy;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
}
- 单元测试是否替换成功, 成功的标志是调用每个类的shout方法时, 额外在方法前打印hello, 方法后打印world
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CustomConfig.class)
public class MyTest {
// 注意, 这里必须用Animal接口接收, 否则会报错,
// 因为如果是用具体的实现类接收, 使用jdk动态代理创建出来的代理类是一个Animal接口的实现, 和目标类是兄弟关系, 所以用目标类的类型接收的时候会报错!
@Autowired
Animal dog;
@Autowired
Animal tiger;
@Test
public void test1(){
dog.shout();
tiger.shout();
}
}