Spring中的aop(aspect oriented programming)(切面编程)

Posted by zjhChester on November 7, 2019

spring中的aop(Aspect Oriented Programming)(切面编程)

1、前言

在认识aop之前我们需需要了解一下动态代理(基于基础不扎实的原因,可能缺失)

1、什么是代理:

*、在以前,我们买东西是从厂商直接拿:消费者—–>厂商(生产和销售),这样的弊端就是厂商的成本太高,既要关乎生产,又要关乎销售。

*、逐渐的,我们到现在的消费模式是:消费者—>销售商(代理商)—->厂商,这样就减少了厂商的运营成本。

2、java中的代理模式:

以上的模式咱们也可以在java代码中实现,在什么情况实现呢呢,举个例子:咱们使用JDBC进行数据持久化的时候,我们在正式持久化之前,需要先加载驱动,建立连接;在持久化完成之后,我们需要关闭连接,释放资源;此类的重复的工作,我们可以建立一个工具类帮助我们完成。

1、静态代理

实现原理:

​ 这里我就不写静态代理的源码了,大概含义就是,咱们写一个jdbc的实现类,对每个方法执行前,咱们先执行需要执行的工作,比如加载驱动,建立连接,执行之后咱们关闭连接,释放资源,这样我们的重复代码肯定会减少特别多的冗余,使代码的可读性更高,到这里,咱们就有了aop的意思了。

弊端:

​ 如果持久化中咱们有n个需要进行的动作,那么咱们就需要写n个实现类去进行切面式的代理,而且如果临时更改一些驱动,那么咱们就需要对源码进行修改,耦合性比较高

2、动态代理

实现原理:

所谓动态代理,就是让静态代理中,需要创建实现类的方法,得到了优化,在哪里实现某个动作,就在哪里去代理这个对象,即:随用随代理,并且实现了不改变源码的情况下对原有方法进行增强

实现代码:使用的就是jdk官方提供的接口动态代理Proxy.newProxyInstance,去编写咱们的动态代理

AccountService accountService1 = (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            /**
             *原生动态代理代码  接口动态代理  提供者  jdk官方
             * @param proxy 代理对象的引用
             * @param method 代理的方法
             * @param args 参数列表
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtValue = null;
                try {
                    System.out.println("前置 原生接口动态代理");
                    if ("saveAccount".equals(method.getName())) {
                        rtValue = method.invoke(accountService,args);
                    }
                    System.out.println("后置 原生接口动态代理");
                    return rtValue;
                } catch (Throwable t) {
                    System.out.println("异常 原生接口动态代理");
                } finally {
                    System.out.println("最终 原生接口动态代理");
                }
                return rtValue;
            }
        });
        accountService1.saveAccount();

这样就实现了,随用随代理,对源码并没有改动。

2、spring中的aop

与动态代理模式一个思想,用spring的方法去实现动态代理

1、思想和源码

话不多说 上代码 该例子是模拟一个账户的添加修改删除操作,并且在这些进行这些操作时,记录日志。

1、beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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
">


    <!--开启ioc注解扫描-->
    <context:component-scan base-package="cn.tfs"/>
    
    <!--开启aop注解扫描-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--手动编写aop实现的xml-->
    <!--<aop:config>-->
        <!--<aop:pointcut id="pt1" expression="execution(* cn.tfs.service.impl.*.*(..))"/>-->
        <!--<aop:aspect ref="logger">-->
            <!--<aop:before method="beforeLog" pointcut-ref="pt1"/>-->
            <!--<aop:after-returning method="afterReturningLog" pointcut-ref="pt1"/>-->
            <!--<aop:after-throwing method="afterThrowingLog" pointcut-ref="pt1"/>-->
            <!--<aop:after method="afterLog" pointcut-ref="pt1"/>-->
            <!--&lt;!&ndash;<aop:around method="AroundLog" pointcut-ref="pt1"/>&ndash;&gt;-->
        <!--</aop:aspect>-->
    <!--</aop:config>-->




</beans>

2、AccountService.java

package cn.tfs.service;

import org.springframework.stereotype.Service;

public interface AccountService {
    void saveAccount();
    void updateAccount(int i);
    int deleteAccount();
}

3、AccountServiceImpl.java

package cn.tfs.service.impl;

import cn.tfs.service.AccountService;
import org.springframework.stereotype.Service;

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    public void saveAccount() {
        System.out.println("保存了账户....");
//       int i= 1/0;
    }

    public void updateAccount(int i) {
        System.out.println("更新了账户....");

    }

    public int deleteAccount() {
        System.out.println("删除了账户....");
        return 0;
    }
}

4、TestAop.java

public class TestAop {

public static void main(){
AccountService accountService = new ClassPathXmlApplicationContext("beans.xml").getBean("accountService",AccountService.class);
        accountService.saveAccount();
	}
}

5、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.tfs</groupId>
    <artifactId>aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
</project>

6、Log.java

package cn.tfs.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 日志通知类  模拟aop实现切面编程
 */
@Component("logger")
@Aspect
public class Log {
    //到方法名: 访问修饰符 返回值 全限定类名.方法名(参数列表)
    @Pointcut("execution(* cn.tfs.service.impl.*.*(..))")
    public void po1(){ }
    //前置通知
    @Before("po1()")
    public void beforeLog(){
        System.out.println("beforeLog....前置通知");
    }
    //后置通知
    @AfterReturning("po1()")
    public void afterReturningLog(){
        System.out.println("afterReturningLog....后置通知");
    }
    //异常通知
    @AfterThrowing("po1()")
    public void afterThrowingLog(){
        System.out.println("afterThrowingLog....异常通知");
    }
    //最终通知
    @After("po1()")
    public void afterLog(){
        System.out.println("afterLog....最终通知");
    }
	//环绕通知
//    @Around("po1()")
    public Object AroundLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            System.out.println("AroundLog....前置通知");

            Object[] args = pjp.getArgs();
            rtValue = pjp.proceed(args);

            System.out.println("AroundLog....后置通知");
            return rtValue;
        }catch (Throwable t){

            System.out.println("AroundLog....异常通知");
        }finally {

            System.out.println("AroundLog....最终通知");
        }
        return  rtValue;
    }
}

2、需要注意的问题

aspectj基于全注解的aop,在分别实现aop的前置后置异常最终通知,他的位置会有一点点出入(后置通知/异常通知 会执行在最终通知之后)

所以建议的是使用xml配置形式或者基于全注解的环绕通知的形式进行spring中各个层之间的事务控制