spring aop原理

使用AspectJ的编译时增强实现AOP

之前提到,AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。

举个实例的例子来说。首先我们有一个普通的Hello

1
2
3
4
5
6
7
8
9
10
public class Hello {
     public void sayHello() {
         System.out.println( "hello" );
     }
 
     public static void main(String[] args) {
         Hello h = new Hello();
         h.sayHello();
     }
}

使用AspectJ编写一个Aspect

1
2
3
4
5
6
7
public aspect TxAspect {
     void around():call( void Hello.sayHello()){
         System.out.println( "开始事务 ..." );
         proceed();
         System.out.println( "事务结束 ..." );
     }
}

这里模拟了一个事务的场景,类似于Spring的声明式事务。使用AspectJ的编译器编译

1
ajc -d . Hello.java TxAspect.aj

编译完成之后再运行这个Hello类,可以看到以下输出

1
2
3
开始事务 ...
hello
事务结束 ...

显然,AOP已经生效了,那么究竟AspectJ是如何在没有修改Hello类的情况下为Hello类增加新功能的呢?

查看一下编译后的Hello.class

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Hello {
     public Hello() {
     }
 
     public void sayHello() {
         System.out.println( "hello" );
     }
 
     public static void main(String[] args) {
         Hello h = new Hello();
         sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure) null );
     }
}

可以看到,这个类比原来的Hello.java多了一些代码,这就是AspectJ的静态代理,它会在编译阶段将Aspect织入Java字节码中, 运行的时候就是经过增强之后的AOP对象。

1
2
3
4
5
public void ajc$around$com_listenzhangbin_aop_TxAspect$ 1 $f54fe983(AroundClosure ajc$aroundClosure) {
         System.out.println( "开始事务 ..." );
         ajc$around$com_listenzhangbin_aop_TxAspect$ 1 $f54fe983proceed(ajc$aroundClosure);
         System.out.println( "事务结束 ..." );
     }

从Aspect编译后的class文件可以更明显的看出执行的逻辑。proceed方法就是回调执行被代理类中的方法。

使用Spring AOP

与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

为了验证以上的说法,可以做一个简单的测试。首先测试实现接口的情况。

定义一个接口

1
2
3
public interface Person {
     String sayHello(String name);
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Chinese implements Person {
 
     @Timer
     @Override
     public String sayHello(String name) {
         System.out.println( "-- sayHello() --" );
         return name + " hello, AOP" ;
     }
 
     public void eat(String food) {
         System.out.println( "我正在吃:" + food);
     }
 
}

这里的@Timer注解是我自己定义的一个普通注解,用来标记Pointcut。

定义Aspect

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
@Component
public class AdviceTest {
 
     @Pointcut ( "@annotation(com.listenzhangbin.aop.Timer)" )
     public void pointcut() {
     }
 
     @Before ( "pointcut()" )
     public void before() {
         System.out.println( "before" );
     }
}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {
 
     //这里必须使用Person接口做注入
     @Autowired
     private Person chinese;
 
     @RequestMapping ( "/test" )
     public void test() {
         chinese.sayHello( "listen" );
         System.out.println(chinese.getClass());
     }
 
     public static void main(String[] args) {
         SpringApplication.run(SpringBootDemoApplication. class , args);
     }
}

输出

1
2
3
before
-- sayHello() --
class com.sun.proxy.$Proxy53

可以看到类型是com.sun.proxy.$Proxy53,也就是前面提到的Proxy类,因此这里Spring AOP使用了JDK的动态代理。

再来看看不实现接口的情况,修改Chinese

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Chinese {
 
     @Timer
//    @Override
     public String sayHello(String name) {
         System.out.println( "-- sayHello() --" );
         return name + " hello, AOP" ;
     }
 
     public void eat(String food) {
         System.out.println( "我正在吃:" + food);
     }
 
}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
public class SpringBootDemoApplication {
 
     //直接用Chinese类注入
     @Autowired
     private Chinese chinese;
 
     @RequestMapping ( "/test" )
     public void test() {
         chinese.sayHello( "listen" );
         System.out.println(chinese.getClass());
     }
 
     public static void main(String[] args) {
         SpringApplication.run(SpringBootDemoApplication. class , args);
     }
}

输出

1
2
3
before
-- sayHello() --
class com.listenzhangbin.aop.Chinese$$EnhancerBySpringCGLIB$$56b89168

可以看到类被CGLIB增强了,也就是动态代理。这里的CGLIB代理就是Spring AOP的代理,这个类也就是所谓的AOP代理,AOP代理类在切点动态地织入了增强处理。

五种类型的通知

  1. Before advice:在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。 
    < aop:before>

  2. After advice:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 
    < aop:after>

  3. After returnadvice:在某连接点正常完成后执行的通知,不包括抛出异常的情况。 
    < aop:after-returning>

  4. Around advice:包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法执行的前后实现逻辑,也可以选择不执行方法 
    < aop:around>

  5. Afterthrowing advice:在方法抛出异常退出时执行的通知。 
    < aop:after-throwing>

 基于Schema的Spring AOP实例

第一步、定义具体业务逻辑模块(目标对象)

两个业务逻辑模块都是基于接口

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }

}

TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }

}

第二步和第三步、 定义切面(即实现通知逻辑)

JointPoint是连接点,aop创建代理后会返回一个连接点,然后在通知中可以通过该连接点实现我们的切面逻辑

日志切面

public class LogAdivice{

    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

    /**
     * 环绕通知将决定要不要执行连接点
     * @throws Throwable 
     */
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知,执行代码前");
        //选择执行
        point.proceed();
        System.out.println("环绕通知,执行代码后");
    }
}

时间切面:

public class TimeAdvice {

