Spring中的AOP

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中的AOP相关的知识,希望对你有一定的参考价值。

本节内容:

  • AOP介绍
  • Spring底层AOP的实现原理
  • Spring的AOP名词
  • Spring中的AOP开发

 

一、AOP介绍

1. 什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

Spring 是解决实际开发中的一些问题:

  • AOP 解决 OOP 中遇到的一些问题.是 OOP 的延续和扩展。

【注意】:Spring是提供了对对象进行aop编程的支持,并不是说Spring本身就能aop。Spring作为对象的容器,它能够帮为容器中管理的对象生成动态代理对象。以前生成动态代理对象需要自己写代码,而Spring能够帮我们生成代理对象,只需要配置文件配置下或者使用注解。

 

aop思想:横向重复,纵向抽取。

aop是个思想,在各个业务中、领域中都会体现,最早是在filter中,Servlet解决乱码。没学filter之前,解决乱码比较烦,每次接收参数之前都要解决参数乱码,于是每个Servlet中都要设置解决乱码。当我们使用了filter之后,请求到达Servlet之前先会经过filter,于是我们将解决乱码的代码放到了filter中,于是所有Servlet中的解决乱码的代码都可以舍弃了,见下图。从系统架构图来说,Filter中解决乱码这个事,等于代码加到了所有的Servlet类上,这样切面就形成了,所谓的面向切面编程。

技术分享图片

动态代理也使用过这个aop思想:使用动态代理技术将这个抽取出来的handler加到所有的service中,形成代理对象。

技术分享图片

还有个拦截器示例,也用到了aop思想:

技术分享图片

 

2. 为什么学习 AOP

对程序进行增强:不修改源码的情况下

  • AOP 可以进行权限校验,日志记录,性能监控,事务控制。

 

3. Spring 的 AOP 的由来

AOP 最早由 AOP 联盟的组织提出的,制定了一套规范。Spring 将 AOP 思想引入到框架中,必须遵守 AOP 联盟 的规范。

 

4. 底层实现

代理机制:Spring 的 AOP 的底层用到两种代理机制:

  • JDK 的动态代理:针对实现了接口的类产生代理。
  • Cglib 的动态代理:针对没有实现接口的类产生代理。应用的是底层的字节码增强的技术生成当前类的子类对象。

 

二、Spring底层AOP的实现原理

代理机制:Spring 的 AOP 的底层用到两种代理机制:

  • JDK 的动态代理:针对实现了接口的类产生代理。
  • Cglib 的动态代理:针对没有实现接口的类产生代理。应用的是底层的字节码增强的技术生成当前类的子类对象。

动态代理在使用时有个局限性,被代理对象必须要实现接口才能产生代理对象。如果代理对象没有接口,将不能使用动态代理技术。于是为了保证每一个对象都能生成代理对象,所以Spring融入了一个第三方代理技术:cglib代理。该代理技术对任何类生成代理,其代理的原理是对目标对象进行继承代理。如果目标对象被final修饰,那么该类无法被cglib代理。

那么Spring采用哪一种代理方式生成代理对象呢?

混合的。如果代理对象有接口,优先使用动态代理。如果没有接口,使用cglib代理。

 

【示例】:使用动态代理和cglib代理分别完成对类的代理。(把事务代码插入到需要事务的地方)

动态代理代码示例:

技术分享图片
package com.wisedu.service;

/**
 * Created by jkzhao on 12/12/17.
 */
public interface UserService {
    void save();
    void delete();
    void update();
    void find();

}
接口UserService.java
技术分享图片
package com.wisedu.service;

/**
 * Created by jkzhao on 12/12/17.
 */
public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        //System.out.println("打开事务");
        System.out.println("保存用户");
        //System.out.println("提交事务");
    }

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

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

    @Override
    public void find() {
        System.out.println("查找用户");
    }
}
被代理对象UserServiceImpl.java
技术分享图片
package com.wisedu.proxy;

import com.wisedu.service.UserService;
import com.wisedu.service.UserServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by jkzhao on 12/12/17.
 */
public class UserServiceProxyFactory implements InvocationHandler{

    public UserServiceProxyFactory(UserService us) {
        this.us = us;
    }

    private UserService us;

    public UserService getUserServiceFactory(){ //返回UserService的代理对象
        //生成动态代理返回
        UserService usProxy = (UserService)Proxy.newProxyInstance(
                UserServiceProxyFactory.class.getClassLoader(),
                UserServiceImpl.class.getInterfaces(),
                this);
        return usProxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("打开事务");
        Object invoke = method.invoke(us, args); //原有方法的调用
        System.out.println("提交事务");

        return invoke;
    }
}
代理对象类UserServiceProxyFactory.java
技术分享图片
package com.wisedu.proxy;

import com.wisedu.service.UserService;
import com.wisedu.service.UserServiceImpl;
import org.junit.Test;

