Spring学习-面向切面编程(AOP)

Spring-面向切面编程(AOP)

1. 切面的概念

横切关注点,可以被模块化的特殊类.

白话:定义了我要在程序执行时,在指定地方执行我编写的代码.

2. AOP术语

  • 通知(Advice): 切面的工作. 就是"什么时候做什么"
    • 五种类型: 前置通知(Before) , 后置通知(After),返回通知(After-returning),异常通知(After-throwing),环绕通知(Around)
  • 连接点(Join Point): 执行程序的时候所有可以插入切面的点
  • 切点(Point Cut): 就是 “什么地方”,用来规定通知执行的范围
  • 切面(Aspect): 就是通知+切点的结合.共通定义了切面的全部内容
  • 引入(Introduction):引入允许我们想现有的类添加新方法和属性,
  • 织入(Weaving): 把切面应用到目标对象并创建新的代理对象的过程. 切面在指定的连接点被织入到目标对象.

3. 白话理解

定义一个切面.定义了我要在某个地方执行时,执行我编写的切面的代码.

所以我需要定义一个AOP类:
1. 我需要它声明为切面类(用@Aspect注解放在类上实现)
2. 我需要指定切面做什么类型的事情和做什么事情.(五种通知和业务代码)
3. 我需要定义一个切点,用来指定我在什么地方执行切面类的内容(切点)
4. 我需要在配置我的工程,让切面可以被认识.(@Component<aop:aspectj-autoproxy/>)

4. 简单的代码实现

  • 需要的依赖

    <!-- spring-aspects中依赖的aspectjweaver是aspectj的织入包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    
    <!-- 在我测试的时候,以下依赖不是必要的 --> 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
    </dependency>
    <!-- aspectjrt包是aspectj的runtime包 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
    </dependency>
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    
    
  • 切面类

    @Aspect  //声明切面
    @Component //加入容器
    public class LogAspect {
    
        protected static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    
        //定义一个切点(写法一)
        @Pointcut("execution(public * com.min.spring.service..*(..))")
        public void pt1(){}
    
        //通知中指定上面定义的切点
        @Before("pt1()")
        public void start(JoinPoint joinPoint) {
            logger.info("service start====" + joinPoint.getSignature().getName());
        }
    
        @After("pt1()")
        public void after(JoinPoint joinPoint) {
            logger.info("service after====" + joinPoint.getSignature().getName());
        }
    
        //直接在通知中写明起点,不需要专门定义切点(写法二)
        @Before("execution(public * com.min.spring.controller..*(..))")
        public void cstart(JoinPoint joinPoint) {
            logger.info("controller start====" + joinPoint.getSignature().getName());
        }
    
        @After("execution(public * com.min.spring.controller..*(..))")
        public void cafter(JoinPoint joinPoint) {
            logger.info("controller after====" + joinPoint.getSignature().getName());
        }
    }
    
  • 配置xml (spring-context.xmlspring-mvc.xml中都要加上启用aspectJ自动代理的标记)

    <aop:aspectj-autoproxy/>
    
  • 执行结果

    2019-08-14 17:45:06,345 8602   [nio-8080-exec-7] INFO  om.min.spring.aspect.LogAspect  - controller start====login
    2019-08-14 17:45:06,346 8603   [nio-8080-exec-7] INFO  om.min.spring.aspect.LogAspect  - service start====login
    2019-08-14 17:45:06,346 8603   [nio-8080-exec-7] INFO  om.min.spring.aspect.LogAspect  - service start====findByUsername
    2019-08-14 17:45:06,517 8774   [nio-8080-exec-7] INFO  om.min.spring.aspect.LogAspect  - service after====findByUsername
    2019-08-14 17:45:06,518 8775   [nio-8080-exec-7] INFO  om.min.spring.aspect.LogAspect  - service after====login
    2019-08-14 17:45:06,518 8775   [nio-8080-exec-7] INFO  om.min.spring.aspect.LogAspect  - controller after====login
    2019-08-14 17:45:06,540 8797   [nio-8080-exec-8] INFO  om.min.spring.aspect.LogAspect  - controller start====index
    2019-08-14 17:45:06,548 8805   [nio-8080-exec-8] INFO  om.min.spring.aspect.LogAspect  - controller after====index
    

5. 切点(PointCut)的常见写法

//任意公共方法的执行:
execution(public * *..))

//任何一个名字以 set 开始的方法的执行:
execution(* set*..))

//AccountService 接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))

