看现象
AOP是在Spring IOC的基础之上的功能,所以需要先了解一下IOC,如果不了解IOC,请看之前的Spring源码系列之IOC初始化过程的文章
maven依赖
本文是要在IOC容器的基础之上测试AOP与事务,所以需要spring-aspects的支持与sqlite的支持
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.2</version>
</dependency>
<!--IOC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>RELEASE</version>
</dependency>
<!--AOP-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>RELEASE</version>
</dependency>
<!--sqlite驱动-->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.28.0</version>
</dependency>
</dependencies>
测试用例
/**
* 测试AOP代理
*/
public class TestSpringAOP {
@Test
public void test(){
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("aop/aop.xml");
AopInterfaceUserDao aopUserDao = context.getBean("aopUserDao", AopInterfaceUserDao.class);
List<User> users = aopUserDao.findUsers(10);
System.out.println("aopUserDao 是一个JDK动态代理:" + aopUserDao.getClass());
System.out.println("---------------------------------");
AopClassUserDao aopClassUserDao = context.getBean("aopClassUserDao", AopClassUserDao.class);
List<User> users1 = aopClassUserDao.aopClassUserDaoFindUsers(10);
System.out.println("aopClassUserDao 是一个CGLIB动态代理:" + aopClassUserDao.getClass());
}
}
测试用例里面引入了配置文件:aop.xml,看看里面写了啥
- aop.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!--开启切面代理-->
<aop:aspectj-autoproxy />
<!--开启后强制只用CGLIB代理,不管有没有实现接口-->
<!--<aop:config proxy-target-class="true"/>-->
<!--被代理对象-->
<bean id="aopUserDao" class="com.spring.dao.impl.AopInterfaceUserDaoImpl"/><!--实现了接口的AOP代理-->
<bean id="aopClassUserDao" class="com.spring.dao.AopClassUserDao"/><!--没实现接口的AOP代理-->
<!--切面bean-->
<bean id="loggingAspect" class="com.spring.aop.LoggingAspect"/>
<aop:config>
<aop:aspect ref="loggingAspect">
<!-- @Around 环绕切面-->
<aop:pointcut id="pointCutAround" expression="within(com.spring.dao.*) || within(com.spring.dao.impl.*)" />
<aop:around method="logAround" pointcut-ref="pointCutAround" />
</aop:aspect>
</aop:config>
</beans>
- AopInterfaceUserDao与AopInterfaceUserDaoImpl
/**
* 实现接口的AOP代理
*/
public interface AopInterfaceUserDao {
List<User> findUsers(int howMany);
}
public class AopInterfaceUserDaoImpl implements AopInterfaceUserDao {
@Override
public List<User> findUsers(int howMany) {
System.out.println("findUsers调用中...");
List<User> list = new ArrayList<>();
Faker faker = new Faker(new Locale("zh-CN"));
for (int i = 0; i < howMany; i++) {
User user = new User();
user.setName(faker.name().fullName());
user.setAge(faker.number().numberBetween(0,100));
user.setPhone(faker.phoneNumber().cellPhone());
user.setLocation(faker.address().city());
list.add(user);
}
return list;
}
}
- AopClassUserDao
/**
* 不实现接口,直接是类的AOP代理
*/
public class AopClassUserDao {
public List<User> aopClassUserDaoFindUsers(int howMany) {
System.out.println("AopClassUserDaoFindUsers调用中...");
List<User> list = new ArrayList<>();
Faker faker = new Faker(new Locale("zh-CN"));
for (int i = 0; i < howMany; i++) {
User user = new User();
user.setName(faker.name().fullName());
user.setAge(faker.number().numberBetween(0,100));
user.setPhone(faker.phoneNumber().cellPhone());
user.setLocation(faker.address().city());
list.add(user);
}
return list;
}
}
- LoggingAspect
public class LoggingAspect {
/**
* 环绕切面
* @param joinPoint
* @return
* @throws Throwable
*/
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String name = joinPoint.getSignature().getName();
System.out.println(name + "调用之前....");
Object original = joinPoint.proceed();//调用原始方法
System.out.println(name + "调用之后....");
return original;
}
}
运行结果
findUsers调用之前....
findUsers调用中...
findUsers调用之后....
aopUserDao 是一个JDK动态代理:class com.sun.proxy.$Proxy7
---------------------------------
aopClassUserDaoFindUsers调用之前....
AopClassUserDaoFindUsers调用中...
aopClassUserDaoFindUsers调用之后....
aopClassUserDao 是一个CGLIB动态代理:class com.spring.dao.AopClassUserDao$$EnhancerBySpringCGLIB$$df386aef
思考
为什么可以在一个方法前后动态的调用别的代码呢?我们创建的AopInterfaceUserDao到了最后发现实际创建的并不是它自己,而是一个com.sun.proxy.$Proxy7,这又是个啥?
我们知道Java是要编译成字节码然后才能被JVM执行,JVM执行只看字节码,与具体语言无关,只要能生成合法的字节码,那么JVM就可以运行之,那有没有可能我们自己的类编译成的字节码已经被修改了呢?答案是肯定的,Spring这招偷天换日真是高,那么是怎么实现操作字节码的?答案就是JDK动态代理与CGLIB,CGLIB的底层又是ASM,字节码操纵器
- asm:h