横切关注点:跨越应用程序多个模块的方法或功能。(软件系统,可以看做由一组关注点即业务或功能或方法组成。其中,直接的业务关注点是直切关注点,而为直切关注点服务的,就是横切关注点。)即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。
切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知执行的“地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
下面示意图:
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
1.引入spring-aop
新建 一个Maven项目,在项目中引入Spring核心库与AOP,修改pom.xml文件,在dependencies中增加如下节点:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
2.定义通知(Advice)
前置通知
package org.shangjack.spring.aop.aop05
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 前置通知
*/
public class BeforeAdvice implements MethodBeforeAdvice {
/**
* method 方法信息
* args 参数
* target 被代理的目标对象
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----------------前置通知-----------------");
}
}
后置通知
package org.shangjack.spring.aop.aop05;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
/**
* 后置通知
*
*/
public class AfterAdvice implements AfterReturningAdvice {
/*
* returnValue 返回值
* method 被调用的方法
* args 方法参数
* target 被代理对象
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----------------后置通知-----------------");
}
}
环绕通知
package org.shangjack.spring.aop.aop05;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 环绕通知
* 方法拦截器
*
*/
public class SurroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation i) throws Throwable {
//前置横切逻辑
System.out.println("方法" + i.getMethod() + " 被调用在对象" + i.getThis() + "上,参数 " + i.getArguments());
//方法调用
Object ret = i.proceed();
//后置横切逻辑
System.out.println("返回值:"+ ret);
return ret;
}
}
3.创建代理工厂、设置被代理对象、添加通知。
package org.shangjack.spring.aop.aop05
import org.springframework.aop.framework.ProxyFactory;
public class Test {
@org.junit.Test
public void test01()
{
//实例化Spring代理工厂
ProxyFactory factory=new ProxyFactory();
//设置被代理的对象
factory.setTarget(new Math());
//添加通知,横切逻辑
factory.addAdvice(new BeforeAdvice());
factory.addAdvice(new AfterAdvice());
factory.addAdvice(new SurroundAdvice());
//从代理工厂中获得代理对象
IMath math=(IMath) factory.getProxy();
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
@org.junit.Test
public void test02()
{
//message.message();
}
}
运行结果:
4.封装代理创建逻辑
在上面的示例中如果要代理不同的对象需要反复创建ProxyFactory对象,代码会冗余。同样以实现方法耗时为示例代码如下:
4.1创建一个环绕通知:
package org.shangjack.spring.aop.aop05;
import java.util.Random;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 用于完成计算方法执行时长的环绕通知
*/
public class TimeSpanAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
// 被织入的横切内容,开始时间 before
long start = System.currentTimeMillis();
lazy();
//方法调用
Object result = invocation.proceed();
// 被织入的横切内容,结束时间
Long span = System.currentTimeMillis() - start;
System.out.println("共用时:" + span);
return result;
}
// 模拟延时
public void lazy() {
try {
int n = (int) new Random().nextInt(500);
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.2封装动态代理类
package org.shangjack.spring.aop.aop05;
import org.springframework.aop.framework.ProxyFactory;
/**
* 动态代理类
*
*/
public abstract class DynamicProxy {
/**
* 获得代理对象
* @param object 被代理的对象
* @return 代理对象
*/
public static Object getProxy(Object object){
//实例化Spring代理工厂
ProxyFactory factory=new ProxyFactory();
//设置被代理的对象
factory.setTarget(object);
//添加通知,横切逻辑
factory.addAdvice(new TimeSpanAdvice());
return factory.getProxy();
}
}
4.3测试运行
package org.shangjack.spring.aop.aop05;
import org.springframework.aop.framework.ProxyFactory;
public class DynaminProxyTest {
@org.junit.Test
public void test01()
{
//从代理工厂中获得代理对象
IMath math=(IMath) DynamicProxy.getProxy(new Math());
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
@org.junit.Test
public void test02()
{
IMessage message=(IMessage) DynamicProxy.getProxy(new Message());
message.message();
}
}
4.4封装动态代理类:
package org.shangjack.spring.aop.aop05;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
public class SpringProxy<T> implements MethodInterceptor {
/**获得代理后的对象*/
public T getProxyObject(Object target){
//代理工厂
ProxyFactory proxy=new ProxyFactory();
//添加被代理的对象
proxy.setTarget(target);
//添加环绕通知
proxy.addAdvice(this);
//获得代理后的对象
return (T) proxy.getProxy();
}
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
before();
//调用方法获得结果
Object result=methodInvocation.proceed();
after(result);
return result;
}
public void before(){
System.out.println("调用方法前");
}
public void after(Object result){
System.out.println("调用方法后"+result);
}
}
通过反射创建对象:
Class<T> entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
T entity = entityClass.newInstance();
测试运行:
package org.shangjack.spring.aop.aop05;
public class SpringProxyTest {
@org.junit.Test
public void test01()
{
SpringProxy<Math> proxy = new SpringProxy();
//从代理工厂中获得代理对象
IMath math= proxy.getProxyObject(new Math());
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
}
5.使用IOC配置的方式实现AOP
5.1引入Spring IOC的核心jar包,方法与前面相同。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
5.2创建IOC的配置文件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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 被代理的目标对象 -->
<bean id="target" class="org.shangjack.spring.aop.aop06.Math"></bean>
<!--通知、横切逻辑-->
<bean id="advice" class="org.shangjack.spring.aop.aop06.AfterAdvice"></bean>
<!--代理对象 -->
<!--interceptorNames 通知数组 -->
<!--p:target-ref 被代理的对象-->
<!--p:proxyTargetClass 被代理对象是否为一个类,如果是则使用cglib,否则使用jdk动态代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="advice"
p:target-ref="target"
p:proxyTargetClass="true"></bean>
</beans>
5.3获得代理类的实例并测试运行
package org.shangjack.spring.aop.aop06;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void test01()
{
//容器
ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");
//从代理工厂中获得代理对象
IMath math=(IMath)ctx.getBean("proxy");
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
}
5.4、小结
这里有个值得注意的问题:从容器中获得proxy对象时应该是org.springframework.aop.framework.ProxyFactoryBean类型的对象(如下代码所示),但这里直接就转换成IMath类型了,这是因为:ProxyFactoryBean本质上是一个用来生产Proxy的FactoryBean。如果容器中的某个对象持有某个FactoryBean的引用它取得的不是FactoryBean本身而是 FactoryBean的getObject()方法所返回的对象。所以如果容器中某个对象依赖于ProxyFactoryBean那么它将会使用到 ProxyFactoryBean的getObject()方法所返回的代理对象这就是ProxyFactryBean得以在容器中使用的原因。
ProxyFactoryBean message=new ProxyFactoryBean();
message.setTarget(new Message());
message.addAdvice(new SurroundAdvice());
((IMessage)message.getObject()).message();