/**
 * Created by jkzhao on 12/13/17.
 */
public class demo {
    @Test
    public void fun1(){
        UserService us = new UserServiceImpl(); //创建被代理对象
        UserServiceProxyFactory factory = new UserServiceProxyFactory(us);
        UserService usProxy = factory.getUserServiceFactory(); //代理对象
        usProxy.save();
    }

}
测试代码demo.java

cglib代理代码示例:(Spring整合了cglib,不需要在导入其他包)

技术分享图片
package com.wisedu.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.wisedu.service.UserService;
import com.wisedu.service.UserServiceImpl;

//观光代码=>cglib代理(cglib主要是对类进行继承代理)
public class UserServiceProxyFactory2 implements MethodInterceptor {

    public UserService getUserServiceProxy(){
        
        Enhancer en = new Enhancer();//帮我们生成代理对象
        
        en.setSuperclass(UserServiceImpl.class);//设置对谁进行代理(cglib主要是对类进行继承代理,得告诉它它爹是谁)
        
        en.setCallback(this);//代理要做什么
        
        UserService us = (UserService) en.create();//创建代理对象
        
        return us;
    }

    @Override
    public Object intercept(Object prxoyobj, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
        //打开事务
        System.out.println("打开事务!");
        //调用原有方法
        Object returnValue = methodProxy.invokeSuper(prxoyobj, arg);
        //提交事务
        System.out.println("提交事务!");
        
        return returnValue;
    }


}
UserServiceProxyFactory2.java
技术分享图片
    @Test
    public void fun2(){
        UserServiceProxyFactory2 factory = new UserServiceProxyFactory2();
        UserService usProxy = factory.getUserServiceProxy(); //代理对象
        usProxy.save();

        System.out.println(usProxy instanceof UserServiceImpl);
    }
测试方法

动态代理针对的是接口,而cglib代理是对被代理对象进行继承。

但是这些代码意义不大,将来在使用Spring时知道怎么去配置,不会让我们自己去手写。

 

三、Spring的AOP名词

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。 
  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
  • Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • Target(目标对象):代理的目标对象
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
  • Aspect(切面): 是切入点和通知(引介)的结合

技术分享图片

 

四、Spring中的AOP开发

1. 导包

4+2+2+2:4个Spring核心包,2个日志包,2(spring的aop包:spring-aop-4.2.4.RELEASE.jar和spring-aspects-4.2.4.RELEASE.jar),2(spring需要的第三方aop包:com.springsource.org.aopalliance-1.0.0.jar和com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar)

 

2. 准备目标对象

以上面示例中的 UserServiceImpl 作为目标对象。

技术分享图片
package com.wisedu.service;

/**
 * Created by jkzhao on 12/12/17.
 */
public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        //System.out.println("打开事务");
        System.out.println("保存用户");
        //System.out.println("提交事务");
    }

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

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

    @Override
    public void find() {
        System.out.println("查找用户");
    }
}
UserServiceImpl.java

 

3. 定义通知(增强)

在Spring当中如何定义通知呢?使用方法来定义通知。先建一个包,com.wisedu.springaop,然后在包里新建一个类,将通知放于类中。如下:

技术分享图片
package com.wisedu.springaop;

import org.aspectj.lang.ProceedingJoinPoint;

//通知类(通知:增强的代码)
public class MyAdvice {
    
    //前置通知    
//        |-目标方法运行之前调用
    //后置通知(如果出现异常不会调用)
//        |-在目标方法运行之后调用
    //环绕通知
//        |-在目标方法之前和之后都调用
    //异常拦截通知
//        |-如果出现异常,就会调用
    //后置通知(无论是否出现异常都会调用)
//        |-在目标方法运行之后调用
//----------------------------------------------------------------
    //前置通知
    public void before(){
        System.out.println("这是前置通知!!");
    }
    //后置通知
    public void afterReturning(){
        System.out.println("这是后置通知(如果出现异常不会调用)!!");
    }
    //环绕通知(最特殊的一个通知,要控制目标方法的调用,也就是需要自己手动调用目标方法,在目标方法之前和之后加增强的代码)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("这是环绕通知之前的部分!!");
        Object proceed = pjp.proceed();//调用目标方法
        System.out.println("这是环绕通知之后的部分!!");
        return proceed;
    }
    //异常通知
    public void afterException(){
        System.out.println("出事啦!出现异常了!!");
    }
    //后置通知
    public void after(){
        System.out.println("这是后置通知(出现异常也会调用)!!");
    }
}
MyAdvice.java

 

4. 配置将通知织入目标对象

技术分享图片
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       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-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">

<!-- 准备工作: 导入aop(约束)命名空间 -->
<!-- 1.配置目标对象 -->
    <bean name="userService" class="com.wisedu.service.UserServiceImpl" ></bean>