    public void timeBefore(){
        System.out.println("beforeTime = " + System.currentTimeMillis());
    }

    public void timeAfter(){
        System.out.println("afterTime = " + System.currentTimeMillis());
    }
}

在applicationContext中配置切面:

<context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <aop:config>
       <!-- 配置一个切面 -->
       <aop:aspect id="logaop" ref="logAdivice" order="2">
           <!-- 定义切入点,表示对service的所有方法都进行拦截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定义前置通知 -->
           <aop:before method="myBeforeAdivice" pointcut-ref="testpointcut"/>
           <!-- 定义后置通知 -->
           <aop:after-returning method="myAfterAdivice" pointcut-ref="testpointcut"/>
           <!-- 定义环绕通知 -->
           <aop:around method="myAroundAdivice" pointcut-ref="testpointcut"/>
       </aop:aspect>

       <!-- 定义另一个切面 -->
       <aop:aspect id="timeaop" ref="timeAdvice" order="1">
           <!-- 定义切入点,表示对service的所有方法都进行拦截 -->
           <aop:pointcut expression="execution(* com.ssh.service.TestAOPService.*(..))" id="testpointcut"/>
           <!-- 定义前置通知 -->
           <aop:before method="timeBefore" pointcut-ref="testpointcut"/>
           <!-- 定义后置通知 -->
           <aop:after-returning method="timeAfter" pointcut-ref="testpointcut"/>
       </aop:aspect>
    </aop:config>

当有多个切面时,Spring默认是按照切面定义的顺序来执行,也可以通过order属性来配置切面的执行属性,order=1 早于 order=2执行

测试结果

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        TestAOPService service = (TestAOPService) context.getBean("testAOPService");
        service.addUser();
    }

}

这里写图片描述

基于@AspectJ注解的AOP实现

第一步、定义具体业务逻辑模块(目标对象)

第一步和上面一样

TestAOPDaoImpl .java

public class TestAOPDaoImpl implements TestAOPDao{

    @Override
    public void addUser() {
        System.out.println("添加成功");
    }

}

TestAOPServiceImpl.java

public class TestAOPServiceImpl implements TestAOPService{

    @Autowired
    private TestAOPDao testAOPDao;

    @Override
    public void addUser() {
        testAOPDao.addUser();
    }

}

第二步和第三步、 定义切面(即实现通知逻辑)

重点是定义切入点

@Aspect
public class LogAdivice{

    //定义一个方法作为切入点id
    @Pointcut("execution(* com.ssh.service.TestAOPService.*(..))")
    private void allMethod(){}

    @Before("allMethod()")
    public void myBeforeAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——前置通知——" + methodname);
    }

    @AfterReturning("allMethod()")
    public void myAfterAdivice(JoinPoint joinPoint){
        String classname = joinPoint.getTarget().getClass().getSimpleName();
        String methodname = joinPoint.getSignature().getName();
        System.out.println(classname + " ——后置通知——" + methodname);
    }

    /**
     * 环绕通知将决定要不要执行连接点
     * @throws Throwable 
     */
    @Around("allMethod()")
    public void myAroundAdivice(ProceedingJoinPoint point) throws Throwable{
        System.out.println("环绕通知,执行代码前");
        //执行
        point.proceed();
        System.out.println("环绕通知,执行代码后");
    }
}

在applicationContext的配置:

<!-- 打开自动扫描(隐式打开注解管理器) -->
    <!-- <context:component-scan base-package="com.ssh"/> -->
    <context:annotation-config/>
    <bean id="testAOPDao" class="com.ssh.dao.impl.TestAOPDaoImpl"/>
    <bean id="testAOPService" class="com.ssh.service.impl.TestAOPServiceImpl"/>
    <bean id="logAdivice" class="com.ssh.adivice.LogAdivice"/>
    <bean id="timeAdvice" class="com.ssh.adivice.TimeAdvice"/>

    <!-- 打开aop注解管理器 -->
    <aop:aspectj-autoproxy/>


参考自http://listenzhangbin.com/post/2016/09/spring-aop-cglib/

         http://blog.csdn.net/jeffleo/article/details/54136904


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Spring AOP(Aspect-Oriented Programming)是一种基于面向切面编程的技术,它通过将一个应用程序分解成许多独立的部分,从而提高了应用程序的模块化程度,同时也提高了代码的可重用性和可维护性。 Spring AOP 是在运行时对程序进行修改的,它不需要重新编译源代码,因此使得代码的维护和修改更加方便。Spring AOP 原理主要分为以下几个方面: 1. 切面(Aspect):切面是一个,其中包含了一组相关的Advice和其他相关的代码,用于在目标对象的方法执行前、执行后或者抛出异常时执行相应的操作。 2. 连接点(Join point):连接点是指在应用程序的执行过程中,可以被切面拦截的点,比如方法调用、异常抛出、属性赋值等。 3. 通知(Advice):通知是指在连接点处执行的代码,有多种型的通知,包括前置通知(Before advice)、后置通知(After advice)、返回通知(After returning advice)、异常通知(After throwing advice)和环绕通知(Around advice)。 4. 切入点(Pointcut):切入点是一个表达式,用于定义哪些连接点会被切面拦截。 5. 代理(Proxy):代理是指在目标对象和切面之间创建的对象,它可以拦截目标对象的方法调用,并在方法执行前后执行相应的通知。 Spring AOP 的实现机制主要是通过 JDK 动态代理或者 CGLIB 动态代理来实现的。当目标对象实现了接口时,Spring AOP 使用 JDK 动态代理,否则使用 CGLIB 动态代理。在 JDK 动态代理中,代理对象实现了与目标对象相同的接口,而在 CGLIB 动态代理中,代理对象是目标对象的子

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值