SpringAop学习

AOP简介

AOP是一种思想,一种编写方式。编写一段代码在合适的时机找到切入点然后执行。不直接修改原来的代码,而是在源代码执行前后执行一段额外的代码。

AOP联盟

AOP联盟,一群热爱技术的人觉得AOP思想不错,于是打算促进和标准化AOP,定制了一套标准,推动AOP和JAVA发展。
那么AOP联盟的标准是什么呢?
spring-aop jar包中有个目录org.aopalliance,其中存在几个接口,即是AOP联盟提供的AOP标准API。
SpringAop学习_第1张图片
SpringAop学习_第2张图片
上述中出现的关键词有Advice(顶级通知类/拦截器),MethodInvocation(方法连接点)、MethodInterceptor(方法拦截器),SpringAOP在此基础上又增加了几个类,丰富了AOP定义及使用概念,包括

Advisor:包含通知(拦截器),Spring内部使用的AOP顶级接口,还需要包含一个aop适用判断的过滤器,考虑到通用性,过滤规则由其子类接口定义,例如IntroductionAdvisor和PointcutAdvisor,过滤器用于判断bean是否需要被代理。

PointCut:切点,属于过滤器的一种实现,匹配过滤哪些类哪些方法需要别切面处理,包含一个MethodMatcher和一个ClassFilter,使用PointcutAdvisor定义时需要。

ClassFilter:属于过滤器的一种实现。过滤筛选合适的类,有些类不需要被处理。

MethodMatcher:方法匹配器,定义方法匹配规则,属于过滤器的一种实现,哪些方法需要使用AOP。

SpirngAop实现的大致思路:

  1. 配置获取Advisor(顾问):拦截器+AOP匹配过滤器,生成Advisor
  2. 生成代理:根据Advisor生成代理对象,会生成JdkDynamicAopProxy或CglibAopProxy
  3. 执行代理:代理类执行代理时,会从Advisor取出拦截器,生成MethodInvocation(连接点)并执行代理过程

SpringAOP与AOP联盟关系

SpringAop学习_第3张图片

配置Advisor

这一步对于SpringAop使用者很关键,决定了我们如何自定义配置Advisor,即SpringAOP和Aspectj,对于Aspectj是一种静态代理,下面会讲到,而SpringAOP是动态代理,但Aspectj的一套定义AOP的API非常好,直观易用。所以Spring引入了Aspectj,但只使用部分注解用来定义配置AOP,在获取Advisor阶段用来生成Advisor,与后面的代理生成和代理增强执行无关!

这两种方式有什么不同呢?

1.直接配置Advisor配置
实现Advisor接口,定义拦截器和拦截规则(切点)
2.间接配置Advisor
使用Aspectj jar包提供的注解定义AOP配置,由spring解析配置生成Advisor

直接配置Advisor

最少需要定义三个类,一个Advisor的实现类,一个advice实现类(拦截器),一个aop适配过滤器(这里使用的Advisor为派生的PointcutAdvisor ,需要定义Pointcut切点),可以增加一个注解用于AOP埋点,需要给bean哪个方法进行切面,则在方法上加上该注解。

Advisor:MyAdvisor,返回一个Advice,

Advice:MyInterceptAdvice,拦截器,invoke方法中可以添加切面逻辑代码

PointCut: MyPointCut,切点,匹配过滤出需要切面的类及方法,查找方法头注解了MyAnnotation的方法。

埋点注解:MyAnnotation

MyAdvisor:

@Component
public class MyAdvisor implements PointcutAdvisor {

    @Override
    public Advice getAdvice() {
        return new MyInterceptAdvice();
    }

    @Override
    public boolean isPerInstance() {
        return false;
    }

    @Override
    public Pointcut getPointcut() {
        return new MyPointCut();
    }
}

MyInterceptAdvice

public class MyInterceptAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("go go go MyAdvisor process!!!");
        return invocation.proceed();
    }
}

MyPointCut

public class MyPointCut implements Pointcut {

    @Override
    public ClassFilter getClassFilter() {
        return new MyClassFilter();
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return new MyMethodMatcher();
    }

    private class MyMethodMatcher implements MethodMatcher {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            Annotation[] annoArray = method.getDeclaredAnnotations();
            if (annoArray == null || annoArray.length == 0) {
                return false;
            }

            for (Annotation annotation : annoArray) {
                if (annotation.annotationType() == MyAnnotation.class) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean isRuntime() {
            return false;
        }

        @Override
        public boolean matches(Method method, Class<?> targetClass, Object... args) {
            return false;
        }
    }

