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
- 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()方法");
- }
- }
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
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()方法");
}
}
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>
后置通知是在业务方法执行完才执行的。还有最终通知
MyInterceptor.java
- package cn.itcast.service;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.AfterReturning;
- 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("前置通知");
- }
- @AfterReturning("anyMethod()")
- public void doAfterReturning() {
- System.out.println("后置通知");
- }
- @After("anyMethod()")
- public void doAfter() {
- System.out.println("最终通知");
- }
- }
package cn.itcast.service;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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("前置通知");
}
@AfterReturning("anyMethod()")
public void doAfterReturning() {
System.out.println("后置通知");
}
@After("anyMethod()")
public void doAfter() {
System.out.println("最终通知");
}
}
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");
- }
- }
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()方法
后置通知
最终通知
现在还剩下两种通知,例外通知和环绕通知,例外通知在什么时候起效果呢?看下之前AOP的拦截代码,在JDKProxyFactory.java里面
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {//环绕通知
- PersonServiceBean bean = (PersonServiceBean) this.targetObject;
- Object result = null;
- if(bean.getUser()!=null){
- //..... advice()-->前置通知
- try {
- result = method.invoke(targetObject, args);
- // afteradvice() -->后置通知
- } catch (RuntimeException e) {
- //exceptionadvice()--> 例外通知
- }finally{
- //finallyadvice(); -->最终通知
- }
- }
- return result;
- }
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {//环绕通知
PersonServiceBean bean = (PersonServiceBean) this.targetObject;
Object result = null;
if(bean.getUser()!=null){
//..... advice()-->前置通知
try {
result = method.invoke(targetObject, args);
// afteradvice() -->后置通知
} catch (RuntimeException e) {
//exceptionadvice()--> 例外通知
}finally{
//finallyadvice(); -->最终通知
}
}
return result;
}
可以看到,例外通知是在业务方法出现例外时执行的通知。它执行的时机是在捕获到例外之后,定义一个例外通知
MyInterceptor.java
- package cn.itcast.service;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.AfterThrowing;
- 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("前置通知");
- }
- @AfterReturning("anyMethod()")
- public void doAfterReturning() {
- System.out.println("后置通知");
- }
- @After("anyMethod()")
- public void doAfter() {
- System.out.println("最终通知");
- }
- @AfterThrowing("anyMethod()")
- public void doAfterThrowing() {
- System.out.println("例外通知");
- }
- }
package cn.itcast.service;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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("前置通知");
}
@AfterReturning("anyMethod()")
public void doAfterReturning() {
System.out.println("后置通知");
}
@After("anyMethod()")
public void doAfter() {
System.out.println("最终通知");
}
@AfterThrowing("anyMethod()")
public void doAfterThrowing() {
System.out.println("例外通知");
}
}
要应用例外通知的话,业务bean要做一下手脚
PersonServiceBean.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) {
- throw new RuntimeException("我爱例外。。");
- //System.out.println("我是save()方法");
- }
- public void update(String name, Integer id) {
- System.out.println("我是update()方法");
- }
- }
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) {
throw new RuntimeException("我爱例外。。");
//System.out.println("我是save()方法");
}
public void update(String name, Integer id) {
System.out.println("我是update()方法");
}
}
如果出现例外的话,后置通知是不会执行的;不管出不出例外,最终通知必然会执行
运行单元测试代码,控制台输出:
前置通知
最终通知
例外通知
在JUnit里面,出现了例外
java.lang.RuntimeException: 我爱例外。。
at cn.itcast.service.impl.PersonServiceBean.save(PersonServiceBean.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
.............................................
我的疑问:为什么例外通知会在最终通知后面的呢?不明白!
把例外注释掉,恢复原型
PersonServiceBean.java
- package cn.itcast.service.impl;
- import cn.itcast.service.PersonService;
- public class PersonServiceBean implements PersonService {
- public String getPersonName(Integer id) {
- System.out.println("我是getPersonName()方法");
- return "xxx";
- }
- public void save(String name) {
- //throw new RuntimeException("我爱例外。。");
- System.out.println("我是save()方法");
- }
- public void update(String name, Integer id) {
- System.out.println("我是update()方法");
- }
- }
package cn.itcast.service.impl;
import cn.itcast.service.PersonService;
public class PersonServiceBean implements PersonService {
public String getPersonName(Integer id) {
System.out.println("我是getPersonName()方法");
return "xxx";
}
public void save(String name) {
//throw new RuntimeException("我爱例外。。");
System.out.println("我是save()方法");
}
public void update(String name, Integer id) {
System.out.println("我是update()方法");
}
}
然后学习最后一种通知,环绕通知。 其实Struts2里面提供的拦截器,就是属于环绕通知。环绕通知在我们做权限的时候,被大量使用到
先看下环绕通知的定义:
- @Around("anyMethod()")//环绕通知
- public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
- return pjp.proceed();
- }
@Around("anyMethod()")//环绕通知
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
这方法需要有一个输入参数ProceedingJoinPoint pjp,这输入参数是固定的形式,固定的写法。
注意:如果使用了环绕通知的话,必须确保在环绕通知内部执行return pjp.proceed();这个方法,如果不执行这个方法的话,那么业务bean中被拦截的方法是不会执行的。当然在执行这个方法的时候,如果后面还有切面的话,它的执行顺序应该是这样的,先执行后面的切面,如果后面没有切面的话,再执行最终目标对象的业务方法。如果不调用pjp.proceed();这个方法,那么后面的切面及业务bean的方法都不会被执行,所以在这里就要小心了。
MyInterceptor.java
- package cn.itcast.service;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.AfterReturning;
- import org.aspectj.lang.annotation.AfterThrowing;
- import org.aspectj.lang.annotation.Around;
- 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("前置通知");
- }
- @AfterReturning("anyMethod()")
- public void doAfterReturning() {
- System.out.println("后置通知");
- }
- @After("anyMethod()")
- public void doAfter() {
- System.out.println("最终通知");
- }
- @AfterThrowing("anyMethod()")
- public void doAfterThrowing() {
- System.out.println("例外通知");
- }
- @Around("anyMethod()")//环绕通知
- public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
- //if(){ 这种应用场合下,特别适合做权限的,因为这里可以判断用户是否有权限
- System.out.println("进入方法");
- Object result = pjp.proceed();
- System.out.println("退出方法");
- //}
- return result;
- }
- }
package cn.itcast.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
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("前置通知");
}
@AfterReturning("anyMethod()")
public void doAfterReturning() {
System.out.println("后置通知");
}
@After("anyMethod()")
public void doAfter() {
System.out.println("最终通知");
}
@AfterThrowing("anyMethod()")
public void doAfterThrowing() {
System.out.println("例外通知");
}
@Around("anyMethod()")//环绕通知
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
//if(){ 这种应用场合下,特别适合做权限的,因为这里可以判断用户是否有权限
System.out.println("进入方法");
Object result = pjp.proceed();
System.out.println("退出方法");
//}
return result;
}
}
实际上,如果我们使用环绕通知的话,前面的前置通知,后置通知,例外通知,最终通知都可以实现,都可以人工去实现它.
因为我们完全可以在Object result = pjp.proceed();前执行前置通知的方法,在这句后执行后置的代码,出现例外的时候再执行例外通知的方法。。使用环绕通知完全可以实现这几种功能,而没必要再使用这几种通知类型。
看下环绕通知的应用是否能够成功,运行单元测试代码,控制台输出
前置通知
进入方法
我是save()方法
后置通知
最终通知
退出方法
成功。这里已经给大家介绍完了怎样去定义切面,定义切入点,怎样应用各种类型的通知。 这里,肯定会有一些同学想到一些问题:如果我需要得到输入参数的情况下,怎么办呢?好比在前置通知里面,我需要得到用户输入的数据,可以采用这种写法:
- @Before("anyMethod() && args(n)")
- /*
- 两个条件一合成,代表执行cn.itcast.service.impl.PersonServiceBean这个类里面的所有方法,
- 并且要求方法里面的参数只有一个,而且是String类型
- */
- public void doAccessCheck(String n) {
- System.out.println("前置通知:" + n);
- }
@Before("anyMethod() && args(n)")
/*
两个条件一合成,代表执行cn.itcast.service.impl.PersonServiceBean这个类里面的所有方法,
并且要求方法里面的参数只有一个,而且是String类型
*/
public void doAccessCheck(String n) {
System.out.println("前置通知:" + n);
}
现在呢,这个前置通知只针对PersonServiceBean.java里面的save(String name)方法,getPersonName(Integer id)方法和update(String name, Integer id)方法对前置通知而言,它都不会被拦截到。
执行单元测试代码,控制台输出:
前置通知:xx
进入方法
我是save()方法
后置通知
最终通知
退出方法
看到,前置通知把xxx打印出来了,xxx是输入参数n,说明我们获取到输入参数了
有同学还会问:我要获取返回结果,那该怎么办呢? 比如,要获得getPersonName(Integer id)的返回结果,那该怎么办呢?
代码应该这样写:
- @AfterReturning(pointcut="anyMethod()", returning="rsl")
- public void doAfterReturning(String rsl) {
- System.out.println("后置通知:" + rsl);
- }
@AfterReturning(pointcut="anyMethod()", returning="rsl")
public void doAfterReturning(String rsl) {
System.out.println("后置通知:" + rsl);
}
这样使用的话,就相当于在"execution (* cn.itcast.service.impl.PersonServiceBean.*(..))"这里的第一个*号,申明返回类型是String,如果使用了returning属性,会把返回结果作为输入参数传给doAfterReturning方法.
客户端代码更换一下,
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.getPersonName(2);
- }
- }
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.getPersonName(2);
}
}
运行单元测试代码,控制台输出:
进入方法
我是getPersonName()方法
后置通知:xxx
最终通知
退出方法
证明,返回结果我们是得到了。
有同学还会问:在出现例外的时候,能不能得到业务方法抛出的例外呢?也是可以的。可以这样做:
- @AfterThrowing(pointcut="anyMethod()", throwing="e")
- public void doAfterThrowing(Exception e) {
- System.out.println("例外通知:" + e);
- }
@AfterThrowing(pointcut="anyMethod()", throwing="e")
public void doAfterThrowing(Exception e) {
System.out.println("例外通知:" + e);
}
把例外抛出:
PersonServiceBean.java
- package cn.itcast.service.impl;
- import cn.itcast.service.PersonService;
- public class PersonServiceBean implements PersonService {
- public String getPersonName(Integer id) {
- System.out.println("我是getPersonName()方法");
- return "xxx";
- }
- public void save(String name) {
- throw new RuntimeException("我爱例外。。");
- //System.out.println("我是save()方法");
- }
- public void update(String name, Integer id) {
- System.out.println("我是update()方法");
- }
- }
package cn.itcast.service.impl;
import cn.itcast.service.PersonService;
public class PersonServiceBean implements PersonService {
public String getPersonName(Integer id) {
System.out.println("我是getPersonName()方法");
return "xxx";
}
public void save(String name) {
throw new RuntimeException("我爱例外。。");
//System.out.println("我是save()方法");
}
public void update(String name, Integer id) {
System.out.println("我是update()方法");
}
}
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("tertret");
- }
- }
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("tertret");
}
}
运行单元测试代码,控制台输出:
前置通知:tertret
进入方法
最终通知
例外通知:java.lang.RuntimeException: 我爱例外。。
可以看到,例外是得到了。
这些就是我们在开发时,怎样得到输入参数,返回结果,抛出的异常,通过这些方式我们都可以得到。
基于注解的方式使用切面,已经给大家介绍完了。