AOP在Spring框架中并不是特有的,Spring只是支持AOP编程的框架之一。每个框架对AOP的支持都各有各的特点,有些AOP能够对方法的参数进行拦截,有些AOP对方法进行拦截。其中Spring AOP是一种基于方法拦截的AOP,也就是Spring只能支持方法拦截的AOP,在Spring中有4种方式实现AOP的拦截功能。
- 使用ProxyFactoryBean和对应的接口实现AOP
- 使用XML配置AOP
- 使用@AspectJ注解驱动切面
- 使用AspectJ注入切面
在4种方式中,真正常用的也就两种就是@AspectJ注解和XML配置,而且是用@AspectJ注解的方式实现的切面,有时候XML配置也起到一定的辅助作用。本篇就是使用第3种方式@AspectJ注解来实现Spring AOP。
使用@AspectJ注解测试Spring AOP,我们可以以一个用户类的例子来测试(为了更好的使用注解,都是使用注解的方式来创建):
- 创建个用户信息类
- 创建一个用户接口
- 提供一个用户接口实现类
- 创建个切面类
- 配置XML
- 测试
1.用户信息类创建:
package soft.demo6_2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository("user")
public class User {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
@Value("1")
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
@Value("张三")
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
@Value("22")
public void setAge(Integer age) {
this.age = age;
}
}
@Value注解推荐放在set方法上使用,因为放在定义变量上时是通过反射的Filed赋值,破坏了封装性。
2.用户接口的创建:
package soft.demo6_2;
public interface UserInterface {
void getInfo(User user);
}
3.接口实现类的创建:
package soft.demo6_2;
import org.springframework.stereotype.Repository;
@Repository("userImpl")
public class UserImpl implements UserInterface {
@Override
public void getInfo(User user) {
System.out.println(" id:"+user.getId()+",\n name:"+user.getName()+",\n age:"+user.getAge());
}
}
可在此处使用注解,也可以在XML中配置该目标对象,由于我使用注解,所有在此处用注解代替XML的配置。
4.创建切面类--使用@注解Aspect:
package soft.demo6_2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class UserAspect {
@Pointcut("execution(* soft.demo6_2.*Impl.*(..))")
public void pc(){}
//前置
@Before("UserAspect.pc()")
public void before(){
System.out.println("------前置------");
}
//后置
@AfterReturning("UserAspect.pc()")
public void afterReturning(){
System.out.println("------后置(如果出现异常不会被调用)------");
}
//环绕
@Around("UserAspect.pc()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("------环绕(之前部分)------");
Object proceed = pjp.proceed();//调用目标方法
System.out.println("------环绕(之后部分)------");
return proceed;
}
//异常
@AfterThrowing("UserAspect.pc()")
public void afterException(){
System.out.println("-------异常(异常出现了!!!)------");
}
//后置
@After("UserAspect.pc()")
public void after(){
System.out.println("------后置(出现异常也会被调用)------");
}
}
需要定义的切点使用注解在该类中定义切入点,为了便于修改切点,可定义一个切入点方法,
@Pointcut("execution(* soft.demo6_2.*Impl.*(..))")
public void pc(){}
然后在后面直接调用该切入点方法即可。Spring就是通过这个切入点的正则表达式判断是否需要拦截你的方法,表达式:
execution(* soft.demo6_2.*Impl.*(..))
- execution:代表执行方法的时候会触发
- *:代表任意返回类型的方法
-
soft.demo6_2.*Impl.*(..)):代表在该包下类的限定名
- *(..):星号代表被拦截任意方法名称
- (..):任意的参数
代码中注解部分就是AspectJ的注解,其中:
- @Before:在被代理对象的方法前先调用,属于前置通知。
- @AfterReturning:在被代理对象的方法正常返回后调用,属于返回通知(也等于后置通知)如果发生异常则不会被调用
- @Around:在被代理对象的方法封装起来,并用环绕通知取代它,属于环绕通知,它将覆盖原有的方法,但是允许你通过反射调用原有方法。环绕通知是Spring AOP中最强大的通知,它可以同时实现前置和后置的通知,它保留了调度被代理对象原有方法的功能,所以它强大,又灵活。这个通知里有一个参数ProceedingJoinPoint,是Spring提供的一个参数,使它可以反射连接点。当环绕通知使用pjp.proceed()方法后会先调用前置通知,然后反射切点方法,最后就是后置通知和返回(或者异常)通知。
- @AfterThrowing:在被调用对象的方法抛出异常后调用,属于异常通知,要求被代理对象的方法执行过程中产生异常
- @After:在被代理对象的方法后调用,属于后置通知
5.配置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"
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">
<!-- 值定扫描soft.demo6_2包下的所有类中的注解。
注意:扫描包时,会扫描值定包下的所有子包
-->
<context:component-scan base-package="soft.demo6_2"></context:component-scan>
<!-- 配置目标对象 -->
<!--<bean name="userImpl" class="soft.demo6_2.UserImpl"></bean>-->
<!-- 配置切面对象 -->
<bean name="userAspect" class="soft.demo6_2.UserAspect"></bean>
<!-- 开始使用注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
使用了注解配置后,XML文件将会变的简单方便。
6.创建测试类测试代码:
package soft.demo6_2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath*:soft/demo6_2/applicationContext.xml")
public class DemoTest {
@Resource(name = "user")
private User user;
@Resource(name = "userImpl")
private UserInterface uf;
@Test
public void test(){
uf.getInfo(user);
System.out.println("--------------------------------");
user=null;
uf.getInfo(user);
}
}
注意:获取UserImpl实现类应该用UserInterface接口来接收,不然将会出现一下类型不匹配的异常:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'soft.demo6_2.DemoTest': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'userImpl' must be of type [soft.demo6_2.UserImpl], but was actually of type [com.sun.proxy.$Proxy22]
最后测试结果为: