当前位置: 首页 > news >正文

再学习之Spring(面向切面编程).

一、概念

1、理论

    把横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或 组成(delegation)。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用组成可能需要对委托对象进行复杂的调用。切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。Spring AOP 基于动态代理,所以Spring只支持方法连接点,这与一些其他的AOP框架是不同的,例如AspectJJBoss,除了方法切点,它们还提供了字段和构造器接入点。 

2、AOP术语

横切关注点(cross-cutuing concern):散布在应用中多处的功能。

切面(aspect) : 横切关注点模块化为特殊的类。切面是通知和切点的结合。

通知(advice):定义了切面是什么以及何时使用。

Spring切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

切点(pointcut):定义了切面在何处调用,会匹配通知所要织入的一个或多个连接点。
连接点(join point):在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。

织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

织入有三种方式可以实现,Spring采用的是第三种,在运行期织入的:

编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。SpringAOP就是以这种方式织入切面的。

3、AspectJ的切点表达式语言

 

 

注意:只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说execution指示器是我们在编写切点定义时最主要使用的指示器 。同时需要注意的是, 表达式之间允许用 &&(and)、||(or)、!(not) 来匹配复杂的被通知类。除了上面罗列的表达式外,Spring 还提供了一个Bean 表达式来匹配 Bean 的id,例如  execution(* com.service.Performance.perform(..)) && bean(performance)

@args的正确用法:自定义一个ElementType.TYPE的注解,这个注解用来修饰自定义类型(比如自己写的一个类),一个方法以这个自定义的类的实例为参数且只能有这唯一一参数,那这个方法在调用时会被匹配@args(自定义注解)的切面拦截。
@annotation的正确用法:在切面类上用@annotation加自定义注解就可以拦截使用这个注解的方法。比如匹配 @RequestMapping  注解的类  @annotation(org.springframework.web.bind.annotation.RequestMapping)

@target (cn.javass.spring.chapter6.Secure)  任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。

二、使用注解创建切面

1、添加pom.xml依赖

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.11</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.11</version>
        </dependency>
View Code

2、定义切面

@Aspect //表示这是一个切面类
public class Audience {

    //使用简明的PointCut
    @Pointcut("execution(* com.service.Performance.perform(..))")
    public void performance(){}
    
    //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
    @Before("performance()")
    public void  silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
    @Before("performance()")
    public void takeSeats(){
        System.out.println("Taking seats");
    }

    //方法调用结束通知(并不是指返回值通知,即使是void的返回值,仍然会触发通知) 即 @AfterReturning("execution(* com.service.Performance.perform(..))")
    @AfterReturning("performance()")
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }

    //有异常抛出的时候通知,即 @AfterThrowing("execution(* com.service.Performance.perform(..))")
    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}
View Code

3、启用AspectJ注解的自动代理 

有两种方式可以启用AspectJ 注解的自动代理:

(1)在 Java 配置文件中显示配置

@Configuration
@EnableAspectJAutoProxy //启用Aop自动代理
public class JavaConfig {
    @Bean
    public Audience getAudience(){
        return new Audience();
    }
}
View Code

(2)在XML文件中配置

    <!--启用AspectJ自动代理-->
    <aop:aspectj-autoproxy/>
    <bean id="audience" class="com.aspect.Audience"/>
View Code

不管你是使用JavaConfig还是XMLAspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。当程序执行到连接点的时候,就会由代理转到切面触发相应的通知。

4、创建环绕通知

@Aspect
public class Audience3 {

    @Pointcut("execution(* com.service.Performance.perform(..))")
    public void performance(){}

    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinPoint) {
        System.out.println("Silencing cell phones");
        System.out.println("Taking seats");
        try {
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
            throwable.printStackTrace();
        }
    }
}
View Code

 注意 ProceedingJoinPoint 作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPointproceed()方法。 

5、切面匹配输入参数

@Aspect
public class TrackCounter {
    
    private Map<Integer,Integer> trackCounts=new HashMap<Integer, Integer>();

    //@Pointcut("execution(* com.service.CompactDisc.playTrack(int)) && args(trackNumber)") //带入输入参数
    //@Pointcut("target(com.service.CompactDisc) && args(trackNumber)") // target 匹配目标对象(非AOP对象)为指定类型
    //@Pointcut("within(com.service..*) && args(trackNumber)") //com.service 包以及子包下的所有方法都执行
    //@Pointcut("within(com.service..CompactDisc+) && args(trackNumber)") //com.service 包的CompactDisc类型以及子类型
     @Pointcut("this(com.service.CompactDisc) && args(trackNumber)") //匹配当前AOP代理对象类型,必须是类型全称,不支持通配符
     public void trackPlayed(int trackNumber){}

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber){
        int playCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber,playCount+1);
        System.out.println(trackCounts.toString());
    }

    public int getPlayCount(int trackNumber){
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}
View Code
 参数的配置可以用占位符 *.. 
  * 的意思是任意类型任意名称的一个参数
 .. 的意思是任意类型,任意多个参数,并且只能放到args的后面。 

6、利用切面注入新功能

    Java并不是动态语言。一旦类编译完成了,我们就很难为该类添加新的功能了。但是,我们的切面编程却可以做到动态的添加方法...话虽如此,其实也不过是障眼法罢了。实际上,面向切面编程,不过是把方法添加到切面代理中,当要对添加的方法调用的时候,可以把被通知的 Bean 转换成相应的接口。也就是代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中。(说实话,想了半天,实在想不到这个功能有什么作用......)

