来学习采用Spring框架如何进行面向切面(AOP)编程
使用Spring进行面向切面(AOP)编程
-------------------------------------------------------------
要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
</beans>
如果要使用切面编程(AOP),还需要下列jar文件
lib/aspectj/aspectjweaver.jar和aspectjrt.jar
lib/cglib/cglib-nodep-2.1_3.jar
我们使用Spring框架进行AOP编程的时候呢,Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:
1.基于XML配置方式进行AOP开发。
2.基于注解方式进行AOP开发。
对这两种方式都进行下介绍,先学习基于注解方式进行AOP开发
先是beans.xml里,要引入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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<aop:aspectj-autoproxy/>
<!--
打开配置项,这个配置项是对@Aspectj这个注解进行支持
前面已经说过了,注解本身是不能干活的,注解之所以能干活是因为后面有处理器对其进行处理
这个配置相当于为我们将要使用的@Aspectj注解提供了解析的功能
-->
</beans>
接下来的工作是,定义一个切面,在切面里面定义切入点,所谓切入点就是说我们要对业务bean里面的哪些方法进行拦截,我们还要定义通知,所谓通知,就是说我们拦截到方法后我们需要做的工作。
新建业务bean用作测试,PersonService.java
package cn.itcast.service;
public interface PersonService {
public void save(String name);
public void update(String name, Integer id);
public String getPersonName(Integer id);
}
PersonServiceBean.java
Java代码
package cn.itcast.service.impl;
import cn.itcast.service.PersonService;
public class PersonServiceBean implements PersonService {
public String getPersonName(Integer id) {
return "xxx";
}
public void save(String name) {
System.out.println("我是save()方法");
}
public void update(String name, Integer id) {
System.out.println("我是update()方法");
}
}
用作测试用的业务bean已经开发好了,接着就要进行AOP的开发,因为我们采用的是基于注解的方式来实现AOP功能,先了解一些概念
基于注解方式声明切面
------------------------------------------------------------------------------
@Aspect
public class LogPrint {
@Pointcut("execution(* cn.itcast.service..*.*(..))")
private void anyMethod() {}//声明一个切入点
@Before("anyMethod() && args(userName)")//定义前置通知
public void doAccessCheck(String userName) {
}
@AfterReturning(pointcut="anyMethod()",returning="revalue")//定义后置通知
public void doReturnCheck(String revalue) {
}
@AfterThrowing(pointcut="anyMethod()", throwing="ex")//定义例外通知
public void doExceptionAction(Exception ex) {
}
@After("anyMethod()")//定义最终通知
public void doReleaseAction() {
}
@Around("anyMethod()")//环绕通知
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}
在这里@Pointcut("execution(* cn.itcast.service..*.*(..))")
private void anyMethod() {}//声明一个切入点
切入点用来定义我们要拦截的方法,因为Spring只支持方法的拦截,所以这里说的是拦截方法
切入点的定义里面,使用了一个AOP的表达式语言@Pointcut("execution(* cn.itcast.service..*.*(..))"),这个语言是比较灵活的,功能也是比较强大的,在实际使用中呢几乎80%多我们使用的表达式都是类似这种写法,给大家介绍下切入点的表达式的含义。
execution代表执行,执行业务方法的时候我要进行拦截;
第一个*号代表的是返回值的类型,通配符*号表示的话就代表任何的返回值类型;
cn.itcast.service代表的是包名,也就是说你要对哪些包底下的类进行拦截;
然后是两个点..,这两个点代表对cn.itcast.service的子包底下的类也要进行拦截,如果不定义这两个点的话,那么它只会对service包底下的类进行拦截,如果定义了两个点,那么即会对service包底下的类进行拦截,也会对service子包底下的类进行拦截;
第二个*号代表的是类,你要对哪个类进行拦截,*号代表所有类,也就是说service子包底下的所有类进行拦截
第三个*号代表方法,代表所有的方法;
最后的括号里面有两个点(..),这两个点代表方法的参数可以任意,有可以,没有也可以。一个也可以,多个也可以,代表随意
MyInterceptor.java
package cn.itcast.service;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 切面
*/
@Aspect //用来指明这个类是一个切面
public class MyInterceptor {
@Pointcut("execution (* cn.itcast.service.impl.PersonServiceBean.*(..))")
//切入点的表达式写完了,接下来要为切入点定义一个名称,这个名称的定义有点怪怪的
//采用的是方法定义的形式来定义它的名称的
private void anyMethod() {}//声明一个切入点,anyMethod()就是切入点的名称,括号也带上
@Before("anyMethod()") //里面应该填入切入点的名称
public void doAccessCheck() {
System.out.println("前置通知");
}
}
这样切面就定义好了,在切面里面有我们关注的切入点,并且有我们关注的通知,我们的代码很简洁,当我们定义好切面之后就要注意了,要把MyInterceptor这个切面交给Spring容器管理,如果你不交给Spring管理的话,你定义完了它也不会起效果。
要交给Spring管理的话,有两种方式,一种方式是在配置文件里面通过<bean>元素把它交给Spring管理,还有种方式是通过扫描的方式,加一个注解@Component在public class MyInterceptor{上面的方式把这个bean交给Spring管理。现在并没有开启自动扫描的方式,所以这里就采用基于XML配置的方式把bean交给Spring管理
beans.xml
<?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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<aop:aspectj-autoproxy/>
<!--
打开配置项,这个配置项是对@Aspectj这个注解进行支持
前面已经说过了,注解本身是不能干活的,注解之所以能干活是因为后面有处理器对其进行处理
这个配置相当于为我们将要使用的@Aspectj注解提供了解析的功能
-->
<bean id="myInterceptor" class="cn.itcast.service.MyInterceptor"/>
<bean id="personService" class="cn.itcast.service.impl.PersonServiceBean"></bean>
</beans>
现在就来开发客户端,在客户端里面我们调用PersonServiceBean这个业务bean的save方法,看一下MyInterceptor这个切面能否帮我们拦截到这个方法,然后在拦截到之前先执行前置通知代码,然后再执行业务方法save()
新建一个单元测试
SpringAOPTest.java
package junit.test;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.itcast.service.PersonService;
public class SpringAOPTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@Test public void interceptorTest(){
ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");
PersonService personService = (PersonService)cxt.getBean("personService");
personService.save("xx");
}
}
运行单元测试代码,控制台输出
前置通知
我是save()方法
说明,先执行前置通知,在执行业务方法,前置通知起效果了。