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"/>-->
            <!--<!–<aop:around method="AroundLog" pointcut-ref="pt1"/>–>-->
        <!--</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中各个层之间的事务控制