//在 service 包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))

//在 service 包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))

//在 service 包中的任意连接点(在 Spring AOP 中只是方法执行):
within(com.xyz.service.*)

//在 service 包或其子包中的任意连接点(在 Spring AOP 中只是方法执行):
within(com.xyz.service..*)

//实现 AccountService 接口的代理对象的任意连接点 (在 Spring AOP 中只是方法执行):
this(com.xyz.service.AccountService)

//实现 AccountService 接口的目标对象的任意连接点 (在 Spring AOP 中只是方法执行):
target(com.xyz.service.AccountService)

//任何一个只接受一个参数,并且运行时所传入的参数是 Serializable 接口的连接点(在 Spring AOP 中只是方法执行):
args(java.io.Serializable)
//请注意在例子中给出的切入点不同于execution(* *(Java.io.Serializable)),args 版本只有在动态运行时候传入参数是 Serializable 时才匹配,而 execution 版本在方法签名中声明只有一个 Serializable 类型的参数时候匹配。

//目标对象中有一个 @Transactional 注解的任意连接点 (在 Spring AOP 中只是方法执行):
@target(org.springframework.transaction.annotation.Transactional)

//任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在 Spring AOP 中只是方法执行):
@within(org.springframework.transaction.annotation.Transactional)

任何一个执行的方法有一个 @Transactional 注解的连接点 (在 Spring AOP 中只是方法执行):
@annotation(org.springframework.transaction.annotation.Transactional)

//任何一个只接受一个参数,并且运行时所传入的参数类型具有 @Classified 注解的连接点(在 Spring AOP 中只是方法执行):
@args(com.xyz.security.Classified)

任何一个在名为 tradeService 的 Spring bean 之上的连接点 (在 Spring AOP 中只是方法执行):
bean(tradeService)

//任何一个在名字匹配通配符表达式*Service的 Spring bean 之上的连接点 (在 Spring AOP 中只是方法执行):
bean(*Service)

//其中,this、tagart、args、 @target、 @with、 @annotation和@args在绑定表单中更加常用

常见问题

1. SpringMVC容器和Spring容器父子关系导致Aspect不起作用

描述:

  • Aspect中定义的切点包括了Controller层和Service层
  • 有父子容器存在SpringMVC容器和Spring容器
  • 定义的Aspect类的对象放在了父容器
  • 启用aspectJ自动代理只放在了spring-context.xmlspring-mvc.xml
  • 切面执行的时候只有部分有效果或者没效果

解决方案一:

spring-context.xmlspring-mvc.xml中都要加上启用aspectJ自动代理的标记

    <!--启用aspectJ自动代理-->
    <aop:aspectj-autoproxy/>

解决方案二:

spring-context.xmlspring-mvc.xml中不要分包扫描,

 <context:component-scan base-package="com.min.spring">
 </context:component-scan>

原因:

1.SpringMVC容器和Spring容器的影响

在一般的项目配置web.xml中,Spring容器是由ContextLoaderListener生成,而SpringMVC容器是由DispatcherServlet来加载, Spring容器是父容器, SpringMVC容器是子容器.

子容器(SpringMVC容器)可以访问父容器(Spring容器)的内容,反之则不行

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-context*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

<!--配置 Spring 的 Servlet 分发器处理所有 HTTP 的请求和响应-->
    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:/spring-mvc*.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

在扫描包的时候父容器(Spring容器)负责扫描@Controller以外的注解,

子容器(SpringMVC容器)负责只扫描@Controller注解.

spring-context.xml

 <context:component-scan base-package="com.min.spring">
     <context:exclude-filter type="annotation"
                             expression="org.springframework.stereotype.Controller"/>
 </context:component-scan>

spring-mvc.xml

<!-- 使用 Annotation 自动注册 Bean,只扫描 @Controller use-default-filters必需设置为false-->
<context:component-scan base-package="com.min.spring"
                        use-default-filters="false">
    <context:include-filter type="annotation"
                            expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

如果你写的切面既有Controller的,又有Service层的时候, 在父容器和子容器中都需要加上启用aspectJ自动代理

如果在只在一个xml中的时候,产生的自动代理只是对应容器的的部分.

所以要在2个容器的xml中都配置启动aspectJ自动代理.

spring-context.xmlspring-mvc.xml

    <!--启用aspectJ自动代理-->
    <aop:aspectj-autoproxy/>

如果在只在一个xml中的时候,产生的自动代理只是对应容器的的部分.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值