04-Spring AOP

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linzhaoliangyan/article/details/88687979

 * 什么是AOP

1 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
2 AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
3 AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
4 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
5 AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容
6 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

* 为什么要使用AOP

    对程序进行增强:不修改源码的情况下.
       * AOP可以进行权限校验,日志记录,性能监控,事务控制.

* AOP的底层实现

        * 参考代理文档

        * 总结

Spring的AOP底层实现(动态代理)
1. Srping框架的AOP技术底层也是采用的就是代理技术,代理的方式提供了两种
        1. 基于JDK的动态代理
            * 必须是面向接口的,只有实现了具体接口的类才能生成代理对象
        
        2. 基于CGLIB动态代理
            * 对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式
    
2. Spring的传统AOP中根据类是否实现接口,来采用不同的代理方式
        1. 如果实现类接口,使用JDK动态代理完成AOP
        2. 如果没有实现接口,采用CGLIB动态代理完成AOP
  
AspectJ的AOP的实现(静态代理)
 * 采用编译期织入和类装在期织入  
 
* Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别

*注意: Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器

* AOP相关术语

Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.

Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.

Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Target(目标对象):代理的目标对象

Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.
       spring AOP采用动态代理织入,而AspectJ采用编译期织入和类装在期织入

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面): 是切入点和通知的结合

   

* AOP的开发

* xml的形式

            * 添加依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.18.RELEASE</version>
  </dependency>
   <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
   </dependency>
    <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
     </dependency>

  * 导入命名空间

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.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">

* 编写目标对象

public interface UserDao {
    public void addUser(User user);
}

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser(User user) {
        System.out.println("addUser");
    }
}


public class TransactionAspect {
    public void startTransation(){
        System.out.println("开始事务");
    }

    public void commitTransation(){
        System.out.println("提交事务");
    }
}

<!--目标类-->
    <bean id="userDao" class="com.hx.hx02.dao.UserDaoImpl"></bean>
    <!--切面类-->
    <bean id="tsa" class="com.hx.hx02.aspect.TransactionAspect"></bean>

    <aop:config>
        <aop:aspect ref="tsa">
            <aop:before method="startTransation" pointcut="execution(public void com.hx.hx02.dao.UserDaoImpl.addUser(com.hx.hx02.bean.User))"></aop:before>
            <aop:after method="commitTransation" pointcut="execution(public void com.hx.hx02.dao.UserDaoImpl.addUser(com.hx.hx02.bean.User))"></aop:after>
        </aop:aspect>
    </aop:config>


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class SpringTest {
    @Autowired
    private UserDao userDao;
    @Test
    public void test2(){
        User user=new User();
        userDao.addUser(user);
    }
}

* xml开发细节的形式

            * execution 表达式语法

            * 测试execution 表达式效

   参考:
    <!--目标类-->
    <bean id="userDao" class="com.hx.hx02.dao.UserDaoImpl"></bean>
    <bean id="topicDap" class="com.hx.hx02.dao.impl.TopicDaoImpl"></bean>
    <bean id="studentService" class="com.hx.hx02.service.StudentServiceImpl"/>
    <bean id="teacherService" class="com.hx.hx02.service.TeacherServiceImpl"></bean>
    <!--切面类-->
    <bean id="tsa" class="com.hx.hx02.aspect.TransactionAspect"></bean>

    <aop:config>
        <aop:aspect ref="tsa">
            <aop:before method="startTransation" pointcut="execution(* addUser(..))"></aop:before>
            <aop:after method="commitTransation" pointcut="execution(* add*(..))"></aop:after>
            <aop:before method="pleaseSpeak" pointcut="execution(* *(..))"></aop:before>
        </aop:aspect>
    </aop:config>

execution表达式语法简介
复杂形式格式:
execution(方法的修饰符 方法的返回值类型 方法所属的类 方法名 方法中参数列表 方法抛出的异常)
简化形式格式:
execution(方法的返回值类型 方法名(方法中参数列表)) 
        * 可以不写包名和类:代表是工程所有addUser方法都有效
         execution(public void addUser() throws Exception)
        * 可以不写包名和类,一个参数可以使用 * 号代替
         execution(public void addUser(*)throws Exception)
        * 可以不写包名和类,任意参数(包括没有参数)可以使用 ..   
         execution(public void addUser(..)throws Exception)
        * 省略修饰符,返回值用*代替
         execution(* com.hx.hx02.dao.UserDaoImpl.addUser(..))
        * 可以不写包名和类,返回值用*代替,声明异常
         execution(* addUser() throws Exception)
        * 可以不写包名和类,任意方法名:*
         execution(public void *(..)throws Exception)
        * 可以不写包名和类,方法名以add开始
         execution(public void add*(..)throws Exception)
        * 可以不写包名和类,方法名以service结尾
         execution(public void *service(..)throws Exception):方法名以e字符结束
        *省略修饰符,可以不写包名和类,返回值用*代替  
         execution(* add())
        * 省略修饰符,可以不写包名和类,返回值用*代替,任意方法名:*,任意参数(包括没有参数)可以使用 ..  
         execution(* *(..))
        * 省略修饰符,com.hx.hx02.dao包和其子包都有效果,方法名以add开始
         execution(* com.hx.hx02.dao..insert*(..))

* 通知类型 

五种通知:
前置通知Before advice:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。

正常返回通知After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。

异常返回通知After throwing advice:在连接点抛出异常后执行。

后置通知After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。

环绕通知Around advice:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。

五种通知的执行顺序
xml的形式:
1 在方法执行正常时:
    前置通知before
    环绕通知around
    方法执行
    环绕通知around
    后置通知after
    正常返回通知afterReturning
    
