项目搭建
项目用到的jar包结构如下:
除了Spring基本的开发包,还包括spring的传统AOP的开发的包:
spring-aop-4.2.4.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
aspectJ的开发包:
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.2.4.RELEASE.jar
Spring的配置文件的具体的AOP的schema约束如下:
<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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
applicationContext.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--开启注解扫描,可以扫描com.mq包下的所有bean -->
<context:component-scan base-package="com.mq"/>
<!--开启AOP的自动代理,开启后,可以使用AOP的注解 -->
<aop:aspectj-autoproxy/>
</beans>
注意:context:component-scan base-package=”com.mq” 是开启注解扫描,扫描特定包结构下的java文件。
aop:aspectj-autoproxy 是开启自动代理,开启后才可以使用AOP的注解。
目标类的代码如下:
@Component(value="myService")
public class MyServiceImpl implements MyService {
@Override
public void saveUser() {
System.out.println("业务层保存用户");
}
}
该类只有一个很简单的方法,saveUsere();
注意MyServiceImpl 实现了MyService接口,这个在AOP开发中非常重要。
切面类的代码如下:
@Aspect
@Component(value="myAspect")
public class MyAspect {
@Before(value="execution(public void com.mq.service.MyServiceImpl.saveUser())")
public void log(){
System.out.println("记录日志...");
}
}
声明一个切面类,需要用到@Aspect注解。
@Before是通知类型,表示执行saveUser方法之前,执行log方法。value值是切入点表达式。
通知类型的注解类型:
* @Before – 前置通知
* @AfterReturing – 后置通知
* @Around – 环绕通知(目标对象方法默认不执行的,需要手动执行)
* @After – 最终通知
* @AfterThrowing – 异常抛出通知
测试类如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
@Resource(name="myService")
private MyService myService;//切记不能使用MyServiceImpl创建对象,否则会报错。
@Test
public void f1(){
myService.saveUser();
}
}
注意:必须使用接口的引用调用方法,如果使用MyServiceImpl对象,会报错。
执行结果:
记录日志...
业务层保存用户
切面类还有另外一种写法,将通知和切入点分开写。
配置通用的切入点:
@Aspect
@Component(value="myAspect")
public class MyAspect {
@After(value="MyAspect.fn()")
public void log(){
System.out.println("记录日志...");
}
@Pointcut(value="execution(public void com.mq.service.MyServiceImpl.saveUser())")
public void fn(){}
}
@Pointcut是切入点的注解。
@After的值是”类名.切入点方法名“
配置通用切入点的好处是,不用经常写繁琐的切入点表达式,可以简化开发。
环绕通知
环绕通知的方法需要手动执行,比较特殊。环绕通知的代码如下:
@Aspect
@Component(value="myAspect")
public class MyAspect {
@Pointcut(value="execution(public void com.mq.service.MyServiceImpl.saveUser())")
public void fn(){}
/**
* 环绕通知
*/
@Around(value="MyAspect.fn()")
public void around(ProceedingJoinPoint joinPoint){
System.out.println("环绕通知执行前");
try {
// 让目标对象的方法执行
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("环绕通知执行后");
}
}
执行结果:
环绕通知执行前
业务层保存用户
环绕通知执行后
java.lang.ClassCastException: com.sun.proxy.$Proxy* cannot be cast to **
在使用AOP的时候,一不小心就会遇到这样的异常。
因为AOP底层采用动态代理的方式实现,一种是常规的JDK,另一种是CGLIB。当代理对象实现接口时,默认使用JDK的方式动态创建代理对象。如果代理对象没有实现任何接口,则采用CGLIB的方式。
一般情况下,代理对象都会实现接口。
解决方法:
如果代理对象实现了接口,那么就使用接口的引用调用方法,不能直接使用代理类的引用。简而言之,使用多态。