<!-- 2.配置通知对象 -->
    <bean name="myAdvice" class="com.wisedu.springaop.MyAdvice" ></bean>
<!-- 3.配置将通知织入目标对象 -->
    <aop:config>
        <!-- 配置切入点(目标对象哪些方法要增强),表达式多种写法:
            public void com.wisedu.service.UserServiceImpl.save()
            void com.wisedu.service.UserServiceImpl.save()  默认值就是针对public方法,public可以省略
            * com.wisedu.service.UserServiceImpl.save()  对该类下的返回值不做要求的save方法,把void改为*
            * com.wisedu.service.UserServiceImpl.*()  对方法不要求,但是要求方法空参
            * com.wisedu.service.UserServiceImpl.*(..) 对参数不做要求,没有也行,有也行

            * com.wisedu.service.*ServiceImpl.*(..)   对类不做要求,寻找service包下以ServiceImpl结尾的类中的所有方法。贴近现实项目中的表达式
            * com.wisedu.service..*ServiceImpl.*(..)  service的子包下也会去找
        -->
        <aop:pointcut expression="execution(* com.wisedu.service.*ServiceImpl.*(..))" id="pc"/> <!--id随便取,就是为切入点起个名字,expression是重点-->
        <aop:aspect ref="myAdvice" > <!--通知的描述-->
            <!-- 指定名为before方法作为前置通知 -->
            <aop:before method="before" pointcut-ref="pc" />
            <!-- 后置 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pc" />
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pc" />
            <!-- 异常拦截通知 -->
            <aop:after-throwing method="afterException" pointcut-ref="pc"/>
            <!-- 后置 -->
            <aop:after method="after" pointcut-ref="pc"/>
        </aop:aspect>
    </aop:config>
</beans>
applicationContext.xml
技术分享图片
package com.wisedu.springaop;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.wisedu.service.UserService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/wisedu/springaop/applicationContext.xml")
public class Demo {
    @Resource(name="userService")
    private UserService us; //这里获得到的已经是userService的代理对象了
    
    @Test
    public void fun1(){
        us.save(); //如果想使得异常通知生效,需要save()方法里需要出现异常,比如int i=0/1;
    }
    
}
测试Demo.java

技术分享图片

 

5. Spring的aop注解配置(了解)

(1)导包

和上面一样,4+2+2+2

(2)准备目标对象

和上面一样

(3)准备通知

和上面一样

(4)配置织入

技术分享图片
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       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-4.2.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">

<!-- 准备工作: 导入aop(约束)命名空间 -->
<!-- 1.配置目标对象 -->
    <bean name="userService" class="com.wisedu.service.UserServiceImpl" ></bean>
<!-- 2.配置通知对象 -->
    <bean name="myAdvice" class="com.wisedu.annotationaop.MyAdvice" ></bean>
<!-- 3.开启使用注解完成织入 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
applicationContext.xml
技术分享图片
package com.wisedu.annotationaop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

//通知类
@Aspect
//表示该类是一个通知类
public class MyAdvice {
    @Pointcut("execution(* com.wisedu.service.*ServiceImpl.*(..))")
    public void pc(){}
    //前置通知
    //指定该方法是前置通知,并制定切入点
    @Before("MyAdvice.pc()")
    public void before(){
        System.out.println("这是前置通知!!");
    }
    //后置通知
    @AfterReturning("execution(* com.wisedu.service.*ServiceImpl.*(..))")
    public void afterReturning(){
        System.out.println("这是后置通知(如果出现异常不会调用)!!");
    }
    //环绕通知
    @Around("execution(* com.wisedu.service.*ServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("这是环绕通知之前的部分!!");
        Object proceed = pjp.proceed();//调用目标方法
        System.out.println("这是环绕通知之后的部分!!");
        return proceed;
    }
    //异常通知
    @AfterThrowing("execution(* com.wisedu.service.*ServiceImpl.*(..))")
    public void afterException(){
        System.out.println("出事啦!出现异常了!!");
    }
    //后置通知
    @After("execution(* com.wisedu.service.*ServiceImpl.*(..))")
    public void after(){
        System.out.println("这是后置通知(出现异常也会调用)!!");
    }
}
MyAdvice.java
技术分享图片
package com.wisedu.annotationaop;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.wisedu.service.UserService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/wisedu/annotationaop/applicationContext.xml")
public class Demo {
    @Resource(name="userService")
    private UserService us;
    
    @Test
    public void fun1(){
        us.save();
    }
    
}
测试Demo.java

 

以上是关于Spring中的AOP的主要内容,如果未能解决你的问题,请参考以下文章

2018.12.24 Spring中的aop演示

每日一学之认识Spring中的AOP

spring中的AOP

Spring中的AOP

Spring中的AOP

Spring中的AOP