本篇文章讲 Spring AOP 的部分。AOP 是面向切面编程,切面编程是先要切开,那切开之后就可以找个代理,在这个切入点里做一些额外辅助性的事情。而不影响原来的核心功能。
Spring 大白话系列:AOP 详解
今天我们就来讲讲,为什么需要代理,最基础的代理(静态代理)是什么样的,它的优缺点是什么,而为了解决对应的缺点,动态代理是怎么解决的。动态代理的原理又是什么?
Sring aop 概念
Spring aop 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
为什么需要代理:
举个例子:service 有核心功能,也有额外附加功能。额外附加的,就可以直接让代理去做。这个场景不熟悉么?房屋中介,房东是Service,房东想出租房子。中介可以替他带看房,发广告啥的。房东只需要签合同再收钱就行了。不影响核心功能。
静态代理:
通过代理类,为原始类增加额外辅助的功能。好处:不打破原始类,利于对原始类的维护
核心要素:
代理类:原始类+原始类实现相同的接口+额外功能
问题:静态代理类数量过多,不利于项目管理(一个目标类都会对应一个代理类)
spring 动态代理:
四个步骤:原始类+ 额外功能 + 定义一个切入点 + 把额外功能和切入点组合起来
1 原始类:
public class BossServiceImpl implements BossService { a1 a2 }
2 额外功能:MethodBeforeAdvice 接口
public class Before implements MethodBeforeAdvice{ @Override public void before(Method method , Object[] args,Object target ) throws Throwable{ System.out.print("this is before "); } }
3 定义切入点:就是这个代理功能加在哪。举例给所有方法都加入额外的功能。
aop:config <aop:pointcut id=“point” expression=“execution(* *(…))”/> </aop:config>
4组合:所有的方法 都加入 before的额外功能
<aop:advisor advice-ref=“before” pointcut-ref=“point”/>
这四步骤,从流程来看其实还是我们下图这里要表示的意思。也没有新东西。但是从上边四个步骤里,我们并没有创建一个中介类!那spring 是怎么想的呢?
Spring说,其实创建了,只不过是在代码运行过程创建的,会在程序结束的时候和JVM 一起消失。
怎么做到的?就是运行过程生成代理类。这里边就是动态字节码技术。
再具体点:
Java 运行一个类,这个类在代码里是 .java 文件,编译后生成.class 文件,这个.class 就是字节码。
正常是JVM 运行时,会加载.class 字节码文件。
那动态字节码呢?
没有.java 文件,也没有.class 文件。而是在JVM 运行时,诶,在JVM中创建对应的字节码,进而创建对象。当虚拟机结束,动态字节码也就跟着消失。
好处:
极大的简化了开发。
还使维护性大大增强了。
Spring 动态代理详解
Spring 动态代理还有其他方式。那我们还是按照这四个步骤,拆解看每个环境还都有哪些新的方式
原始类+ 额外功能 +定义一个切入点 + 把额外功能和切入点组合起来。
一:额外功能:
MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能。例如:
public class Before1 implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(“-----new method before advice log-----”); } }
methodinterceptor接口:额外功能可以根据需要运行在原始方法执行 前、后、前后。
public class Arround implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println(“-----额外功能运行在原始方法执行 之前 log----”); Object ret = invocation.proceed(); System.out.println(“-----额外功能运行在原始方法执行 之后 log----”); return ret; } }
二:切入点表达式、函数
方法,类什么的都可以。
AOP 的概念
AOP (Aspect Oriented Programing)面向切面编程 =Spring动态代理开发,以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面=切入点+额外功能
重新理解下这个代理:有没有一点切面的感觉。
AOP 的底层原理
AOP 是一种编程思想,但是其具体实现 (也有人说是其本质):就是Spring的动态代理开发,通过代理类为原始类增加额外的功能。
那接下来我们就以Spring 的AOP 来聊下,其是怎么创建的代理类,又咱们在创建代理类之后交给工厂进行加工的呢?
核心问题
- how :AOP 怎么创建的动态代理类
- how : Spring 工厂如何加工创建代理对象
要解决这两个问题,我们还是需要先看一下具体的动态代理的创建过程。
动态代理类的创建
Spring 有两种方式创建动态代理类。
一种是JDK 的方式。一种是CGLIB 的方式。
三要素
不论是哪种方式,我们都记得有创建对象关键的三点因素。
- 原始类
- 额外功能
- 代理对象和原始对象实现相同的接口
JDK 的方式
**Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) **
按照我们的三要素:
loader的作用:借用一个类加载器,创建代理类的Class 对象,进而可以创建代理对象。(因为动态代理类没有对应的.class文件,JVM 也就不会为其分配ClassLoader,所以就借用一个)
interfaces : userService.getClass().getInterface()
InvocationHandler: 用于书写额外功能
public static void main(String[] args) {
//1 创建原始对象
UserService userService = new UserServiceImpl();
//2 JDK创建动态代理
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------proxy log --------");
//原始方法运行
Object ret = method.invoke(userService, args);
return ret;
}
};
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), userService.getClass().getInterfaces(), handler);
userServiceProxy.login("jdk proxy", "111");
userServiceProxy.register(new User());
}
注意看 这里参数是一个接口:userService.getClass().getInterfaces()
CGLIB 的方式
原理:父类继承关系创建代理对象,原始类作为父类,代理类作为子类。
//1 创建原始对象
UserService userService = new UserService();
/*
2 通过cglib方式创建动态代理对象
Proxy.newProxyInstance(classloader,interface,invocationhandler)
Enhancer.setClassLoader()
Enhancer.setSuperClass()
Enhancer.setCallback(); ---> MethodInterceptor(cglib)
Enhancer.create() ---> 代理
*/
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor interceptor = new MethodInterceptor() {
//等同于 InvocationHandler --- invoke
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---cglib log----");
Object ret = method.invoke(userService, args);
return ret;
}
};
enhancer.setCallback(interceptor);
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("cglib", "123345");
userServiceProxy.register(new User());
}
两种代理类有什么不同?为什么要有两种?
什么时候用哪个配置?
Spring使用JDK代理时,需要实现接口,而CGLIB则不需要。在Spring中,可以通过配置文件来指定使用JDK代理还是CGLIB代理。
在applicationContext.xml或spring.xml文件中,可以使用aop:aspectj-autoproxy标签来配置使用JDK代理还是CGLIB代理,其中proxy-target-class属性可以指定是否使用CGLIB代理,如果设置为true,则使用CGLIB代理,如果设置为false,则使用JDK代理。默认使用的是JDK
**为什么默认用JDK **
主要是因为JDK代理更加简单,它只能对实现了接口的类生成代理,而CGLIB代理则可以对任意类进行代理,但是它的实现比较复杂,所以Spring默认使用JDK代理。(JDK代理的性能比CGLIB代理要差很多,因为JDK代理需要反射来实现,而CGLIB代理则是直接生成子类,所以CGLIB代理的性能要比JDK代理好很多。 但是JDK 实现简单且更安全 )
小结
- JDK动态代理 Proxy.newProxyInstance() 通过接⼝创建代理的实现类
- Cglib动态代理 Enhancer 通过继承⽗类创建的代理类
Spring 工厂如何加工原始对象
第一步咱们聊的是如何创建了这个代理对象。
第二步就聊下这个代理对象如何被Spring工厂加工的?
在流程里,有这么一个口子,就是BeanPostProcessor。 在对象初始化之后,有这样一个操作 postProcessAfterInitialization
以下代码是上图的 步骤5。
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
/*
Proxy.newProxyInstance();
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("----- new Log-----");
Object ret = method.invoke(bean, args);
return ret;
}
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
}
}
总结:在流程中 通过postProcessAfterInitialization 这个方法 进行加工。
坑
方法内部A 调用B 则调用B的没有加入额外功能。
这是因为,代码里调用B的时候是用原来的类调用的。如图:
怎么解:
把原始类改为代理类调用
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.login();
但是这样太占用内存了。ctx 一个就够了。
所以这个时候直接用一个ctx就行了。改为:
public class UserServiceImpl implements UserService, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
@Log
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算 + DAO ");
UserService userService = (UserService) ctx.getBean("userService");
userService.login("suns", "123456");
}
....
源码部分:
关于Spring aop如何决定用JDK 还是CGLIB?
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
//config.isProxyTargetClass() 代表 配置中的proxy-target-class属性true/false,默认false
@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.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}