AOP简单实例
原理图:
1. 功能需求: 在调用service1之前完成写日志功能[前置通知]
2. 工程结构:
TestServiceInter .java
package com.itcast.aop.service;
// 接口1
public interface TestServiceInter {
// 目标方法
public void sayHello();
}
Test1Service.java
package com.itcast.aop.service;
// 要被写日志的service
public class Test1Service implements TestServiceInter {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 目标方法实现
@Override
public void sayHello() {
System.out.println("Hello, " + this.name);
}
}
MyMethodBeforeAdvice.java
package com.itcast.aop.service;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
// 前置通知[在目标方法之前执行]
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/**
* method: 目标方法[即sayHello()]
* args: 目标方法的参数[即sayHello(参数1, 参数2, 参数3, ...)]
* target: 被代理的对象[接口实现类, 即Test1Service类]
*/
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
for (Object arg: args) {
System.out.println(arg);
}
System.out.println("在这里写日志..." + method + " | " + args + " | " + target.getClass().getName());
}
}
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"
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">
<!-- 配置被代理对象 -->
<bean id="test1Service" class="com.itcast.aop.service.Test1Service">
<property name="name" value="张三" />
</bean>
<!-- 配置前置通知service -->
<bean id="myMethodBeforeAdvice" class="com.itcast.aop.service.MyMethodBeforeAdvice" />
<!-- 配置代理对象 -->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 1. 配置代理接口集 -->
<property name="proxyInterfaces">
<list>
<value>com.itcast.aop.service.TestServiceInter</value>
<!-- 可以在这里继续配置其他要写日志的service -->
</list>
</property>
<!-- 2. 指定被代理的对象 -->
<property name="target" ref="test1Service" />
<!-- 3. 织入前置通知 -->
<property name="interceptorNames">
<value>myMethodBeforeAdvice</value>
</property>
</bean>
</beans>
ApplicationContextInit.java
package com.itcast.aop.util;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
// ApplicationContext是一个重量级的类, 很耗内存, 所以要设计成单例
public class ApplicationContextInit {
private static ApplicationContext ac = null;
private ApplicationContextInit() {
}
public static ApplicationContext getApplicationContext() {
if (ac == null) {
ac = new ClassPathXmlApplicationContext("beans.xml");
}
return ac;
}
}
AOPTest.java
package com.itcast.aop.test;
import org.springframework.context.ApplicationContext;
import com.itcast.aop.service.TestServiceInter;
import com.itcast.aop.util.ApplicationContextInit;
// 测试类
public class AOPTest {
public static void main(String[] args) {
ApplicationContext ac = ApplicationContextInit.getApplicationContext();
TestServiceInter ti = (TestServiceInter) ac.getBean("proxyFactoryBean"); // 面向接口编程
ti.sayHello();
}
}
运行结果:
---------------------------------------------------------------------------------------
功能需求: 在调用sayHelo()方法之后关闭资源[后置通知]
MyAfterReturningAdvice.java
package com.itcast.aop.service;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
// 后置通知
public class MyAfterReturningAdvice implements AfterReturningAdvice {
/**
* returnValue: 目标方法的返回值
* method: 目标方法
* args: 目标方法的参数
* target: 被代理的对象
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {
System.out.println("后置通知: 关闭资源...");
}
}
beans.xml
测试:
---------------------------------------------------------------------------------------
[环绕通知]: 可以选择在目标方法执行前/后执行
MyMethodInterceptor.java
package com.itcast.aop.service;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// 环绕通知
public class MyMethodInterceptor implements MethodInterceptor {
/**
* method: 目标方法
*/
@Override
public Object invoke(MethodInvocation method) throws Throwable {
System.out.println("环绕通知: 目标方法前调用...");
Object object = method.proceed();
System.out.println("环绕通知: 目标方法后调用...");
return object;
}
}
beans.xml
测试
[异常通知]: 目标方法发生异常时执行
Test1Service.java
MyThrowsAdvice.java
package com.itcast.aop.service;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
// 异常通知: 在目标方法执行过程中出异常时调用
// ThrowsAdvice: 此接口是标识性接口, 没有任何方法
public class MyThrowsAdvice implements ThrowsAdvice {
/**
* @param method: 目标方法
* @param objects: 目标方法的参数
* @param target: 被代理的对象, 即: Test1Service对象
* @param exception: 捕获到的异常
*/
public void afterThrowing(Method method, Object[] objects, Object target, Exception exception) {
System.out.println("异常通知: 出异常了---->" + exception.getMessage());
}
}
beans.xml
测试
[引入通知]: 只对某些目标方法进行某种通知, 即: 可以选择对哪些[个]方法进行某种通知
功能需求: 只对sayHello()这个方法进行前置通知
引入通知只需在配置文件中进行配置即可: 为了做这个测试, 需要增加一个方法sayBy()
TestServiceInter.java
Test1Service.java
beans.xml
AOPTest.java
测试结果: [只对sayHello()方法引入前置通知, 而sayBy()方法没有被引入前置通知]
-------------------------
测试结果: [只对sayBy()方法引入前置通知, 而sayHello()方法没有被引入前置通知]
---------------------------------------------
插入知识点: 切入点可以用正则表达式进行方法匹配, 比如: sayHello(), sayBy()方法都将会被执行
测试:
---------------------------------------------
插入知识点: 获取到的bean并不是ProxyFactoryBean对象, 而是返回动态代理对象[比如Test1Service对象],
如果目标对象实现了若干接口, spring将使用jdk动态代理技术
如果没有实现接口, spring将使用CGLIB技术