2 在方法执行抛出异常时:
    这个时候顺序会根据环绕通知的不同而发生变化:
2.1环绕通知捕获异常并不抛出:
    前置通知before
    环绕通知around
    方法执行
    环绕通知around
    后置通知after
    正常返回通知afterReturning
    
2.2 环绕通知捕获异常并抛出:
    前置通知before
    环绕通知around 
    方法执行
    环绕通知around
    后置通知after
    异常返回通知afterThrowing

* 注解的形式
环绕通知around 比 前置通知before 先执行
 

* 测试通知的执行顺序

public interface ISpeakService {
     void speakEnligh();
     String speak(String languge);
     void err(boolean isThrow);
}

public class SpeakServiceImpl implements ISpeakService {
    @Override
    public void speakEnligh() {
        System.out.println("speak English");
    }

    @Override
    public void speak(String languge) {
        System.out.println("speak "+languge);
    }

    @Override
    public void err(boolean isThrow) {
        System.out.println("error....");
        throw new RuntimeException("speak error");
    }
}

public class SpeakAspect {
    public void speakBefore(){
        System.out.println("---speakBefore---");
    }

    public void speakAfter(){
        System.out.println("---speakAfter---");
    }

    public Object speakAround(ProceedingJoinPoint pjp) throws Throwable {
        Object obj=null;
        System.out.println("---around前---");
        try{
            // 执行目标方法
            obj = pjp.proceed();
        }catch (Throwable e){
            System.out.println("---around异常---");
            // 监听参数为true则抛出异常,为false则捕获并不抛出异常
            if(pjp.getArgs().length>0 && (Boolean) pjp.getArgs()[0]){
                throw e;
            }else{
                obj=null;
            }
        }
        System.out.println("---around后---");
        return obj;
    }

    public void speakAfterReturning(){
        System.out.println("speakAfeterReturning");
    }

    public void speakAfterThrowing(){
        System.out.println("speakAfterThrowing");
    }
}


<bean id="speakService" class="com.hx.hx02.service.SpeakServiceImpl"></bean>
    <bean id="sa" class="com.hx.hx02.aspect.SpeakAspect"/>
    <aop:config>
        <aop:pointcut id="speak" expression="execution(* com.hx.hx02.service.SpeakServiceImpl.*(..))"></aop:pointcut>
        <aop:aspect ref="sa">
            <aop:before method="speakBefore"
                        pointcut-ref="speak"></aop:before>
            <aop:around method="speakAround"
                        pointcut-ref="speak"></aop:around>
            <aop:after method="speakAfter"
                       pointcut-ref="speak"></aop:after>
            <aop:after-returning method="speakAfterReturning" pointcut-ref="speak"/>
            <aop:after-throwing method="speakAfterThrowing" pointcut-ref="speak"/>
        </aop:aspect>
    </aop:config>


@Test
    public void test3(){
        speakService.speakEnligh();
    }

       


  

 @Test
    public void test3(){
        speakService.speak("Chinese");
    }

@Test
    public void test3(){
        speakService.err(false);
    }

 @Test
    public void test3(){
        speakService.err(true);
    } 

 * 注解的形式(AspectJ)

<!--为true基于cglib代理,
        为false或者省略这个属性基于jdk接口代理(假如没有接口类,会自动转换cglib代理)-->
    <aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>

 * 编写切面类(aspect)在切面类上使用注解

/**
 * @author xiaozhao
 */
@Component
@Aspect
public class SpeakAspect {

    @Pointcut("execution(* com.hx.hx02.service.SpeakServiceImpl.*(..))")
    public void pointcut(){
    }

    @Before("pointcut()")
    public void speakBefore(){
        System.out.println("---speakBefore---");
    }

    @After("pointcut()")
    public void speakAfter(){
        System.out.println("---speakAfter---");
    }

    @Around("pointcut()")
    public Object speakAround(ProceedingJoinPoint pjp) throws Throwable {
        Object obj=null;
        System.out.println("---around前---");
        try{
            // 执行目标方法
            obj = pjp.proceed();
        }catch (Throwable e){
            System.out.println("---around异常---");
            // 监听参数为true则抛出异常,为false则捕获并不抛出异常
            if(pjp.getArgs().length>0 && (Boolean) pjp.getArgs()[0]){
                throw e;
            }else{
                obj=null;
            }
        }
        System.out.println("---around后---");
        return obj;
    }

    @AfterReturning("pointcut()")
    public void speakAfterReturning(){
        System.out.println("speakAfeterReturning");
    }

    @AfterThrowing("pointcut()")
    public void speakAfterThrowing(){
        System.out.println("speakAfterThrowing");
    }
}


/**
 * @author xiaozhao
 */
public interface ISpeakService {
     void speakEnligh();
     void speak(String languge);
     void err(boolean isThrow);
}

@Service
public class SpeakServiceImpl implements ISpeakService {
    @Override
    public void speakEnligh() {
        System.out.println("speak English");
    }

    @Override
    public void speak(String languge) {
        System.out.println("speak "+languge);
    }

    @Override
    public void err(boolean isThrow) {
        System.out.println("error....");
        throw new RuntimeException("speak error");
    }
}

@Test
    public void test3(){
        speakService.speakEnligh();
    }

@Test
    public void test3(){
        speakService.speak("chinese");
    }

 @Test
    public void test3(){
        speakService.err(false);
    }

 @Test
    public void test3(){
        speakService.err(true);
    }

猜你喜欢

转载自blog.csdn.net/linzhaoliangyan/article/details/88687979