(1) 重新定义一个接口和实现类

public interface Encoreable {
    void performEncode();
}
View Code
public class DefaultEncoreable implements Encoreable {
    
    public void performEncode() {
        System.out.println("this is DefaultEncoreable");
    }
}
View Code

(2) 把接口实现类嵌入到目标类代理中

@Aspect
public class EncoreableIntroducer {
    
    @DeclareParents(value = "com.service.CompactDisc+",
              defaultImpl = DefaultEncoreable.class) //value 表示要嵌入哪些目标类的代理 。 defaultImpl:表示要嵌入的接口的默认实现方法
    public static Encoreable encoreable;
}
View Code

(3) JUnit 测试

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

    @Autowired
    private CompactDisc compactDisc;
    
    @Test
    public void test02(){
        compactDisc.playTrack(123);
        Encoreable compactDisc = (Encoreable) this.compactDisc; //当要调用添加的新功能的时候,这个用法相当于由代理转换到对应类实现,不会报类型转换错误
        compactDisc.performEncode();

    }
}
View Code

 

三、使用XML声明切面

 

1、定义切面

public class AudienceXML {
    
    public void  silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    public void takeSeats(){
        System.out.println("Taking seats");
    }
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}
View Code

2、XML配置切面

    <aop:config>
        <aop:aspect ref="audienceXML">
            <aop:pointcut id="performance" expression="execution(* com.service.Performance.perform(..))"/>
            <aop:before method="silenceCellPhones" pointcut-ref="performance"/>
            <aop:before method="takeSeats" pointcut-ref="performance"/>
            <aop:after-returning method="applause" pointcut-ref="performance"/>
            <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>
View Code

3、创建环绕通知

public class Audience3XML {
    
    public void watchPerformance(ProceedingJoinPoint joinPoint) {
        System.out.println("Silencing cell phones");
        System.out.println("Taking seats");
        try {
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
            throwable.printStackTrace();
        }
    }
}
View Code
    <aop:config>
        <aop:aspect ref="audience3XML">
            <aop:pointcut id="performance3" expression="execution(* com.service.Performance.perform(..))"/>
            <aop:around method="watchPerformance" pointcut-ref="performance3"/>
        </aop:aspect>
    </aop:config>
View Code

4、匹配输入参数

    <aop:config>
        <aop:aspect ref="trackCounter">
            <aop:pointcut id="trackPlayed" expression="execution(* com.service.CompactDisc.playTrack(int)) and args(trackNumber)"/>
            <aop:before method="countTrack" pointcut-ref="trackPlayed"/>
        </aop:aspect>
    </aop:config>
View Code

5、注入新功能

    <aop:config>
        <aop:aspect>
            <aop:declare-parents types-matching="com.service.CompactDisc+"
                                 implement-interface="com.service.Encoreable"
                                 default-impl="com.service.impl.DefaultEncoreable"
                                 delegate-ref="encoreableDelegate"/>

        </aop:aspect>
    </aop:config>
View Code

转载于:https://www.cnblogs.com/jmcui/p/8031259.html

相关文章:

  • java:解决eclipse配置Tomcat时找不到server选项
  • Qt532.QSettings_默认分隔符
  • python 高阶函数三 filter()和sorted()
  • bzoj1036[ZJOI2008]树的统计Count 树链剖分+线段树
  • 自然语言处理入门学习(一)
  • 文件和目录权限chmod 更改所有者和所属组chown umask 隐藏权限lsattr/chattr
  • 面向对象java知识汇总题
  • PAT 1023.组个最小数
  • mongodb for windows安装
  • 【比赛】NOIP2017 宝藏
  • gdb调试多线程程序总结
  • Excel2016通过宏生成拼音码
  • Web离线应用解决方案——ServiceWorker
  • am335x SPI spi_d0, spi_d1 out, in 模式设定
  • spring+activemq实战之配置监听多队列实现不同队列消息消费
  • 《深入 React 技术栈》
  • 【MySQL经典案例分析】 Waiting for table metadata lock
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • co模块的前端实现
  • java多线程
  • LeetCode18.四数之和 JavaScript
  • PHP 7 修改了什么呢 -- 2
  • php中curl和soap方式请求服务超时问题
  • Spring核心 Bean的高级装配
  • Webpack 4 学习01(基础配置)
  • 聚类分析——Kmeans
  • 坑!为什么View.startAnimation不起作用?
  • 力扣(LeetCode)21
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 微信小程序:实现悬浮返回和分享按钮
  • 用jQuery怎么做到前后端分离
  • 做一名精致的JavaScripter 01:JavaScript简介
  • Java总结 - String - 这篇请使劲喷我
  • Nginx实现动静分离
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • #if和#ifdef区别
  • $refs 、$nextTic、动态组件、name的使用
  • (+4)2.2UML建模图
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (二)windows配置JDK环境
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (论文阅读40-45)图像描述1
  • (四)库存超卖案例实战——优化redis分布式锁
  • (新)网络工程师考点串讲与真题详解
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (转) Face-Resources
  • (转)EXC_BREAKPOINT僵尸错误
  • .“空心村”成因分析及解决对策122344
  • .apk 成为历史!
  • .bat批处理(六):替换字符串中匹配的子串
  • .bat批处理(七):PC端从手机内复制文件到本地