当前位置:主页 > 软件编程 > JAVA代码 >

详解spring面向切面aop拦截器

时间:2020-10-20 13:19:01 | 栏目:JAVA代码 | 点击:

spring中有很多概念和名词,其中有一些名字不同,但是从功能上来看总感觉是那么的相似,比如过滤器、拦截器、aop等。
过滤器filter、spring mvc拦截器Interceptor 、面向切面编程aop,实际上都具有一定的拦截作用,都是拦截住某一个面,然后进行一定的处理。

在这里主要想着手的是aop,至于他们的比较,我想等三个都一一了解完了再说,因此这里便不做过多的比较。

在我目前的项目实践中,只在一个地方手动显示的使用了aop,那便是日志管理中对部分重要操作的记录。

据我目前所知,aop拦截一般都是用在具体的方法上,或者说是具体的某一类方法,我所用过的实现方式有两种,一种是直接代码声明,一种是在xml文件中配置。

由于我目前实际开发的项目都是使用spring+spring mvc的架构,然后使用maven管理,然后junit测试。因此我自己几乎所有的个人项目也都是采用这些架构和项目管理工具,在这个理解aop的小项目中,自然也是这样,依赖包如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>springTest</groupId>
 <artifactId>aopTest</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.0.3.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.0.3.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.0.3.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.4</version>
  </dependency>
 </dependencies>
</project>

第一种方式,Java代码声明:

这里实例中,我要声明一个aop来拦截dao层中的get开头的所有方法,首先建一个dao以及简单的额imp实现:

dao接口如下:

package com.ck.aopTest.dao;
import com.ck.aopTest.model.UserModel;

public interface MyAopDao {
  public void getUser();
  public void getName(UserModel user);
  public void addUser();
}

简单的实现:

package com.ck.aopTest.dao.impl;
import org.springframework.stereotype.Repository;
import com.ck.aopTest.dao.MyAopDao;
import com.ck.aopTest.model.UserModel;

@Repository
public class MyAopDaoImpl implements MyAopDao {

  @Override
  public void getUser() {
    System.out.println("这是我的aop测试dao方法一");
  }
  @Override
  public void getName(UserModel userModel) {
    System.out.println("这是我的aop测试dao方法二");
  }
  @Override
  public void addUser() {
    System.out.println("这是我的aop测试dao方法三");
  }
}

然后声明一个aop:

package com.ck.aopTest.aop;
import java.util.Date;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import com.ck.aopTest.model.UserModel;

@Aspect
@Component
public class MyAop {

  @Pointcut("execution(public * com.ck.aopTest.dao.impl.*.get*(..))")
  private void aopTest() {

  }
  @Before("aopTest()")
  public void before() {
    System.out.println("调用dao方法前拦截" + new Date().getTime());
  }
  @After("aopTest()" + "&&args(user)")
  public void after(UserModel user) {
    System.out.println(user.getName());
    System.out.println("调用dao方法之后拦截" + new Date().getTime());
  }
  @Around("aopTest()")
  public void around(ProceedingJoinPoint pdj) {
    System.out.println("调用dao之前的环绕拦截" + new Date().getTime());
    try {
      pdj.proceed();
    } catch (Throwable e) {
      e.printStackTrace();
    }
    System.out.println("调用dao之后的环绕拦截" + new Date().getTime());
  }
}

上述代码便是用java声明aop的核心代码,其中注解@Aspect的作用的就是告诉spring这是一个aop类,然后@Component就不用多说了,告诉spring这是一个需要扫描的类。

再往下,@Pointcut(“execution(public * com.ck.aopTest.dao.impl..get(..))”)正式声明需要拦截的切面,@Pointcut以及后边的额execution是固定的写法,execution后括号内的内容便是具体的切面,这里的意思是拦截所有public的任何返回值或者void的、命名空间是com.ck.aopTest.dao.impl下边的所有的类中的所有get开头的拥有任意多个参数的方法。

