spring中使用了两种动态代理方式JDK动态代理和CGLib动态代理。
代理的英文称呼是Proxy,顾名思义就是一个人代表另个人,或者一个机构代表另个机构去做某个事情。动态代理的动态意思就是,在程序运行时利用反射机制动态创建而成。
1、JDK动态代理代码实现是什么样的?
我们对于动态代理的研究是基于IOC的,我们需要这四个类:目标对象接口、目标对象类、代理类、测试类,直接上代码:
Lecturer.java(目标类接口)
package com.spring.test;
public interface Lecturer {
public void ClassBegin();
}
LecturerImpl.java(目标类)
package com.spring.test;
public class LecturerImpl implements Lecturer{
public void ClassBegin() {
// TODO Auto-generated method stub
System.out.println("讲师开始上课,同学激烈的讨论!");
}
}
我们这里写了一个讲师的接口,原因是因为JDK动态代理只能代理接口,这也是JDK动态代理的一个局限,我们继续。
LecturerProxy.java(代理类)
package com.spring.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LecturerProxy implements InvocationHandler {
private LecturerImpl target;
public void setTarget(LecturerImpl target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("上课前学校要求点名!");
// 用反射执行target的method方法
method.invoke(target, args);
System.out.println("下课的时候学校要求点名!");
return null;
}
}
ApplicationContext.java
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<bean id="lecturer" class="com.spring.test.LecturerImpl">bean>
<bean id="proxy" class="com.spring.test.LecturerProxy">
<property name="target" ref="lecturer">property>
bean>
beans>
在配置文件里,我们将目标对象的bean注入到代理类的bean中,这样代理类就成了目标对象的代理类,代码继续。
SpringTest.java
import java.lang.reflect.Proxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.LecturerImpl;
import com.spring.test.LecturerProxy;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
Lecturer lecturer = (LecturerImpl) content.getBean("lecturer");
LecturerProxy proxy = (LecturerProxy) content.getBean("proxy");
Lecturer lecturerProxy = (Lecturer) Proxy.newProxyInstance(lecturer.getClass().getClassLoader(), lecturer.getClass().getInterfaces(), proxy);
lecturerProxy.ClassBegin();
}
}
在测试类中,我们分别获取了目标对象的bean和代理类的bean,然后使用Proxy类的newProxyInstance方法,将目标对象的接口给到了代理类,让代理类可以调用相应方法。
此时,代理类将增强织入到目标对象的每个方法上,调用任何方法都会进行点名。我们如果只想将增强织入都某个切点,在代理类中判断一下就可以了,代码如下:
LecturerProxy.java
package com.spring.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LecturerProxy implements InvocationHandler {
private LecturerImpl target;
public void setTarget(LecturerImpl target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
if(method.getName().equals("ClassBegin")) {
System.out.println("上课前学校要求点名!");
// 用反射执行target的method方法
method.invoke(target, args);
System.out.println("下课的时候学校要求点名!");
}
return null;
}
}
提醒一下,在所有导包过程中,主要不要导错成CGLib的包。
2、CGLib动态代理代码实现是什么样的?
CGLib动态代理,不要求目标对象一定是接口,需要三个类:目标对象类、代理类、测试类。代码如下:
Lecturer.java(目标对象类)
package com.spring.test;
public class Lecturer{
public void ClassBegin() {
// TODO Auto-generated method stub
System.out.println("讲师开始上课,同学激烈的讨论!");
}
}
LecturerProxy.java
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class LecturerProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] arg2, MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
if(method.getName().equals("ClassBegin")) {
System.out.println("上课前点名!");
methodProxy.invokeSuper(obj, arg2);
System.out.println("下课时候点名!");
}
return null;
}
}
我们来分析一下这个代理类。他实现了方法拦截器的接口MethodInterceptor。在这个代理类中,我们声明了一个增强类Enhancer对象,并封装方法getProxy给增强类设置父类为目标对象的class,设置子类为当前代理类对象,并返回一个实例。
ApplicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<bean id="lecturer" class="com.spring.test.Lecturer">bean>
<bean id="proxy" class="com.spring.test.LecturerProxy">bean>
beans>
SpringTest.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.LecturerProxy;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
Lecturer lecturer = (Lecturer) content.getBean("lecturer");
LecturerProxy proxy = (LecturerProxy) content.getBean("proxy");
Lecturer lecturerProxy = (Lecturer) proxy.getProxy(lecturer.getClass());
lecturerProxy.ClassBegin();
}
}
在测试类中,我们获取到目标对象和代理类对象,调用代理对象的getProxy方法,将目标对象的class传参并返回一个目标对象的增强,然后增强充当了目标对象的代理职责。
3、两种代理是否就是spring中的AOP了呢?
这两种代理方式是spring AOP的实现原理,但不是springAOP,这两种代理方式有以下缺陷:
目标类的所有方法都被拦截,而有时候我们只希望对目标类的某些特定方法进行横切逻辑。
使用硬编码的形式进行横切,也就是在运行过程中无法修改。
需要我们编码代理类,并使用编码为其指定目标对象,目标对象单一,无法通用。
这三个问题,在springAOP中占有重要地位,springAOP也很好的解决了这些问题,我们下面几节课会聊到。