    private class MyClassFilter implements ClassFilter {

        @Override
        public boolean matches(Class<?> clazz) {
            return AnnotationUtils.isCandidateClass(clazz, MyAnnotation.class);
        }
    }
}

需要切面的Service类

@Component
public class TestService {

    @MyAnnotation
    public void test() {
        System.out.println("test!");
    }
}

再定义一个测试类

@Component
public class RunTest {

    @Autowired
    private TestService testService;

    @PostConstruct
    public void test() {
        testService.test();
    }
}

AOP之代理

AOP是通过代理方式实现的,由代理对象持有原对象,在执行原对象目标方法的前后执行增强的方法。

代理对象需要是原对象接口的实现或原对象的子类。

代理方式分为静态代理和动态代理,区别在于代理对象生成方式不同。

静态代理:编译器增强,编写代理class,使用代理类替换原有类进行调用。
动态代理:运行期增强,内存中动态生成代理类,使用反射动态调用原对象方法。

AOP的实现有:AspectJ、JDK动态代理、CGLIB动态代理、JBossAOP、JMangler(后面两种仅限于传说,没接触过)。

AspectJ属于静态代理,使用特定编译器在编译器生成代理对象并于源码融在一起,即修改了class文件。
JDK动态代理必须基于接口,即生成的代理对象是对原对象接口的实现,相当于替换了实现类。
CGLIB动态代理基于继承,即生成的代理对象是原对象的子类。

关于Aspectj

先看一个demo:

public class HelloWord {

    public void sayHello(){
        System.out.println("hello world !");
    }
    public static void main(String args[]){
        HelloWord helloWord =new HelloWord();
        helloWord.sayHello();
    }
}

编写AspectJ类,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类(需要用特定的编译器,了解一下就行)

aspect MyAspectJDemo {
    /**
     * 定义切点,日志记录切点
     */
    pointcut recordLog():call(* HelloWord.sayHello(..));

    /**
     * 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
     */
    pointcut authCheck():call(* HelloWord.sayHello(..));

    /**
     * 定义前置通知!
     */
    before():authCheck(){
        System.out.println("sayHello方法执行前验证权限");
    }

    /**
     * 定义后置通知
     */
    after():recordLog(){
        System.out.println("sayHello方法执行后记录日志");
    }
}

ok~,运行helloworld的main函数:
SpringAop学习_第4张图片

SpringAop学习_第5张图片

pointcut定义切点,后面跟着函数名称,最后写匹配表达式,此时函数一般使用call()或者execution()进行匹配,案例中使用call():

pointcut 函数名 : 匹配表达式
pointcut recordLog():call(* HelloWord.sayHello(..));

案例:recordLog()是函数名称,自定义的,* 表示任意返回值,接着就是需要拦截的目标函数,sayHello(…)的…,表示任意参数类型。这里理解即可,整行代码的意思是使用pointcut定义一个名为recordLog的切点函数,其需要拦截的(切入)的目标方法是HelloWord类下的sayHello方法,参数不限。

advice定义通知语法,有一下五种类型:

  • before 目标方法执行前执行,前置通知
  • after 目标方法执行后执行,后置通知
  • after returning 目标方法返回时执行 ,后置返回通知
  • after throwing 目标方法抛出异常时执行 异常通知
  • around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知
[返回值类型] 通知函数名称(参数) [returning/throwing 表达式]:连接点函数(切点函数){
				函数体
	}

案例中的around环绕通知,可以通过proceed()方法控制目标函数是否执行

