0%

Spring AOP

1 AOP概述

1.1 面向切面编程

  • Aspact Oriented Programming,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
  • AOP是OOP的延续,Spring框架中的一个重要内容,是函数式编程的一种衍生规范。
  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
  • AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码。
  • AOP的应用:事务管理,性能监视,安全检查,缓存,日志。
  • Spring AOP使用纯Java的AOP框架,不需要专门的编译过程的类加载器,在运行期通过代理方式向目标类织入增强代码。
  • AspectJ是一个基于Java语言的AOP框架,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供代码的织入。

1.2 AOP实现原理

AOP底层采用代理机制进行实现。接口与实现类采用动态代理Proxy,有或没有接口的类都采用cglib字节码增强。

1.3 AOP术语

  1. target:目标类,需要被代理的类。
  2. Joinpoint 连接点:指可能被拦截到的方法。
  3. PointCut 切入点:已经被增强的连接点。
  4. advice 通知/增强:增强代码。
  5. Weaving 织入:指把增强advice应用到目标对象target来创建代理对象proxy的过程。
  6. proxy:代理类。
  7. Aspect:切入点pointcut和通知advice的结合。

1.4 手动代理

1.4.1 JDK动态代理

目标类的接口:

1
2
3
4
5
public interface IUserService{
public void addUser();
public void updateUser();
public void deleteUser();
}

目标类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserServiceImpl implements IUserService{
@Override
public void addUser(){
System.out.println("添加用户");
}

@Override
public void updateUser(){
System.out.println("更新用户");
}

@Override
public void deleteUser(){
System.out.println("删除用户");
}
}

切面类:增强代码与切入点的结合

1
2
3
4
5
6
7
8
9
public calss MyAspect{
public void before(){
System.out.println("开启事务");
}

public void after(){
System.out.println("提交事务");
}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class UserServiceFactory{
public static IUserService createUserService(){
//创建目标对象target
IUserService userService = new UserviceImpl();

//声明切面类对象
MyAspect aspect = new MyAspect();

//把切面类的两个方法应用到目标类
//创建JDK代理,拦截方法
IUserService serviceProxy = (IUserservice) Proxy.newProxyInstance(
UserServiceFactory.class.getclass(),
userService.getClass.getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throeable{
//开启事务
aspect.before();

//方法返回值是业务方法的返回值
Object retObj = method.invoke(UserService.args);
System.out.println("拦截返回值" + retObj);

//提交事务
aspect.after();

return retObj;
}
}
);

return serviceProxy;
}
}

现在,在测试类中调用由工厂类创建的userService对象,实际上是由JDK创建的目标类的代理。在调用userService的方法的前后,都会执行在切面类中定义的增强方法。

测试类:

1
2
3
4
5
6
7
8
public class test{
@Test
public void test1() throws Exception{
IUserService userService = UserServiceFactory.createUserService();

userService.deleteUser();
}
}

1.4.2 cglib字节码增强

  • 没有接口,只有实现类。
  • 采用字节码增强框架cglib。在运行时,创建目标类的子类,从未对目标类进行增强。
  • 所用jar包:spring-core

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class UserServiceFactory{
public static UserServiceImpleateUserService(){
//创建目标对象target
IUserService userService = new UserviceImpl();

//声明切面类对象
MyAspect aspect = new MyAspect();

//创建增强对象
Enhancer enhancer = new Enhancer();

//设置父类
enhancer.setSuperclass(UserServiceImpl.getClass());

//设置回调(拦截)
enhancer.Callback(new MethodInterceptor(){
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy){

aspect.before();
//放行方法
//Object ret Obj = method.invoke(UserServiceImpl, args);
//第二种写法,解耦
Object ret Obj = methodProxy.invokeSuper(proxy,args);
aspect.after();

}
});

//创建代理对象
UserServiceImpl serviceProxy = (UserServiceImpl) enhancer.create();

return serviceProxy;
}
}

测试类:

1
2
3
4
5
6
7
8
public class test{
@Test
public void test1() throws Exception{
UserServiceImpl userService = UserServiceFactory.createUserService();

userService.deleteUser();
}
}

1.5 AOP联盟通知类型

AOP联盟为通知Advice定义了org.aopalliance.Advice

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:

  1. 前置通知 org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强。
  2. 后置通知 org.springframework.aop.AfterRetunringAdvice,在目标方法执行后实施增强。
  3. 环绕通知 org.aopalliance.intercept.MethodInterceptor,在目标方法前后实施增强。
  4. 异常抛出通知 org.springframework.aop.ThrowAdvice,在方法抛出异常后实施增强。
  5. 引介通知 org.springframework.aop.Introductionterceptor,在目标类中添加一些新的方法和属性。

1.6 Spring编写代理

导入com.springsource.org.aopalliance-1.0.0.jar(AOP联盟制定的接口)和 spring-aop-5.2.3.RELEASE.jar(Spring对接口的实现)两个jar包。

目标类和手动代理时一样。

