Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象:
.java文件到jjvm的过程图:
class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的、具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列。或者是Java虚拟机规范。
由于JVM通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
由于本人知识有限就不胡给大家介绍如何利用字节码增强,实现动态生成.clas文件了,大家可以参考http://www.blogjava.net/hello-yun/archive/2014/09/28/418365.html
今天就介绍如何利用jdk和cglib生成动态代理,:
首先我们编写目标类,即要被增强的类:
package com.leige.proxy; public class UserServiceImpl implements UserService { @Override public void addUser() { // TODO Auto-generated method stub System.out.println("adddUser------"); } }
接口:
package com.leige.proxy; public interface UserService { public void addUser(); }切面类即增强代码类利用动态代理可以动态的将增强代码加入目标类中:
package com.leige.proxy; public class MyAspect { public void before(){ System.out.println("前置通知"); } public void after(){ System.out.println("后置通知"); } }
工厂类:
package com.leige.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.util.MethodInvoker; public class MyFactoryBean { /** * @return * 利用jdk实现动态代理, * jdk实现动态代理,必须要保证目标类有接口,否则无法实现动态代理 */ public Object getBean(){ //目标类,即被增强类 UserService service=new UserServiceImpl(); //切面类,即增强代码类 MyAspect aspect=new MyAspect(); //jdk动态代理实现 return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub //前置通知 aspect.before(); //执行目标类的方法: Object obj=method.invoke(service, args); //后置通知 aspect.after(); return obj; } }); } /** * @return * 通过cglib实现动态代理,不需要目标类有接口,代理之后返回的是目标类的子类,所以目标类不是final的, * */ public Object getBeanByCglib(){ //目标类,即被增强类 UserService service=new UserServiceImpl(); //切面类,即增强代码类 MyAspect aspect=new MyAspect(); Enhancer enhancer=new Enhancer(); //设置父类,即被代理类,cglib的代理对象通过子类实现的 //因为我们的切入点是方法,所以使用MethodInterceptor enhancer.setSuperclass(service.getClass()); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // TODO Auto-generated method stub //前置通知 aspect.before(); //执行目标类的方法有两种方式 //Object obj=method.invoke(service, args); /*执行的是代理对象的父类,在此代理对象是proxy,proxy的父类就是目标类即UserService * 这是cglib的工作机制,即生成目标的的子类,作为代理对象 */ Object obj=methodProxy.invokeSuper(proxy, args); //后置通知 aspect.after(); return obj; } }); //创建代理类: Object obj=enhancer.create(); return obj; } }
测试类:
package com.leige.proxy; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:com/leige/proxy/beans.xml") public class TestApp { @Autowired UserService userService; @Test public void test() { userService.addUser(); } }beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.xsd"> <context:annotation-config/> <bean id="myfactory" class="com.leige.proxy.MyFactoryBean"/> <bean id="userService" factory-bean="myfactory" factory-method="getBeanByCglib"/> </beans>
以上是未使用springaop的动态代理,下面将介绍spring实现动态代理的方法:第一种通知再切面类中配置,注意都是有接口的,这里不再写接口代码:
实现类:
package com.leige.aspect; public class StudentServiceImpl implements StudentService { public void add() { System.out.println("addd student"); } }
切面类,这种方法通知都是卸载代码中,xml中只需配置切面和切入点即可:
package com.leige.aspect; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class Myaspect implements MethodInterceptor { public Object invoke(MethodInvocation mi) throws Throwable { // TODO Auto-generated method stub System.out.println("前置通知"); Object obj=mi.proceed(); System.out.println("后置通知"); return obj; } }
xml配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <bean id="studentServiceId" class="com.leige.aspect.StudentServiceImpl"></bean> <!-- 配置切面bean --> <bean id="myAspect" class="com.leige.aspect.Myaspect"></bean> <aop:config> <!-- 切入点 expression定义切入点 advisor 定义通知 --> <aop:pointcut expression="execution(* com.leige.aspect.*ServiceImpl.*(..))" id="myPointCut"/> <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/> </aop:config> </beans>第二种方法,通知类型在xml中指定,但是代码实现也是在切面类中:实现类:
package com.leige.aspect2; public class PersonServiceImpl implements PersonService { public void addPerson() { System.out.println("person --------addperson"); } }切面类:
package com.leige.aspect2; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; /** * @author * * * * <!-- 声明通知类型 #1 前置通知 , 目标方法之前执行。 * 第一个参数为JoinPoint,可以获得目标方法名等。 <aop:before method="myBefore" pointcut-ref="myPonitCut"/> #2 后置通知,目标方法之后执行,可以获得返回值。 通过“returning”属性配置第二个参数的名称,获得返回值的,类型必须Object * 第一个参数为:JoinPoint * 第二个参数为:Object xxx <aop:after-returning method="myAfterReturning" pointcut-ref="myPonitCut" returning="xxx"/> #3 环绕通知, 目标方法前后 方法要求:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ 执行目标方法:joinPoint.proceed(); <aop:around method="myAround" pointcut-ref="myPonitCut"/> #4 抛出异常通知,目标方法出现异常时才执行。通过“throwing”属性配置第二个参数的名称,获得具体的异常信息,类型必须是Throwable * 第一个参数为:JoinPoint * 第二个参数为:Throwable e <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPonitCut" throwing="e"/> * */ public class MyAspect { public void myBefore(JoinPoint joinPoint){ System.out.println("前置通知, 方法名称:" + joinPoint.getSignature().getName()); } public void myAfterReturning(JoinPoint joinPoint,Object xxx){ System.out.println("后置通知, 返回值:" + xxx); } public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前"); //必须执行目标方法 Object obj = joinPoint.proceed(); System.out.println("后"); return obj; } public void myAfterThrowing(JoinPoint joinPoint, Throwable e){ System.out.println("抛出异常通知, " + e.getMessage()); } public void myAfter(){ System.out.println("最终"); } }
xml配置方法·:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 配置增强bean --> <bean id="personServiceId" class="com.leige.aspect2.PersonServiceImpl"></bean> <!-- 配置切面bean ,包含多个通知--> <bean id="myAspect" class="com.leige.aspect2.MyAspect"></bean> <aop:config> <!-- 切面 通知配置--> <aop:aspect ref="myAspect"> <!-- 定义切入点 ,表示这个bean下的所有方法都是切入点--> <aop:pointcut expression="execution(* com.leige.aspect2.PersonServiceImpl.*(..))" id="myPointCut"/> <!-- 前置通知 <!-- 声明通知类型 #1 前置通知 , 目标方法之前执行。 * 第一个参数为JoinPoint,可以获得目标方法名等。 <aop:before method="myBefore" pointcut-ref="myPonitCut"/> #2 后置通知,目标方法之后执行,可以获得返回值。 通过“returning”属性配置第二个参数的名称,获得返回值的,类型必须Object * 第一个参数为:JoinPoint * 第二个参数为:Object xxx <aop:after-returning method="myAfterReturning" pointcut-ref="myPonitCut" returning="xxx"/> #3 环绕通知, 目标方法前后 方法要求:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ 执行目标方法:joinPoint.proceed(); <aop:around method="myAround" pointcut-ref="myPonitCut"/> #4 抛出异常通知,目标方法出现异常时才执行。通过“throwing”属性配置第二个参数的名称,获得具体的异常信息,类型必须是Throwable * 第一个参数为:JoinPoint * 第二个参数为:Throwable e <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPonitCut" throwing="e"/> --> <aop:before method="myBefore" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
相比jdk的动态代理,spring屏蔽了底层的实现,使我们可以简单实现aop编程,但是我们也需要对动态代理有一定的基础了解,这是我学习经验,我自己也是理解不是很透侧,并没有具体应用,所以先写在这里,以后在具体研究,欢迎大家矫正,spring中的动态代理采取了两种实现方式:
当拦截对象实现了接口时,生成方式是用JDK的Proxy类。当没有实现任何接口时用的是GCLIB开源项目生成的拦截类的子类.