 /**
  * 环绕通知 可通过proceed()控制目标函数是否执行
  * Object around(参数):连接点函数{
  *     函数体
  *     Object result=proceed();//执行目标函数
  *     return result;
  * }
  */
 Object around():aroundAdvice(){
     System.out.println("sayAround 执行前执行");
     Object result=proceed();//执行目标函数
     System.out.println("sayAround 执行后执行");
     return result;
 }

现在应该对切入点(pointcut)和通知(advice)有一些了解了,而切面则是定义切点和通知的组合,比如上面案例中使用aspect关键字定义的MyAspectJDemo,把切面应用到目标函数的过程称为织入(weaving);案例中定义的helloworld类中除了有sayhello()方法,还有main()方法,这些方法都可以被称为目标函数, 这些目标函数就被称为连接点,切入点的定义就是从这些连接点中过滤出来的:
SpringAop学习_第6张图片
AspectJ的织入方式及其原理

对于织入这个概念,可以理解为就是aspect(切面)应用到目标函数(类)的过程,这个过程一般分为静态织入和动态织入:

动态织入的方式是在运行时动态将要增强的代码织入目标类中,通过动态代理的方式实现;比如java的JDK动态代理(Proxy,底层通过反射实现)或者CGLIB动态代理(底层通过继承实现),Spring AOP 就是基于运行时增强的代理技术,AspectJ采用的是静态织入的方式,也就是在编译期织入,在这个期间使用aspectJ的编译器把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

基于AspectJ Spring AOP 开发

Spring AOP 和AspectJ的目的是一致的,都是统一处理横切业务,但是与AspectJ不同的是,Spring AOP 更注重的是与Spring IOC容器的结合,因此在AOP的功能完善方面,AspectJ的具有更大的优势,同时,spring Aop注意到了AspectJ在AOP的实现方式上依赖于特殊编译器,因此Spring很机智的避免了,采用动态代理技术的实现原理构建Spring AOP 的动态织入,这是与AspectJ的最根本的区别,在AspectJ引用了@Aspect注解之后,spring也引进了,但是,spring只是引用了AspectJ的注解,仍然没有使用特殊的编译器,底层依然是动态代理技术的实现。

间接配置Advisor

定义接口类

 //接口类
public interface UserDao {

    int addUser();

    void updateUser();

    void deleteUser();

    void findUser();
}

定义目标类@Repository

public class UserDaoImp implements UserDao {
    @Override
    public int addUser() {
        System.out.println("add user ......");
        return 6666;
    }

    @Override
    public void updateUser() {
        System.out.println("update user ......");
    }

    @Override
    public void deleteUser() {
        System.out.println("delete user ......");
    }

    @Override
    public void findUser() {
        System.out.println("find user ......");
    }
}

编写spring aop的aspect类:

@Aspect//定义切面类
public class MyAspect {
// 也可以这样定义切入点表达式
//    /**
//     * 使用Pointcut定义切点
//     */
//    @Pointcut("execution(* com.czy.UserDao.addUser(..))")
//    private void myPointcut(){}
//
//    /**
//     * 应用切入点函数
//     */
//    @After(value="myPointcut()")
//    public void afterDemo(){
//        System.out.println("最终通知....");
//    }

    //前置通知
    @Before(value = "execution(* com.czy.UserDao.*(..))")
    public void before(){
        System.out.println("前置通知。。");
    }

    //后置通知
    @AfterReturning(value = "execution(* com.czy.UserDao.*(..))",returning = "returnVal")
    public void after(Object returnVal){
        System.out.println("后置通知。。"+returnVal);
    }

    /**
     * 环绕通知
     * @param joinPoint 用于执行切点的类
     * @return
     * @throws Throwable
     */
    @Around(value = "execution(* com.czy.UserDao.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("环绕同之前。。");
        Object proceed = joinPoint.proceed();
        System.out.println("环绕同之后。。");
        return proceed;
    }

    /**
     * 抛出通知
     * @param e
     */
    @AfterThrowing(value="execution(* com.czy.UserDao.*(..))",throwing = "e")
    public void afterThrowable(Throwable e){
        System.out.println("出现异常:msg="+e.getMessage());
    }

    /**
     * 无论什么情况下都会执行的方法
     */
    @After(value="execution(* com.czy.UserDao.*(..))")
    public void after(){
        System.out.println("最终通知....");
    }

}

编写配置文件配置springioc容器管理

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--    启动aspectJ注解-->
    <aop:aspectj-autoproxy />

<!--    定义目标对象-->
    <bean id="userDao" class="com.czy.UserDaoImp"></bean>
<!--    定义切面类-->
    <bean id="MyAspect" class="com.czy.MyAspect"></bean>
</beans>

编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:SpringConfig.xml")
public class Test {

    @Autowired
    UserDao userDao;

    @org.junit.Test
    public void aspectJTest(){
        userDao.addUser();
    }

}

输出结果
SpringAop学习_第7张图片

总结

  1. SPring AOP 不是一种新的AOP实现,使用JDK动态代理和CGLIB动态代理
  2. Spring AOP 配置方式核心是Advisor。可以自定义Advisor。也可以通过Aspectj间接定义Advisor
  3. Spring AOP 的实现遵循了AOP联盟规范,AOP联盟顶级API接口贯穿了整个AOP过程

你可能感兴趣的:(学习,java,代理模式)