简单点说也就是当任何调用了com.ck.aopTest.dao.impl这个包中任何类中的get开头的方法,便会激活这个aop。

而紧接着上边这一段,我们看到了一个private void aopTest() 空的方法,实际上这个方法的作用是为这个aop切面声明一个名字,便于使用,也便于在多个aop切面时正常区分。

再后边的@Before、@After、@Around便是三个可选拦截方式,见名之意,分别是在上边声明的切面指明的方法调用之前执行、调用之后执行、以及环绕执行,调用之前和调用之后比较好理解,环绕的意思是在调用之前和之后都执行一定的逻辑。
从代码中可以看出,pdj.proceed();之前和之后各打印了两行数据,pdj.proceed();就代表了继续执行,如果是了解filter的应该很容易想到这个方法实际上和chain.doFilter很像,可以理解成放行。

在这三个注解之后需要指定要使用的切面,即@Pointcut声明的切面,指定名称就行。

从代码中可以看到有一个地方后边加了“&&args(UserModel user)”,意思是指定形参,也就是说指定的切面中有效方法的参数,例如上边dao中的getName方法有一个UserModel类型的参数,这里便可以使用。

主要代码写好了,接下来还有个必不可少的步骤,既然是spring项目,是spring的aop,那么自然需要配置spring文件,指明需要spring管理的包:

<?xml version="1.0" encoding="UTF-8"?> 
<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" " 
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 
  <context:component-scan base-package="com.ck.aopTest.*" />
</beans>

为了验证这里的aop是否真的有效,我写了一个junit测试:

package com.ck.aopTest.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ck.aopTest.dao.MyAopDao;
import com.ck.aopTest.model.UserModel;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class AopTest {
  @Autowired
  private MyAopDao myAopDao;

  @Test
  public void aopTest2() {
    UserModel user = new UserModel();
    myAopDao.getName(user);
  }
}

按理说,这里运行测试方法后应该打印出很多条输出,但是遗憾的是结果只是打印出了dao中的一条输出,原因是spring配置中并没有启用aop,正确的配置应该是下边这样:

<?xml version="1.0" encoding="UTF-8"?> 
<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-3.0.xsd 
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 

  <aop:aspectj-autoproxy />
  <context:component-scan base-package="com.ck.aopTest.*" />  

</beans>

我们需要再文件头加入

xmlns:aop=”http://www.springframework.org/schema/aop”

以及http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
除此之外,还要启用aop:

<aop:aspectj-autoproxy />

再次运行测试方法会看到控制台如下:

由此证明这个aop是有效的。

第二种方式,配置文件配置:

同样的,这里还是用之前的dao以及对应的impl,因此这段代码便不再重复,不一样的是具体的aop类如下:

package com.ck.aopTest.aop;
public class MyAop2 {
  public void before2() {
    System.out.println("这是我的使用注解的aop,调用dao之前拦截");
  }
}

可以看到这个类实际上也是极致简单,普通类,普通方法,没有任何特别,然后我们要做的是在spring中配置:

<?xml version="1.0" encoding="UTF-8"?> 
<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-3.0.xsd 
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> 

  <aop:aspectj-autoproxy />
  <bean id="myAop2" class="com.ck.aopTest.aop.MyAop2"></bean>
  <aop:config>
   <aop:pointcut expression="execution(public * com.ck.aopTest.dao.impl.*.add*(..))" id="aopTest1"/>
   <aop:aspect id="myAopTest2" ref="myAop2">
    <aop:before method="before2" pointcut-ref="aopTest1"/>
   </aop:aspect>
  </aop:config>
  <context:component-scan base-package="com.ck.aopTest.*" />

</beans>

至于这里配置内容的具体解释,我想通过我对第一种方式的解释后,也没有太大必要再说,稍微一对比就会一清二楚。

同样的,这里只演示了before,至于after和round的配置应该也很容易就可以根据before推理出来。

您可能感兴趣的文章:

相关文章