一、拦截器模式下的AOP
spring拦截器标准下实现代理模式(将spring源码从底层抽取出,重新实现):
把方法执行的时机(亦称为通知)抽取5个节点 :
- 方法执行之前 如果方法之前,做拦截,拦截方法如果返回为true,才允许后面流程执行
- 方法执行(包围)方法如果能被调用,那么只能在包围方法中调用
result = interceptor.aroundInvoke(method, args, ori, proxy);方法执行(包围) - 方法执行之后 能够获取方法返回的结果
- 当方法发生了异常
- 最终
注:将执行的逻辑交给拦截器去做,最后用的是拦截器的逻辑,而非代理类的逻辑,这样可以对代理类再次进行代理,再次代理见下面ProxyMain.java类的 责任链模式代码块
接口 interceptor
public interface Interceptor {
/**
*
* <b>Description: 在代理类方法执行之前被执行,如果返回为真,则继续执行,如果为假,则方法不被执行</b><br>
*
* @param method 原始类方法
* @param args 执行原始类方法的参数
* @param ori 原始对象
* @param proxy 代理对象
*/
public Boolean beforeInvoke(Method method, Object[] args, Object ori, Object proxy);
//方法执行(包围)
public Object aroundInvoke(Method method, Object[] args, Object ori, Object proxy)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
//方法执行之后,有返回值result
public void afterInvoke(Object result, Method method, Object[] args, Object ori, Object proxy);
//方法发生异常
public void exceptionInvoke(Exception e, Method method, Object[] args, Object ori, Object proxy);
//最终
public void finallyInvoke(Method method, Object[] args, Object ori, Object proxy);
}
接口的子实现类 Interceptor1.java
public class Interceptor1 implements Interceptor {
public Boolean beforeInvoke(Method method, Object[] args, Object ori, Object proxy) {
System.out.println("interceptor1");
return true;
}
public Object aroundInvoke(Method method, Object[] args, Object ori, Object proxy)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return method.invoke(ori, args);
}
public void afterInvoke(Object result, Method method, Object[] args, Object ori, Object proxy) {
System.out.println("after: " + result);
}
public void exceptionInvoke(Exception e, Method method, Object[] args, Object ori, Object proxy) {
System.out.println(e.getMessage());
}
public void finallyInvoke(Method method, Object[] args, Object ori, Object proxy) {
System.out.println("finally");
}
}
创建代理类
public class JDKProxyProcessor implements InvocationHandler {
private Object ori;
private String inteceptorName;//为了拦截器的可配置,将拦截器名抽取出来成为一个变量
public JDKProxyProcessor(Object ori) {
this.ori = ori;
}
public JDKProxyProcessor(Object ori, String inteceptorName) {
this.ori = ori;
this.inteceptorName = inteceptorName;
}
/*
* 目标类中的所有方法,都会被该方法的逻辑增强 不一定是逻辑变多(non-Javadoc)
*
* 方法执行的参数,方法执行的结果 谁再什么时候,用什么参数,调用了什么方法,得到了什么结果 批量增强某些类中的方法
*
* 要求原始类必须有接口
*
* CGLIB的实现 通过字节码增强 性能比JDK的实现要慢一些
*
* 拦截器规范
*
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
* java.lang.reflect.Method, java.lang.Object[])
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (inteceptorName != null) { //判断如果有拦截器的名字传进来,则做逻辑增强,否则原始方法输出;
//通过拦截器的全限定名(反射)创建出拦截器
Interceptor interceptor = (Interceptor) Class.forName(inteceptorName).newInstance();
try {
// 方法执行之前 如果方法之前,做拦截,拦截方法如果返回为true,才允许后面流程执行
if (interceptor.beforeInvoke(method, args, ori, proxy)) {
// 方法执行(包围)方法如果能被调用,那么只能在包围方法中调用
result = interceptor.aroundInvoke(method, args, ori, proxy);
// 方法执行(包围)
interceptor.afterInvoke(result, method, args, ori, proxy);
} else {
return null;
}
// 方法执行之后 能够获取方法返回的结果
} catch (Exception e) {
// TODO: handle exception
// 当方法发生了异常
interceptor.exceptionInvoke(e, method, args, ori, proxy);
} finally {
// 最终
interceptor.finallyInvoke(method, args, ori, proxy);
}
} else {
result = method.invoke(ori, args);
}
/*
* if (method.getName().equals("add")) { System.out.println(new Date() +
* " 使用参数 :" + Arrays.toString(args) + " 执行了方法 " + method.getName() + " 得到了: " +
* result + "结果"); }
*/
return result;
}
}
此时在JDKProxyFactory类中添加方法:
public class JDKProxyFactory {
public static Object bind(Object object) {
// 被代理的类必须有接口,jdk会根据代理类的所有接口,创建一个具有相同实现关系的代理类,原始类和代理类之间相当于兄弟关系
// 第三个参数,要如何增强原始类,将所有增强的逻辑写在InvocationHandler的invoke中
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),
new JDKProxyProcessor(object));
}
//通过拦截器创建代理类,传入原始类对象和接口名字(全限定名)
public static Object bind(Object object, String interceptorName) {
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),
new JDKProxyProcessor(object, interceptorName));
}
}
代理测试类ProxyMain.java:
public class ProxyMain {
public static void main(String[] args) {
Service service1 = new Service1();
service1.service();
System.out.println(service1.add(1));
System.out.println("-----");
//责任链模式
//用Interceptor1这个拦截器去拦截service1这个原始类,得到代理类proxyService2
Service proxyService2 = (Service) JDKProxyFactory.bind(service1, "net.seehope.spring.demo.aop.Interceptor1");
//将代理类作为原始类,再用Interceptor1进行再次拦截
Service proxyService3 = (Service) JDKProxyFactory.bind(proxyService2,
"net.seehope.spring.demo.aop.Interceptor1");
proxyService3.service();
}
}
分隔线
导入切面依赖的jar包
<!-- 切面依赖 -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
二、通过配置文件&注解方式实现AOP
切面类示例:MyAspect.java**(面)**
joinpoint(连接点):目标方法称之为连接点
advice(通知): 拦截方法
aspect(切面):存放通知的类
pointcut(切点):所有连接点的集合
注:Spring 中的切面 只能作用于bean
在Spring中切面用来拦截bean中的方法(点),将这种行为成为“切”(面跟点之间的关系),一个bean是一个组件或单独的节点。spring利用一下类的这些拦截方法(advice),去拦截一系列的方法,成为切面编程。
public class MyAspect {
public void pointCut() {
}
public boolean myBefore(JoinPoint jp) {//JionPoint:连接点
System.out.println("mybefore");
return true;
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed();
return result;
}
public void myAfter(Object result) { //使用配置,不能加JoinPoint jp参数
System.out.println("myafter: " + result);
}
public void myException(Exception e) {
System.out.println(e.getMessage());
}
public void myFinally(JoinPoint jp) {
System.out.println("myAfter");
}
}
在src/main/resources目录下创建的applicationContext.xml文件中,点击NameSpaces(命名空间),将AOP的约束导进来。
AOP在Spring容器中的配置----基于文件的切面配置方式(比之前的切面优秀)
切点表达式:execution()表示它是一个切点表达式,然后根据方法签名进行匹配
* 表示该方法返回值任意
* net.xikee 表示该方法在net.xikee这个包中
* net.xikee…* 表示包后面可以有任意层级
* net.xikee…* .service.impl 表示后面层级为service.impl
* net.xikee…* .service.impl.*.* 表示该包下的所有类的所有方法
* net.xikee…* .service.impl.*.* (··) ()表示所有方法,“··”表示任意参数
如果切点表达式匹配成功,表示切点切中(则Spring中的bean为代理类),不使用原始类而使用代理类
<!-- 将切面组件注册到Spring容器中 -->
<bean id="myAspect" class="net.xikee.druid.aop.MyAspect"></bean>
<!-- 使用切面中的哪些通知去拦截哪些连接点 -->
<!-- pointcut切点,所有连接点的集合 -->
<aop:config proxy-target-class="false"> //aop的配置标签
<aop:aspect ref="myAspect"> //根据已注册的组件id(=myAspect)决定对哪个组件进行配置
<aop:pointcut
expression="execution(* net.seehope..*.service.impl.*.*(..))"
id="pc" />
<aop:before method="myBefore" pointcut-ref="pc" /> //让myBefore这个通知通知pc这个切点
<aop:around method="myAround" pointcut-ref="pc" />
<aop:after-returning method="myAfter"
pointcut-ref="pc" returning="result" /> //给myAfter配置个result的参数,才能通过反射将值注入
<aop:after-throwing method="myException"
pointcut-ref="pc" throwing="e" /> //同上
<aop:after method="myFinally" pointcut-ref="pc" />
</aop:aspect>
</aop:config>
proxy-target-class="false"表示使用默认的JDK代理实现,true则表示使用CGLIB(在字节码文件中对原始类进行增强,不要求原始类有接口,但效率比JDK低)
其中最重要的通知是:Around 和 Exception
AOP在Spring容器中的配置----基于注解的AOP实现
@Component:将MyAspect配置成Spring中的组件
@Aspect: 表示是一个切面
通过注解配置,这个bean与Spring中的其他组件没有耦合,通过以上两个注解将MyAspect插入Spring容器中,成为一个bean组件。如果对项目中的类进行增强,则写个切面,即插即用,对项目源码没有任何影响
@Component
@Aspect
public class MyAspect {
//注册一个名叫“pointCut()”的切点,并将切点表达式封装到方法中
@Pointcut("execution(* net.seehope..*.service.impl.*.*(..))")
public void pointCut() {
}
@Before("pointCut()") //将方法配置成通知,将切点名插入
public boolean myBefore(JoinPoint jp) {//JionPoint:连接点
System.out.println("mybefore");
return true;
}
@Around("pointCut()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed();
return result;
}
@AfterReturning(pointcut = "pointCut()", returning = "result") //配置参数名
public void myAfter(Object result) { //使用配置,不能加JoinPoint jp参数
System.out.println("myafter: " + result);
}
@AfterThrowing(pointcut = "pointCut()", throwing = "e")
public void myException(Exception e) {
System.out.println(e.getMessage());
}
@After("pointCut()")
public void myFinally(JoinPoint jp) {
System.out.println("myAfter");
}
}
最后需在applicationContext.xml文件中开启AOP注解支持功能:
<!-- 开启对AOP注解支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
```