切面类:

1
2
3
4
5
6
7
8
9
10
11
12
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyAspect implements MethodInterceptor{
@Override
public Object invole(MethodInvocation mi) throws Throwable{
System.out.println("前");
Object retobj = mi.proceed();
System.out.println("后");
return retobj;
}
}

beans.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--目标类-->
<bean id="UserService" class="com.Retur0.service.UserServiceImpl"/>
<!--切面类-->
<bean id="myAspect" class="com.Retur0.service.MyAspect"/>
<!--
使用工厂bean创建代理时:
interfaces:确定接口,多个接口使用array,单个使用value。
target:确定目标。
interceptorNames:通知,切面类,是一个数组。

默认使用jdk代理
如果目标类有接口,采用jdk动态代理。
如果没有接口,采用cglib字节码代理。
如果声明optimize = true,无论是否有接口,都采用cglib。
-->
<bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.Retur0.service.IUserService"/>
<property name="target" ref="UserService"/>
<property name="interceptorNames" value="myAspect"/>
<!--使用cglib创建代理-->
<property name="optimize" value="true"/>
</bean>

test.java:

1
2
3
4
5
6
7
8
9
public class test{
@Test
public void test(){
//获取Spring容器中代理对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans3.xml");
IUserService userService = (IUserService) context.getBean("serviceProxy");
userService.deleteUser();
}
}

1.7 Spring全自动代理

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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">
<!--目标类-->
<bean id="userService" class="com.Retur0.service.UserServiceImpl"/>
<!--切面类-->
<bean id="myAspect" class="com.Retur0.aspect.MyAspect"/>
<!-- 全自动配置AOP -->
<aop:config proxy-target-class="true">
<!--切入点 被代理的方法
expression:表达式,给每个service的每个方法都被代理
-->
<aop:pointcut id="myPointcut" expression="execution(* com.Retur0.service.*.*(..))"/>
<!-- 通知 增强代码 -->
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointcut"/>
</aop:config>
</beans>

test.java

1
2
3
4
5
6
7
8
9
public class test {
@Test
public void test(){
//获取Spring容器中代理对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
IUserService userService = (IUserService) context.getBean("userService");
userService.deleteUser();
}
}

2 AspectJ

  • AspectJ是一个基于java 语言的AOP框架。
  • Spring2.0 之后增加了对AspectJ切点表达式的支持。
  • @Aspect 是AspectJ1.5的新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。

2.1 基于XML的AspectJ例子

需要的jar包:spring-aspects-5.2.3.RELEASE.jar

目标类使用之前的UserServiceImpl,切面类重写如下:、

AJAspect.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AJAspect {
public void myBefore(){
System.out.println("advice before the method..");
}
public void myAfter(){
System.out.println("advice after the method..");
}
public Object myAround(ProceedingJoinPoint jp) throws Throwable{
System.out.println("advice around the method...before");
Object obj = jp.proceed();
System.out.println("advice around the method...after");
return obj;
}
}

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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">

<!--配置UserService-->
<bean id="userService" class="com.Retur0.service.UserServiceImpl"/>
<!--配置切面对象-->
<bean id="AJAspect" class="com.Retur0.aspect.AJAspect"/>
<!--配置AOP-->
<aop:config >
<!--指定切面-->
<aop:aspect ref="AJAspect">
<!--指定切入-->
<aop:pointcut id="myPointcut" expression="execution(* com.Retur0.service.UserServiceImpl.*(..))"/>
<!--配置前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
<!--配置后置通知-->
<aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
<!--配置环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>

2.2 基于注解的例子

给目标类添加注解@Service。

给切面类添加注解@Component和@Aspect。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
@Aspect
public class AJAspect {
//声明一个公共切入点
@Pointcut("execution(* com.Retur0.service.UserServiceImpl.*(..))")
public void myPointcut(){}

//Before配置了目标方法
@Before("myPointcut()")
public void myBefore(){
System.out.println("advice before the method..");
}

@After("myPointcut()")
public void myAfter(){
System.out.println("advice after the method..");
}

@Around("myPointcut()")
public Object myAround(ProceedingJoinPoint jp) throws Throwable{
System.out.println("advice around the method...before");
Object obj = jp.proceed();
System.out.println("advice around the method...after");
return obj;
}
}

beans.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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">

<!--扫描注解-->
<context:component-scan base-package="com.Retur0"/>
<!--配置注解生效-->
<aop:aspectj-autoproxy/>
<!--aop配置-->
<aop:config>
<aop:aspect ref="AJAspect">
</aop:aspect>
</aop:config>
</beans>

2.3 注解总结

@Aspect 声明切片,修饰切面类,从而获得通知。

通知:

  • @Before 前置。
  • @AfterReturing 后置。
  • @Around 环绕。
  • @AfterThrowing 抛出异常。
  • @After 最终。

切入点:

@PointCut,修饰方法private void xxx(){},之后通过”方法名”过的切入点引用。