《partner4java 讲述Spring入门》之第二步:Spring AOP

本文详细介绍了Spring AOP的基本概念、应用场景及实现方法。包括AOP的核心概念、Spring AOP的特点、AOP的实现原理等内容。并通过多个示例演示了如何在Spring中使用AOP。
导读,我们本章主要分为三部分:
1、什么是AOP?概念和简单示例讲解。
2、Spring中AOP基础部分。
3、Spring中AOP使用升级篇。


第一部分:什么是AOP?

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。 -- 摘自百度知道

举个例子:
好比说“雷政富”大哥是一个对象,他有“腰部运动”的方法(当然腰部运动方法接收的参数是一个美女对象),这大哥的“腰部运动”方法就是单纯的生理需求。
可是开发商很阴险,不仅给雷大哥提供了“腰部运动”的“道具”,还在雷大哥不知情的情况下,在“腰部运动”方法前后又加了“录像”功能。
当然,前提是雷大哥的“腰部运动”是在开发商提供的“特殊宾馆”里进行的。

这种借助特殊的“宾馆”,来完成在已有“腰部运动”方法前后,添加额外的“录像”功能的方式,称之为 AOP。

适用范围:
1、无法修改原对象;(你让雷哥自己拍了给你,你不找死么?)
2、需要重复修改大量对象,出现大量重复工作;(不是还有5个么?)

AOP的核心概念:
连接点(jointpoint):一个连接点是一个程序执行过程中的特定点。用来定义在程序的什么地方能通过AOP加入额外的逻辑。(“腰部运动”方法的执行位置 -- 趴上床那个点)
通知(advice):在某一特定的连接点处运行的代码称为“通知”。(即“录像”)
切入点(pointcut):切入点是用来定义某一个通知该何时执行的一组连接点。(具体谁开启哪个房间的时候开启录像 -- 因为有时候,有俩大叔无意开了这个房间,你也要看么?)
方面(aspect):通知和切入点的组合叫做方面。这个组合定义了一段程序中应该包含的逻辑以及何时应该执行该逻辑。(就是一整套整合流程,人家演练了好几次呢)
织入(weaving):织入是将方面真正的加入程序代码的过程。(开机、录制过程)
目标(target):如果一个对象的执行过程受到某个AOP操作的修改,那么他就叫做一个目标对象,也成为通知对象。(雷政富)
引入(introduction):通过引入,我们可以在一个对象中加入新的方法或者字段,以改变他的结构。(带到了指定包房)


我们来完成我们上面的例子(通过JDK 的动态代理):
首先我们要明确一点,“腰部运动”要有指定接口,为什么呢?可想而知,人家提前演练总要有规范流程的。

package com.partner4java.demo1;


/**
 * 娱乐项目
 * 
 * @author partner4java
 * 
 */
public interface Entertainment {
	/**
	 * 要不运动
	 * 
	 * @param girl
	 *            训练有素的神秘女孩
	 * @return 返回运动时间,单位秒
	 */
	public int loinMove(String girl);
}


package com.partner4java.demo1;


/**
 * 不要羡慕雷哥,这也是体力劳动,很累的,特别是每天都有那么多科目
 * 
 * @author partner4java
 * 
 */
public class LeiZhengFu implements Entertainment {


	public int loinMove(String girl) {
		System.out.println("雷哥脱光了衣服,露出了傲人的身材");
		System.out.println("爬上了:" + girl);
		// 12秒哥的由来
		return 12;
	}


}


package com.partner4java.demo1;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


/**
 * 房间类: 我们创办了一个特殊的房间,专门用于
 * (本类特点,只是接收了Entertainment类型参数,所以,另外5个人也不需要再单独建立房间(InvocationHandler),只需要让开发商领进来(Proxy)就可以了)
 * @author partner4java
 * 
 */
public class GuestHouse implements InvocationHandler {
	private Entertainment entertainment;


	public Object bind(Entertainment entertainment) {
		this.entertainment = entertainment;
		// 领进来特殊宾馆的特殊房间,让你去做“腰部运动”,其实这哥们并不知道这房间都对他偷偷做了什么,但是偷偷做的动作还是需要依赖于这哥们
		return Proxy.newProxyInstance(
				entertainment.getClass().getClassLoader(), entertainment
						.getClass().getInterfaces(), this);
	}


	// 这就是房间的步骤
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("开拍");
		//雷哥做腰部运动
		Object returnO = method.invoke(entertainment, args);
		System.out.println("关机");
		System.out.println("拍摄时间:" + returnO + "秒");
		System.out.println("真你妈浪费资源,不行让哥来啊");
		return returnO;
	}
}

执行下面三行代码:
诞生了12秒哥的神奇录像
GuestHouse guestHouse = new GuestHouse();
Entertainment leiGe = (Entertainment) guestHouse.bind(new LeiZhengFu());
leiGe.loinMove("神秘女孩");

Demo代码大体解释:
用到了两个类Proxy、InvocationHandler。

Proxy:
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
(也就是那个开发商,他安排了整套计划 -- 比如带着雷哥去指定房价)


newProxyInstance方法:
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序


InvocationHandler:
InvocationHandler 是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
(就是布置好的那个房间,具体拍照工作都在这进行的)


invoke方法:
在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
参数:
proxy - 在其上调用方法的代理实例
method - 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。

=================================================================
再举俩Demo:
两种类型AOP:静态AOP和动态AOP。
两者的区别在于何时真正发生织入过程,以及如何织入。


静态AOP:
我们在一个静态AOP实现中通过修改应用程序实际字节码来完成织入过程,从而根绝需要修改和扩展程序代码。
显然,这是个达到织入过程的高性能方式,因为最终结果就是普通的Java字节码,在运行时我们不再需要特别的技巧来确定什么时候应该执行通知。
(AspectJ便是静态AOP实现的一个绝好的示例。)


demo:
代理对象与被代理对象必须实现同一个接口。
package cn.partner4java.proxy.staticproxy;




/**
 * 静态代理,统一接口
 * @author partner4java
 *
 */
public interface IHello {
	/**
	 * 可以带来的统一方法
	 * @param name
	 */
	public void hello(String name);
}


package cn.partner4java.proxy.staticproxy;


/**
 * 被代理的对象,需要借助代理对象加入日志
 * @author partner4java
 *
 */
public class HelloSpeaker implements IHello {


	public void hello(String name) {
		System.out.println("Hello " + name);
	}


}


package cn.partner4java.proxy.staticproxy;


/**
 * 代理对象,给被代理对象添加日志
 */
public class HelloProxy implements IHello {
	
	private IHello iHello;


	public HelloProxy(IHello iHello) {
		super();
		this.iHello = iHello;
	}


	public void hello(String name) {
		System.out.println("记录日志");
		iHello.hello(name);
	}


}


package cn.partner4java.proxy.staticproxy;


/**
 * 调用
 * @author partner4java
 *
 */
public class ProxyDemo {


	public static void main(String[] args) {
		IHello iHello = new HelloProxy(new HelloSpeaker());
		iHello.hello("long");
	}


}


动态AOP:
动态代理区别于静态带来实现的地方在于织入过程是在运行时动态进行的。
动态AOP实现的主要好处在于,你能轻易的修改一个应用的整个方面的集合而无需重新编译主程序的代码。


例子:
自己实现一般实现java.lang.reflect.InvocationHandler接口。
package cn.partner4java.proxy.dynamicproxy;




public interface IHello {
	public void hello(String name);
}




package cn.partner4java.proxy.dynamicproxy;


/**
 * 被代理的对象,需要借助代理对象加入日志
 * @author partner4java
 *
 */
public class HelloSpeaker implements IHello {


	public void hello(String name) {
		System.out.println("Hello " + name);
	}


}


package cn.partner4java.proxy.dynamicproxy;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


/**
 * 动态代理对象
 * @author partner4java
 *
 */
public class LogHandler implements InvocationHandler {


	private Object delegate;
	
	public Object bind(Object delegate){
		this.delegate = delegate;
		return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), 
				delegate.getClass().getInterfaces(), this);
	}
	/**
	 * 代理对象,这里面还可以改变原有的方法
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result = null;
		try {
			System.out.println("添加日志");
			result = method.invoke(delegate, args);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}


}


package cn.partner4java.proxy.dynamicproxy;


/**
 * 测试
 * @author partner4java
 *
 */
public class ProxyDemo {
	public static void main(String[] args) {
		LogHandler logHandler = new LogHandler();
		IHello iHello = (IHello) logHandler.bind(new HelloSpeaker());
		iHello.hello("long");
	}
}

=================================================================

第二部分:Spring中AOP基础部分


Spring AOP实际上是所有AOP特性集合的一个子集,他只提供了诸如AspectJ等其他AOP实现中的功能的一小部分。
不过,正因为他放弃了一些不常用的功能,才使用如此简单,而简单是Spring AOP最强大的地方之一。

aopalliance AOP联盟:是一个包含Spring在内的很多开源AOP项目的代表们所组成的联合组织,他定义了一个AOP实现接口的标准集。

本章学习主要分为了三部分:


本章第一部分:HelloWorld Spring AOP -- 借助Spring封装类来完成AOP
(我们就不再用雷大哥举例子了,免得有上门查水表的)

需求:
public class MessageWriter {
	public void wirteMessage(){
		System.out.println("World");
	}
}

在World的打印前后加上文字如:"Hello World!"。也就是在前面加了“Hello”,在后面加了“!”。

用AOP术语来说,我们需要一个包围通知(around advice),也就是一个包围连接点的通知。

实现通过两小步:
1、实现包围通知:
实现MethodInterceptor接口:
MethodInterceptor接口是对方法调用连接点实现包围通知的AOP联盟标准接口。
MethodInterceptor对象代表当前被通知的方法调用,我们使用这个类来控制具体什么时候进行方法调用。
public class MessageDecorator implements MethodInterceptor {


	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("Hello ");
		//调用原方法获取返回值
		Object retVal = invocation.proceed();
		System.out.println(" !");
		return retVal;
	}


}

2、将MessageDecorator通知织入代码中:
我们新建一个MessageWriter对象,即目标对象,然后为其创建一个代理,并让代理工厂(proxy factory)织入MessageDecorator通知。
public class HelloWorldWeaver {


	public static void main(String[] args) {
		MessageWriter messageWriter = new MessageWriter();
		
		//create the proxy
		ProxyFactory factory = new ProxyFactory();
		//设置我们的通知
		factory.addAdvice(new MessageDecorator());
		//设置我们的目标
		factory.setTarget(messageWriter);
		
		//获取被织入后的代理对象
		MessageWriter proxy = (MessageWriter) factory.getProxy();
		proxy.wirteMessage();
	}


}

用ProxyFactory类来创建一个目标对象的代理,通过织入通知。
通过调用addAdvice()方法把MessageDecorator通知传给ProxyFactory,然后通过调用setTarget()方法设置织入的目标对象。
就可以调用getProxy()方法获得一个代理。
(final类不能被扩展--以这种方式)

借助Spring的ProxyFactory是不是比我们直接用JDK简单些?(Spring给予了封装)


Spring AOP架构:
Spring AOP架构的核心是建立在代理上的。
当我们建立被通知类的实例时,我们必须使用ProxyFactory类加入我们需要织入该类的所有通知。
使用ProxyFactory创建AOP带来是一个完全编程式的做法。
(大部分情况下,我们不需要在应用中直接这么做,而可以依赖ProxyFactoryBean类来声明式的创建代理。)
Spring内部有两种实现代理的方法:JDK动态代理和CGLIB代理。具体可查看DefaultAopProxyFactory的createAopProxy方法。


ProxyFactory类:
ProxyFactory类控制着Spring AOP中的织入和创建的过程。在真正创建代理之前,我们必须指定被通知对象或者说目标对象。
ProxyFactory内部将生成代理的过程交给DefaultAopProxyFactory,后者又根据设置将其交给CglibProxyFactory或者JdkDynamicAopProxy。
有时候我们希望在目标类中的所有方法被调用时都执行通知,而不是一部分方法被调用,可使用addAdvice,但是当我们想对创建的Advisor有更多的控制,或者要向代理中添加一个引入,可以自行创建Advisor,使用addAdvisor。
还提供了removeAdvisor方法。

本章第二部分:在Spring中创建通知

1、通知的接口

前置通知:org.springframework.aop.MethodBeforeAdvice
使用前置通知可以在连接点执行前进行自定义的操作。
不过,Spring中只有一种连接点,即方法调用,所以前置通知就是在你能在方法调用前进行一些操作。
前置通知可以访问调用的目标方法,也可以对该方法的参数进行操作,不过他不能影响方法调用本身。

后置通知:org.springframework.aop.AfterReturningAdvice
后置通知在方法处的链接调用已经完成,并已经返回值时运行。
后置通知可以访问调用的目标方法,以及该方法的参数和返回值。
因为等到通知执行时该方法已经调用,后置通知完全不能影响方法调用本身。

环绕通知:org.aopalliance.intercept.MethodInterceptor
Spring中的包围通知根据AOP联盟的方法拦截器标准建模。
包围通知可以在目标方法之前和之后运行,我们也可以定义在什么时候运行目标方法。
如果需要,我们也可以完全不调用目标方法而写自己的逻辑。

异常通知:org.springframework.aop.ThrowsAdvice
抛出通知仅在方法调用抛出异常时才被调用,他在目标方法调用返回时执行。
异常通知可以只捕获特定的异常。

前面的通知都继承自Advice,可以通过ProxyFactory的addAdvice方法进行加入。

引入:org.springframework.aop.IntroductionInterceptor
Spring将引入看做一个特殊的拦截器。
使用引入拦截器,我们可以定义通知引入的方法的实现。


2、创建前置通知
和前面的demo类似,实现一个简单的前置打印。
public class SimpleBeforeAdvice implements MethodBeforeAdvice {


	@Override
	public void before(Method method, Object[] args, Object target)
			throws Throwable {
		System.out.println("Before method:" + method.getName());
	}


	public static void main(String[] args) {
		MessageWriter messageWriter = new MessageWriter();
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvice(new SimpleBeforeAdvice());
		factory.setTarget(messageWriter);
		
		MessageWriter proxyMessageWriter = (MessageWriter) factory.getProxy();
		proxyMessageWriter.wirteMessage();
	}
}

3、创建后置通知
顾名思义,后置通知是在方法调用后执行的。既然该方法已经调用了,就没办法修改他的参数了。
我们只能读取,而不能修改目标方法的执行路径或阻止目标方法执行。
这些约束都在预料之中的,真正出人意料的是我们也不能在后置通知中修改目标方法的返回值。
我们只能够进行一些新的操作。
虽然我们不能修改返回值,但是我们还是可以跑出一个异常,这样调用方法就只能看到这个异常而不是返回值了。

(Demo省略了,和前一个没什么大的区别)

4、创建包围通知
包围通知在功能上综合了前置通知和后置通知,除了一个重要区别:我们可以修改方法的返回值。
不仅如此,我们还可以阻止目标方法的实际执行。
这意味着用包围通知,我们可以将目标方法的实现完全更新成新的代码。

(Demo省略了,前面用过了)

5、创建抛出通知
抛出通知和后置通知一样是在连接点之后运行的,不过抛出异常只在方法抛出一个异常时才执行。
另外抛出通知对程序本身也不能做任何改变,这和后置通知一样。
使用抛出通知时我们不能对已经抛出的异常视而不见而为目标方法返回一个值,我们能做的值是改变抛出异常的类型。

我们用ThrowsAdvice接口来实现抛出通知。
与之前的其他接口不同,ThrowsAdvice接口没有定义任何方法,他只是Spring使用的一个标志接口,其原因是Spring允许类型抛出通知,也就是可以定义通知具体接受哪几类的异常。
Spring通知反射机制寻找固定的方法签名来实现类型抛出通知。Spring寻找两种不同的方法签名。

1.首先方法名称必须是afterThrowing;
2.可以接收四个参数的形态afterThrowing(Method method, Object[] args, Object target,Exception ex),也可以接受单个参数的形态
3.只匹配“最亲近异常捕获”
public class ErrorBean {
	public void errorAll() throws Exception{
		throw new Exception("errorAll");
	}
	
	public void illegalArgument(){
		throw new IllegalArgumentException("illegalArgument");
	}
}




public class SimpleThrowsAdvice implements ThrowsAdvice {
	public void afterThrowing(Method method, Object[] args, Object target,
			IllegalArgumentException ex) {
		System.out.println("IllegalArgumentException:" + method.getName());
	}
	
	public void afterThrowing(Method method, Object[] args, Object target,
			Exception ex) {
		System.out.println(method.getName());
	}
	
	public static void main(String[] args) throws Exception {
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvice(new SimpleThrowsAdvice());
		
		ErrorBean errorBean = new ErrorBean();
		factory.setTarget(errorBean);
		
		ErrorBean proxyErrorBean = (ErrorBean) factory.getProxy();
//		proxyErrorBean.errorAll();
		proxyErrorBean.illegalArgument();
	}
}

7、选择通知类型
使用最精确的类型可以是代码的意图更清晰,同事也能减少出差的可能性。

本章第三部分:Spring里的通知者和切入点

到目前为止,全部的示例都用ProxyFactory.addAdvice()方法为代理设定通知。
该方法会在后台委派给addAdvisor()方法(注意哦,这里是Advisor不是Advice),而后者会创建一个DefaultPointcutAdvisor实例并将切入点设为对所有方法的调用
这样,目标的所有方法就都能被通知到了。
在某些情况下,比如用AOP做日志时,这样做可能正是我们所需要的,可能在别的情况下,我们希望所通知的是相关的方法而不是所有的方法(那么就要借助于切入点 -- Pointcut)。


当然,我们可以在通知内检查被通知的方法是不是正确的方法,不过这样做有几个缺点。
1.将接受的方法名称列表直接写进代码中会降低通知的通用性。使用切入点可以控制哪些方法被通知而无需将列表写入通知中,显然这样做可重用性比较好。
2.为了确定被调用的方法是否应该被通知,每次目标上的任何一个方法被调用时都需要做一次检查,这显然会影响程序的性能。当使用切入点时,每个方法会被检查一遍,其结果会被缓冲起来供日后使用。
3.Spring使用代理时会优化没有使用通知的方法,这样没有被通知的方法执行起来会被较快。



=====================================================================

讲述我们都需要借助那些类完成


1、使用切入点接口

(切入点接口讲解)

在Spring中使用切入点就要使用Pointcut接口。
public interface Pointcut {
	ClassFilter getClassFilter();
	MethodMatcher getMethodMatcher();
	Pointcut TRUE = TruePointcut.INSTANCE;
}

Pointcut定义了两个方法getClassFilter、getMethodMatcher,分别返回ClassFilter、MethodMatcher。

ClassFilter:
Spring需要确定一个Pointcut是否适用于某个方法,他先会用Pointcut.getClassFilter()返回的ClassFilter测试该方法的类。
public interface ClassFilter {
boolean matches(Class<?> clazz);
}
ClassFilter接口只有一个matches方法,其参数为一个代表被检测类的Class实例。如果切入点适用于该类,那么matches返回true,否则返回false。

MethodMatcher:
相比之下MethodMatcher接口比较复杂。
public interface MethodMatcher {
	boolean matches(Method method, Class<?> targetClass);
	boolean isRuntime();
	boolean matches(Method method, Class<?> targetClass, Object[] args);
}

Spring支持两种不同的MethodMatcher:静态的和动态的。一个MethodMatcher具体是哪一种可以由isRuntime返回值确定,如果返回false,那么该MethodMatcher是静态的,反之为动态的。
如果切入点是静态的,那么Spring会针对目标的每一个方法调用一次MethodMatcher的matches(Method method, Class<?> targetClass)方法,其返回值被缓存起来便以后调用该方法时使用。这样,对每一个方法的实用性测试只会进行一次,之后调用该方法时不会再调用mathches方法了。
如果切入点是动态的,Spring仍然会在目标方法第一次调用时用matches(Method method, Class<?> targetClass)进行一个静态的测试来检查起总的适用性。不过如果该测试返回true,那么在此基础上,每次该方法被调用时Spring还会再次调用matches(Method method, Class<?> targetClass, Object[] args)方法。
大多数MethodMatcher是静态的,这意味着isRuntime()方法返回false。在这种情况下,matches(Method, Class , Object[])永远不会被调用。

通常很少有人会自己编写Pointcut的实现,因为Spring提供了动态切入点和静态切入点的抽象基类。
接下来,我们学习这些基类以及他们的Pointcut实现:

1.已有的切入点实现概览
Spring提供了Pointcut接口的10个实现,其中两个抽象类分别用来简化创建静态和动态切入点,另外8个是独立类。
ComposablePointcut:可以通过union()、intersection()等操作组合两个或两个以上的切入点。
ControlFlowPointcut:是一种特殊的切入点,他匹配另一个方法的流程中包含的所有方法,也就是另一个方法执行时直接或间接调用的所有方法。
JdkRegexpMethodPointcut:可以用JDK1.4支持的正则表达式定义切入点。
NameMatchMethodPointcut:我们创建一个切入点来简单的匹配一个方法名列表。
StaticMethodMatcherPointcut:用来作为构建静态切入点的基类。
DynamicMethodMatcherPointcut:一个可以方便的构建用来在运行时了解方法参数的动态切入点的基类。
AnnotationMatchingPointcut:用来创建Java 5注解切入点。
AspectJExpressionPointcut:用来通过AspectJ表达式语言定义切入点。(目前只能定义方法执行切入点)


2.DefaultPointcutAdvisor
在使用任何Pointcut之前,必须先生成一个Advisor,更准确的说是一个PointcutAdvisor。
Spring来使用Advisor来表示方面,即通知和切入点的结合,其中切入点定义那些地方应该被通知以及如何通知。
DefaultPointcutAdvisor用来结合一个Pointcut和一个Advice的简单切入点通知。

====================================================================================

创建一个Pointcut

(会对“已有的切入点实现概览”提到的切入点挨个实现)

1.用StaticMethodMatcherPointcut创建静态切入点

StaticMethodMatcherPointcut只需要我们实现一个方法--boolean matches(Method method, Class<?> targetClass),而Pointcut的其他实现是自动完成的。
但是有时候我们也需要覆盖getClassFilter()来保证只有正确类型的方法才会被通知。


Demo(来结合前面提到的DefaultPointcutAdvisor和StaticMethodMatcherPointcut):
我们定义了两个完全相同的类,但是我们只通知其中BeanOne的foo()方法。
public class BeanOne {
	public void foo(){
		System.out.println("foo");
	}
	
	public void bar(){
		System.out.println("bar");
	}
}


public class BeanTwo {
	public void foo(){
		System.out.println("foo");
	}
	
	public void bar(){
		System.out.println("bar");
	}
}


创建Pointcut:
public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {


	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		return ("foo".equals(method.getName()));
	}


	@Override
	public ClassFilter getClassFilter() {
		return new ClassFilter() {


			@Override
			public boolean matches(Class<?> clazz) {
				return (clazz == BeanOne.class);
			}
		};
	}


}


创建Advice:
public class SimpleAdvice implements MethodInterceptor {


	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("invoke:" + invocation.getMethod().getName());
		return invocation.proceed();
	}


}


然后创建一个Advisor,把前面的Pointcut和Advice组合起来:
public class StaticPointcutExample {


	public static void main(String[] args) {
		BeanOne beanOne = new BeanOne();
		BeanTwo beanTwo = new BeanTwo();
		
		Pointcut pointcut = new SimpleStaticPointcut();
		Advice advice = new SimpleAdvice();
		Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
		
		ProxyFactory factory1 = new ProxyFactory();
		factory1.addAdvisor(advisor);
		factory1.setTarget(beanOne);
		BeanOne proxyBeanOne = (BeanOne) factory1.getProxy();
		
		
		ProxyFactory factory2 = new ProxyFactory();
		factory2.addAdvisor(advisor);
		factory2.setTarget(beanTwo);
		BeanTwo proxyBeanTwo = (BeanTwo) factory2.getProxy();
		
		proxyBeanOne.foo();
		proxyBeanTwo.foo();
		
		proxyBeanOne.bar();
		proxyBeanTwo.bar();
	}


}

2.使用DynamicMethodMatcherPointcut创建动态切入点
Demo2:

public class BeanOne {
	public void foo(int x){
		System.out.println("foo:" + x);
	}
	
	public void bar(){
		System.out.println("bar");
	}
}

public class BeanTwo {
	public void foo(int x){
		System.out.println("foo:" + x);
	}
	
	public void bar(){
		System.out.println("bar");
	}
}

和前面demo不同的是只有BeanOne的foo参数为100时才发起通知。

和静态切入点一样,也提供了一个DynamicMethodMatcherPointcut基类,只有一个抽象方法boolean matches(Method method, Class<?> targetClass, Object[] args)。
不过我们会看到,明智的做法是同时也要实现boolean matches(Method method, Class<?> targetClass)方法以控制静态检测。
public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {


//动态切入也加入此方法的好处是,在调用bar方法时,只是第一次做静态判断,以后也不会判断。如果不加,每次调用bar都会做动态切入点判断。
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		System.out.println(targetClass.getSimpleName() + " Static check for " + method.getName());
		return ("foo".equals(method.getName()));
	}


	//动态切入点判断
	@Override
	public boolean matches(Method method, Class<?> targetClass, Object[] args) {
		System.out.println(targetClass.getSimpleName() + " Dynamic check for " + method.getName());
		return (args != null && args.length > 0 && (Integer) args[0] == 100);
	}


	//过滤了类,只对BeanOne做动态或静态matches判断,不对BeanTwo做matches(方法)判断。
	@Override
	public ClassFilter getClassFilter() {
		return new ClassFilter() {
			
			@Override
			public boolean matches(Class<?> clazz) {
				return (clazz == BeanOne.class);
			}
		};
	}


}


3.使用简单的名称匹配
通常创建一个切入点时,我们想要基于方法名称来匹配,而忽略方法签名和返回类型。
在这种情况下,我们可以用NameMatchMethodPointcut来匹配一组方法名称,而不需要创建StaticMethodMatcherPointcut。
使用NameMatchMethodPointcut时,不用理会方法的签名,所以名称foo可以匹配foo(),也可以匹配poo(int)。
Demo:
public class NameBean {
	public void foo(){
		System.out.println("foo");
	}
	
	public void foo(int x){
		System.out.println("foo " + x);
	}
	
	public void bar(){
		System.out.println("bar");
	}
	
	public void yup(){
		System.out.println("yup");
	}
}


public class NamePointcutExample {


	public static void main(String[] args) {
		NameBean nameBean = new NameBean();
		
		NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
		nameMatchMethodPointcut.addMethodName("foo");
		nameMatchMethodPointcut.addMethodName("bar");
		
		Advisor advisor = new DefaultPointcutAdvisor(nameMatchMethodPointcut, new SimpleAdvice());
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(nameBean);
		
		NameBean bean = (NameBean) factory.getProxy();
		
		bean.foo();
		bean.foo(23);
		
		bean.bar();
		bean.yup();
	}


}

我们不需要为切入点创建一个类,只需要简单地创建一个NameMatchMethodPointcut实例就可以了。
我们用addMethodName()方法向切入点加入了两个名字foo和bar。


4.用正则表达式创建切入点
正则表达式具有强大的匹配功能,所以能把正则用在pointcut匹配中,将会“功力大增”。
public class RegexpPointcutExample {


	public static void main(String[] args) {
		RegexpBean bean = new RegexpBean();
		
		JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
		jdkRegexpMethodPointcut.setPattern(".*foo.*");
		Advisor advisor = new DefaultPointcutAdvisor(jdkRegexpMethodPointcut, new SimpleAdvice());
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(bean);
		RegexpBean proxyBean = (RegexpBean) factory.getProxy();
		
		proxyBean.foo1();
	}


}

5.通知者的便利实现
对于很多Pointcut实现,Spring也提供了想对的Advisor的便利实现。这些实现也可以当做Pointcut使用。
比如,前面的NameMatchMethodPointcut和DefaultPointcutAdvisor,我们可以直接用NameMatchMethodPointcutAdvisor提交,这样更加简洁。
Demo:
public class NamePointcutUsingAdvisor {


	public static void main(String[] args) {
		NameBean target = new NameBean();
		NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(new SimpleAdvice());
		advisor.addMethodName("foo");
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(target);
		
		NameBean bean = (NameBean) factory.getProxy();
		bean.foo();
	}


}


6.使用AspectJExpressionPointcut
AspectJExpressionPointcut类让你能够编写AspectJ表达式来定义一个切入点。
Demo:(这里我们只是做简单展示,后面会具体简单介绍AspectJ语言)
public class AspectJExpressionPointcutDemo {


	public static void main(String[] args) {
		AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
		advisor.setAdvice(new SimpleAdvice());
		advisor.setExpression("execution(* com.partner4java..*.foo*(..))");
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(new RegexpBean());
		
		RegexpBean bean = (RegexpBean) factory.getProxy();
		bean.foo1();
		System.out.println("---");
		bean.bar();
	}


}


7.使用AnnotationMatchingPointcut
考虑这样一种情况:在测试时处于某种目的,我们想对若干方法进行通知。但是这些方法都来自不同的包和类中。
另外我们希望尽可能少的配置来对要监控的方法或类进行修改。
一种解决方式就是注解,在所有希望通知的类或方法上使用该注解。
这边是AnnotationMatchingPointcut的用武之地。我们可以使用这个便捷的类为指定了标注的方法或类定义一个切入点。
Demo:
首先自定义一个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface SimpleAnnotation {


}

给要通知的方法加上注解:
public class RegexpBean {
	@SimpleAnnotation
	public void foo1(){
		System.out.println("foo1");
	}
	
	public void foo2(){
		System.out.println("foo2");
	}
然后借助AnnotationMatchingPointcut:
public class AnnotationMatchingPointcutDemo {


	public static void main(String[] args) {
		AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(null, SimpleAnnotation.class);
		Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(new RegexpBean());
		
		RegexpBean bean = (RegexpBean) factory.getProxy();
		bean.foo1();
	}


}
AnnotationMatchingPointcut接收了两个参数,一个类级别的注解类和一个方法级别的注解类。
只传入了类级别注解:那么当某类在类上添加本注解,添加注解类的所有方法都会被通知。
只传入方法级别注解:那么当某方法添加注解,仅本方法会被通知。
两个参数都传入:那么想要使某方法被通知,必须在本方法和方法的类上都添加注解。

8.使用控制流切入点
Spring的控制流切入点是由ControlFlowPointcut类实现的。
简单说,Spring控制流切入点匹配一个类中对某一个方法或所有方法的调用。
但是,如果你对性能有所要求,尽量避免使用。
public class ControlFlowDemo {


	public static void main(String[] args) {
		Pointcut pointcut = new ControlFlowPointcut(ControlFlowDemo.class, "test");
		Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
		
		ProxyFactory factory = new ProxyFactory();
		RegexpBean target = new RegexpBean();
		factory.setTarget(target);
		factory.addAdvisor(advisor);
		
		RegexpBean proxy = (RegexpBean) factory.getProxy();
		
		hello(proxy);
		
		System.out.println("========");
		
		test(proxy);
		
		System.out.println("========");
		
		test();
	}
	
	public static void hello(RegexpBean proxy){
		proxy.foo1();
	}
	
	public static void test(RegexpBean proxy){
		proxy.foo2();
	}
	
	public static void test(){
		RegexpBean proxy = new RegexpBean();
		proxy.foo2();
	}


}
打印:
foo1
========
invoke:foo2
foo2
========
foo2


9、使用ComposablePointcut
在前面的切入示例中,我们每个Advisor中只使用了一个切入点(当然,你可以同时添加多个,因为显而易见,ProxyFactory它提供的是addAdvisor,而不是setAdvisor)。
但是有时我们需要组合两个或更多的切入点才能满足需求。用ComposablePointcut可以对多个切入点进行组合,组合成一个。


ComposablePointcut支持两个方法:union()和interserction()。默认情况下,ComposablePointcut在创建时附带的ClassFilter会匹配所有的类,而其MethodMatcher匹配所有方法,不过我们可以在构造器中提供自己的ClassFilter和MethodMatcher。
union()和intersection()方法都有重载的版本可接受ClassFilter和MethodMatcher参数。


调用接受MethodMatcher的union()方法会将ComposablePointcut的MethodMatcher改为一个UnionMethodMatcher,后者是ComposablePointcut现有的MethodMatcher的并集。
UnionMethodMatcher所包含的两个MethodMatcher中任何一个返回true,UnionMethodMatcher就会返回true。
union()方法可以调用任意多次,每次调用都会生成一个新的UnionMethodMatcher,来包含ComposablePointcut中当前的MethodMatcher和传给union()的MethodMatcher。
将ClassFilter传给union()也会生成类似的结构。


在其内部,intersection()方法的工作原理和union()非常类似。不过,IntersectionMethodMatcher类只在其包含的两个MethodMatcher都返回true时才会返回true。
Demo:
public class ComposablePointcutDemo {


	public static void main(String[] args) {
		ComposablePointcut pointcut = new ComposablePointcut();
		Advisor advisor = new DefaultPointcutAdvisor(pointcut, new SimpleAdvice());
		
		ProxyFactory factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(new RegexpBean());
		
		RegexpBean bean = (RegexpBean) factory.getProxy();
		bean.foo1();
		
		pointcut.intersection(new StaticMethodMatcher() {
			
			@Override
			public boolean matches(Method method, Class<?> targetClass) {
				return method.getName().equals("foo2");
			}
		});
		
		System.out.println("-------------------");
		factory = new ProxyFactory();
		factory.addAdvisor(advisor);
		factory.setTarget(new RegexpBean());
		bean = (RegexpBean) factory.getProxy();
		bean.foo1();
	}


}


可能你会有疑问,我们上面学到的种种基于Spring的AOP实现,全部都是编码的方式,那么是否可以通过XML配置文件的方式实现呢?
我们把AspectJExpressionPointcutDemo转换成XML形式:
只需要把main里面的步骤转换为XML:
<!-- 通知 -->    
<bean id="simpleAdvice" class="com.partner4java.advisor.demo1.SimpleAdvice"></bean>    


<!-- 帮助创建pointcut然后和advice结合成advisor -->    
<bean id="mAspectJExpressionPointcutAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
	<property name="advice" ref="simpleAdvice"/>
	<property name="expression" value="execution(* com.partner4java..*.foo*(..))"/>
</bean>


<!-- 被代理的对象 -->
<bean id="regexpBean" class="com.partner4java.advisor.demo4.RegexpBean"/>


<bean id="proxyRegexpBean" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="regexpBean"/>
	<property name="interceptorNames">
		<set>
			<value>mAspectJExpressionPointcutAdvisor</value>
		</set>
	</property>
</bean>

测试:
public class AspectJExpressionPointcutDemo {


	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/hello_aop.xml");
		RegexpBean bean = (RegexpBean) applicationContext.getBean("proxyRegexpBean");
		bean.foo1();
		System.out.println("---");
		bean.bar();
	}
}


AnnotationMatchingPointcutDemo这种注解形式的如何转换呢?
如果你认真学习了上一章(《partner4java 讲述Spring入门》之第一步:Spring概述与Spring IoC http://blog.youkuaiyun.com/partner4java/article/details/8194747)应该不难推测到。


现在你又有一个疑问,我们不是已经把Advisor交容器了么?能不能省略ProxyFactoryBean,自动代理所有bean?
你只需要在配置文件里面加:<aop:aspectj-autoproxy />
测试:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/hello_aop.xml");
RegexpBean bean = (RegexpBean) applicationContext.getBean("regexpBean");
bean.foo1();
System.out.println("---");
bean.bar();


第三部分:Spring中AOP使用升级篇


Spring AOP的实现分为两个逻辑组成部分。
1、是AOP核心,他提供了完全解耦的、纯编程的式的AOP功能。
2、是让我们能在程序更简便的使用AOP而给出的一组框架服务。


第一条我们前面已经学过了,编码方式,最后也给你略带了如何转为XML形式。接下来我们学习第二条:在程序更简便的使用AOP
如果你足够“苛刻”,你又会对框架提出问题:
1、可不可以借助注解使代码更简洁?
2、是否可以基本不写代码(指我们的Advisor和Pointcut),通过配置文件来实现?


===========================================================================================
解决第一个问题(借助@AspectJ)


1、@AspectJ注解 -- helloworld

三步:

@AspectJ和AspectJ没有关系。它是Spring用来解析连接点和通知的一组Java 5注解。
首先看一个简单的日志Demo:
第一步:定义我们的aspect
@Aspect
public class LoggingAspect {
	@Around("execution(* com.partner4java.*.*.*(..))")
	public Object log(ProceedingJoinPoint point) throws Throwable{
		System.out.println("Before" + point.getTarget().getClass());
		return point.proceed();
	}
}
第二步:打开我们的注解,并把定义的aspect交给spring
<!-- 扫描我们指定目录的注解类解析为Bean  -->
<context:component-scan base-package="com.partner4java"/>
<!--  写进XML,会自动被容器识别 -->
<bean class="com.partner4java.annotation.LoggingAspect"/>
<!--  开启自动识别 -->
<aop:aspectj-autoproxy/>
第三步:测试
@Component
public class TestBean {
	public void work(){
		System.out.println("work");
	}
}


public static void main(String[] args) {
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
			"/META-INF/spring/aspectj-demo.xml");
	TestBean testBean = applicationContext.getBean("testBean",
			TestBean.class);
	testBean.work();
}
	
<aop:aspectj-autoproxy/>Enables the use of the @AspectJ style of Spring AOP.	


2、@AspectJ方面详解


1.切入点
在上一个Demo中我们切入点写在了包围通知中,当然,我们切入点也可以单独拿出来:
@Pointcut("execution(* com.partner4java.annotation.*.*.*(..))")
private void testBeanExecution(){

}
然后我们可以在本类通知中通过方法名引用这个切入点:
@Around("testBeanExecution()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("Before" + joinPoint.getTarget().getClass());
return joinPoint.proceed();
}


如果你想在其他@Aspect声明类中使用本切入点,可以把切入点方法private权限级别加以修改。
在其他类中引用加上类名,如:
@Around("LoggingAspect.testBeanExecution()")


2.切入点表达式
execution:匹配方法执行连接点。我们可以指定包、类或者方法名,以及方法的可见性、返回值和参数类型。这是应用的最为广泛的切入点表达式。
within:匹配那些在已声明的类型中执行的连接点。
this:通过用bean引用的类型跟指定的类型做对比来匹配连接点。
args:通过比较方法的参数类型跟指定的参数类型来匹配连接点。
@target:通过检查调用目标对象是否具有特定注解来匹配连接点。
@args:跟args类似,不过@args检查的是方法参数的注解而不是他们的类型。
@within:跟within相似,这个表达式匹配那些带有特定注解的类中执行的连接点。
@annotation:通过检查讲被调用的方法上的注解是否为指定的注解来匹配连接点。
bean:通过比较bean的ID来匹配连接点,我们也可以在bean名模式中使用通配符。


我们可以使用||(或)和&&(与)运算符来组合多个切入点表达式,并使用!(非)运算符来对表达式值取否。
如:execution(* com.partner4java..*.*(..)) && within(com.partner4java.annotation.demo1.TestBean)


也可以把带有@Pointcut注解的方法跟其他带有@Pointcut注解的方法或一个切入点表达式组合起来:
@Pointcut("testPointcut1() && testPointcut2()")


3.探讨切入点表达式


execution表达式:
Execution表达式语法在Spring中的用法跟AspectJ表达式的语法相同。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
后缀(?)表示可选的表达式元素。
如:
* com.partner4java.demo.Test.*(..)
第一个*表示任何返回类型;后面跟着一个全限定名;这个类名后又跟领一个星号*(..),这表示一个任意名称、任意数量和任意参数类型。
因为我们没有指定修饰符模式(modifiers-pattern)或异常抛出模式(throws-pattern),Spring AOP将匹配任意修饰符和抛出任意异常的方法。


为了匹配com.partner4java子包中的任何类任何方法,我们可以将表达式写为* com.partner4java.demo..*.*(..)
@Aspect
public class LoggingAspect {


	@Around("execution(* com.partner4java..*.*(..)) && !execution(* com.partner4java..*.get*(..))")
	public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("Before" + joinPoint.getTarget().getClass());
		return joinPoint.proceed();
	}
}

within表达式:
within(declaring-type-pattern)
例如:
要声明一个可以匹配在com包及其子包的任意类中对任意方法调用的执行过程的切入点,withi(com..*)


this表达式:
this切入点的语义会匹配某一对象上所有的方法执行过程,不能使用..或*这样的通配符。


target表达式:
target(class-name)
target表达式和this完全一样。


args表达式:
args(type-pattern? (,type-pattern)*) 我们可以指定零个、一个或多个type-pattern表达式。




4.通知的类型
问题:
从第5版与AspectWerkz合并起,AspectJ支持将其aspect编写为带有一租AspectJ注解的POJO。
Spring AOP框架也支持这种aspect,但是这些aspect必须在Spring IoC容器中注册方可生效。

解决方案:
要在Spring中注册AspectJ aspect,只需要将他们声明为IoC容器中的Bean实例就行了。
在Spring IoC容器中启用AspectJ,容器将自动为匹配AspectJ aspect的Bean创建代理。

用AspectJ注解编写的aspect只是一个带有@Aspect注解的java类。通知(Advice)是带有一个通知注解的简单Java方法。
AspectJ注解5中通知注解:@Before、@After、@AfterReturning、@AfterThrowing和@Around。

工作原理:
前置通知:
@Before

方式一:简单方式,只是添加注解
@Aspect  
public class LoggingBeforeAspect {  
    private Log log = LogFactory.getLog(LoggingBeforeAspect.class);  
      
    @Before("execution(* *..*.delete(..))")  
    public void logBefore(){  
        log.debug("The method add() begins");  
    }  
}  
然后把这个class注册进IoC就可以,可以以匿名的方式注册  
<aop:aspectj-autoproxy />  
<bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/>  
<bean class="com.partner4java.aspectj.demo1.LoggingBeforeAspect"/>  


方式二:我们还可以拿到连接点
@Aspect  
public class LoggingBeforeAspect1 {  
    private Log log = LogFactory.getLog(LoggingBeforeAspect1.class);  
      
    @Before("execution(* *..*.delete(..))")  
    public void logBefore(JoinPoint joinPoint){  
        log.debug("The method "+ joinPoint.getSignature().getName() +" add() begins");  
    }  
}  


最终通知:
最终通知(after advice)在连接点结束之后执行,不管返回结果还是抛出异常。
@Aspect  
public class LoggingAfterAspect {  
    private Log log = LogFactory.getLog(LoggingAfterAspect.class);  
  
    @After("execution(* *..*.delete(..))")  
    public void logBefore(JoinPoint joinPoint) {  
        log.debug("The method " + joinPoint.getSignature().getName() + " ends");  
    }  
}  

后置通知:
最终通知不管连接点正常返回还是抛出异常都执行。
如果你希望仅当连接点返回时记录,应该用后置通知(after returning advice)替换最终通知。
在后置通知中,你可以在@AfterReturning注解中添加一个returning属性,访问连接点的返回值。这个参数值应该是通知方法的参数名称,用于传入返回值。
@Aspect  
public class LoggingAfterReturnAspect {  
    private Log log = LogFactory.getLog(LoggingAfterReturnAspect.class);  
  
    @AfterReturning(pointcut = "execution(* *..*.delete(..))", returning = "result")  
    public void logBefore(JoinPoint joinPoint, Object result) {  
        log.debug("The method " + joinPoint.getSignature().getName()  
                + " ends with" + result);  
    }  
}  

异常通知:
仅当连接点抛出异常时执行。
@Aspect  
public class LoggingErrorAspect {  
    private Log log = LogFactory.getLog(LoggingErrorAspect.class);  
  
    @AfterThrowing(pointcut = "execution(* *..*.save(..))", throwing = "throwable")  
    public void logAfterThrowing(JoinPoint joinPoint, Throwable throwable) {  
        log.debug("exception " + throwable + " in method"  
                + joinPoint.getSignature().getName());  
    }  
}  

环绕通知:
功能最强大的,可以满足前面的所有需求,而且可以改变返回的数值。
@Aspect  
public class LoggingAroundAspect {  
    private Log log = LogFactory.getLog(LoggingErrorAspect.class);  
      
    @Around("execution(* *..*.find(..))")  
    public Object logAround(ProceedingJoinPoint joinPoint){  
        log.debug("being " + joinPoint.getSignature().getName());  
          
        try {  
            Object result = joinPoint.proceed();  
            log.debug(" end " + result);  
            return result;  
        } catch (Throwable e) {  
            e.printStackTrace();  
        }  
          
        return null;  
    }  
}  

选择通知类型的通用原则是使用满足你的要求的最不强大的类型。

问题:
在编写aspect时,你可以直接在通知注解中嵌入切入点表达式。但是,相同的切入点表达式在多个通知中必须重复。

解决方案:
和许多其他AOP实现一样,AspectJ也允许独立的定义切入点,在多个通知中重用。

工作原理:
如果你是只在本类中共享,可定义一个private的方法,然后放上切入点,在其他的方法上,直接引用其方法名就可以。
@Aspect  
public class LoggingAspectj {  
    @Pointcut("execution(* *..*.find(..))")  
    private void loggingOpertion(){}  
      
    @Before("loggingOpertion()")  
    public void logBefore(){  
          
    }  
}  

当然,你也可以把loggingOpertion设置为public的,但是在同包下需要加上类名@Before("LoggingAspectj.loggingOpertion()"),
如果跨包加上包名。


问题:
有时候,你可能希望为一组现有的对象添加新的状态,跟踪他们的使用情况,如调用次数、最后修改日期等。
如果所有的对象都有相同的基类,这就不成问题。
但是,如果不同的类不在相同的类层次结构中,添加这样的状态就很难。


解决方案:
你可以为你的对象引入一个新的接口和保存状态字段的实现类。然后,你可以编写另一个通知根绝特定的条件改变状态。


工作原理:
定义一个接入的接口和实现类,然后通过AspectJ引入到目标类中。
<aop:aspectj-autoproxy />  
  
<bean id="userDao" class="com.partner4java.aspectj.demo1.UserDaoImpl"/>  
  
<bean class="com.partner4java.aspectj.declare.DeclareLogAspectj"/>  
  
@Aspect  
public class DeclareLogAspectj {  
  
    //value属性标识引入的目标类  
    //引入的接口,为被注解的定义类,实现为defaultImpl指定的类  
    @DeclareParents(value = "com.partner4java.aspectj.demo1.*", defaultImpl = ExecuteLogImpl.class)  
    public ExecuteLog executeLog;  
  
    //  
    @Around("execution(* *.*(..))" + " && this(executeLog)")  
    public Object declareLog(ProceedingJoinPoint joinPoint,  
            ExecuteLog executeLog) {  
        executeLog.setBeginDate(new Date());  
  
        try {  
            Object result = joinPoint.proceed();  
            executeLog.increase();  
            executeLog.setEndDate(new Date());  
            return result;  
        } catch (Throwable e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
}  
  
获取的时候可以直接让被接入的类强转为接入类的接口类型:  
@Test  
public void testAsp(){  
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");  
    userDao.find(new Integer(22));  
      
    ExecuteLog executeLog = (ExecuteLog)userDao;  
    System.out.println(executeLog.getCount());  
    System.out.println(executeLog.getBeginDate());  
}  


当使用@AspectJ自动代理时要强制使用CGLIB,请将<aop:aspectj-autoproxy> 的proxy-target-class属性设置为true:

<aop:aspectj-autoproxy proxy-target-class="true"/>



=====================================================================
解决第二个问题:
问题:
如果你不想以任何理由使用注解的形式。

解决方案:
基于Spring的XML配置方式。

工作原理:
可以去掉<aop:aspectj-autoproxy />开启注解的声明。

<aop:config>  
    <aop:pointcut id="loggingOperation" expression=  
        "within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />  
  
    <aop:pointcut id="validationOperation" expression=  
        "within(com.apress.springrecipes.calculator.ArithmeticCalculator+) || within(com.apress.springrecipes.calculator.UnitCalculator+)" />  
  
    <aop:aspect id="loggingAspect" ref="calculatorLoggingAspect">  
        <aop:before pointcut-ref="loggingOperation"  
            method="logBefore" />  
  
        <aop:after-returning pointcut-ref="loggingOperation"  
            returning="result" method="logAfterReturning" />  
  
        <aop:after-throwing pointcut-ref="loggingOperation"  
            throwing="e" method="logAfterThrowing" />  
  
        <aop:around pointcut-ref="loggingOperation"  
            method="logAround" />  
    </aop:aspect>  
  
    <aop:aspect id="validationAspect" ref="calculatorValidationAspect">  
        <aop:before pointcut-ref="validationOperation"  
            method="validateBefore" />  
    </aop:aspect>  
  
    <aop:aspect id="introduction" ref="calculatorIntroduction">  
        <aop:declare-parents  
            types-matching=  
                "com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"  
            implement-interface=  
                "com.apress.springrecipes.calculator.MaxCalculator"  
            default-impl=  
                "com.apress.springrecipes.calculator.MaxCalculatorImpl" />  
  
        <aop:declare-parents  
            types-matching=  
                "com.apress.springrecipes.calculator.ArithmeticCalculatorImpl"  
            implement-interface=  
                "com.apress.springrecipes.calculator.MinCalculator"  
            default-impl=  
                "com.apress.springrecipes.calculator.MinCalculatorImpl" />  
  
        <aop:declare-parents  
            types-matching=  
                "com.apress.springrecipes.calculator.*CalculatorImpl"  
            implement-interface=  
                "com.apress.springrecipes.calculator.Counter"  
            default-impl=  
                "com.apress.springrecipes.calculator.CounterImpl" />  
  
        <aop:after pointcut=  
            "execution(* com.apress.springrecipes.calculator.*Calculator.*(..)) and this(counter)"  
            method="increaseCount" />  
    </aop:aspect>  
</aop:config>  


这个就不多说了,自己动手,仔细看看就明白了。


《partner4java 讲述Spring入门》之第一步:Spring概述与Spring IoC
http://blog.youkuaiyun.com/partner4java/article/details/8194747


《partner4java 讲述Spring入门》之第二步:Spring AOP
http://blog.youkuaiyun.com/partner4java/article/details/8239721

2025-08-19 19:17:58.177 INFO [ restartedMain] tuate.endpoint.web.EndpointLinksResolver <init>: Exposing 1 endpoint(s) beneath base path '/actuator' 2025-08-19 19:17:58.314 WARN [alina-utility-2] s.autoconfigure.OptionalLiveReloadServer startServer: Unable to start LiveReload server 2025-08-19 19:17:58.430 DEBUG SUCCESS [ restartedMain] SystemCertificateConfigMapper.selectList debug: ==> Preparing: SELECT id_,issuer,service_url,key_store_path,store_password,key_password,key_alias,certificate_path,type_,used_mode,status,created_time,created_user,last_updated_time,last_updated_user,version,serial_number,before_date,after_date,subject_dn,issuer_dn,sig_alg_name,confidence_info FROM t_system_certificate_config WHERE (status = ? AND type_ = ? AND used_mode = ?) ORDER BY last_updated_time DESC 2025-08-19 19:17:58.431 DEBUG SUCCESS [ restartedMain] SystemCertificateConfigMapper.selectList debug: ==> Parameters: 0(String), ssl(String), dev(String) 2025-08-19 19:17:58.487 DEBUG SUCCESS [ restartedMain] SystemCertificateConfigMapper.selectList debug: <== Total: 0 2025-08-19 19:17:58.506 DEBUG SUCCESS [ restartedMain] SystemCertificateConfigMapper.selectList debug: ==> Preparing: SELECT id_,issuer,service_url,key_store_path,store_password,key_password,key_alias,certificate_path,type_,used_mode,status,created_time,created_user,last_updated_time,last_updated_user,version,serial_number,before_date,after_date,subject_dn,issuer_dn,sig_alg_name,confidence_info FROM t_system_certificate_config WHERE (status = ? AND type_ = ? AND used_mode = ?) ORDER BY last_updated_time DESC 2025-08-19 19:17:58.507 DEBUG SUCCESS [ restartedMain] SystemCertificateConfigMapper.selectList debug: ==> Parameters: 0(String), ssl(String), dev(String) 2025-08-19 19:17:58.562 DEBUG SUCCESS [ restartedMain] SystemCertificateConfigMapper.selectList debug: <== Total: 0 2025-08-19 19:17:58.615 INFO [ restartedMain] scovery.provider.DiscoveryJerseyProvider <init>: Using JSON encoding codec LegacyJacksonJson 2025-08-19 19:17:58.615 INFO [ restartedMain] scovery.provider.DiscoveryJerseyProvider <init>: Using JSON decoding codec LegacyJacksonJson 2025-08-19 19:17:58.718 INFO [ restartedMain] scovery.provider.DiscoveryJerseyProvider <init>: Using XML encoding codec XStreamXml 2025-08-19 19:17:58.718 INFO [ restartedMain] scovery.provider.DiscoveryJerseyProvider <init>: Using XML decoding codec XStreamXml 2025-08-19 19:17:58.944 INFO [ restartedMain] ydb.core.internal.license.VersionPrinter info: Flyway Community Edition 7.15.0 by Redgate 2025-08-19 19:17:58.945 INFO [ restartedMain] .internal.database.base.BaseDatabaseType info: Database: jdbc:mysql://7.220.86.254:3306/parntercenter (MySQL 5.7) 2025-08-19 19:18:00.238 INFO [ restartedMain] nal.schemahistory.JdbcTableSchemaHistory info: Repair of failed migration in Schema History table `parntercenter`.`flyway_schema_history` not necessary. No failed migration detected. 2025-08-19 19:18:00.733 INFO [ restartedMain] .flywaydb.core.internal.command.DbRepair info: Successfully repaired schema history table `parntercenter`.`flyway_schema_history` (execution time 00:01.174s). 2025-08-19 19:18:01.182 INFO [ restartedMain] ydb.core.internal.license.VersionPrinter info: Flyway Community Edition 7.15.0 by Redgate 2025-08-19 19:18:02.447 INFO [ restartedMain] lywaydb.core.internal.command.DbValidate info: Successfully validated 911 migrations (execution time 00:00.589s) 2025-08-19 19:18:03.182 INFO [ restartedMain] flywaydb.core.internal.command.DbMigrate info: Current version of schema `parntercenter`: 90.6 2025-08-19 19:18:03.239 INFO [ restartedMain] flywaydb.core.internal.command.DbMigrate info: Schema `parntercenter` is up to date. No migration necessary. 2025-08-19 19:18:03.633 DEBUG SUCCESS [ restartedMain] .sysorg.mapper.SysOrgMapper.selectAllOrg debug: ==> Preparing: SELECT org_id orgId, short_org_name orgName, short_org_name_en orgNameEn, parent_id parentId, sort, industry industry, org_type orgType, org_level orgLevel, is_operation isOperation, city_id cityId, IF(expiry_time > NOW(), '1', '0') isValid FROM sys_org org 2025-08-19 19:18:03.634 DEBUG SUCCESS [ restartedMain] .sysorg.mapper.SysOrgMapper.selectAllOrg debug: ==> Parameters: 2025-08-19 19:18:03.693 DEBUG SUCCESS [ restartedMain] .sysorg.mapper.SysOrgMapper.selectAllOrg debug: <== Total: 199 2025-08-19 19:18:03.935 INFO [ restartedMain] .security.web.DefaultSecurityFilterChain <init>: Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@77dfad3b, org.springframework.security.web.session.ForceEagerSessionCreationFilter@323c042, org.springframework.security.web.session.ForceEagerSessionCreationFilter@42cd8fcf, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7ffa94ed, org.springframework.security.web.context.SecurityContextPersistenceFilter@586f69ac, org.springframework.security.web.header.HeaderWriterFilter@233a9cd7, com.huawei.kunpeng.center.security.config.CsrfTokenResponseHeaderFilter@471155c5, org.springframework.security.web.authentication.logout.LogoutFilter@3832b7c3, org.springframework.security.web.session.ConcurrentSessionFilter@54bfad72, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1e750439, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@22f3f19b, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4dad8e69, org.springframework.security.web.session.SessionManagementFilter@1631de42, org.springframework.security.web.access.ExceptionTranslationFilter@1b049c3b, com.huawei.kunpeng.center.security.config.handler.CustomizeAbstractSecurityInterceptor@538cb1b7, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2a8fdc05] 2025-08-19 19:18:05.772 WARN [ restartedMain] iguration$LoadBalancerCaffeineWarnLogger logWarning: Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath. 2025-08-19 19:18:05.961 WARN [ restartedMain] ybatisplus.core.metadata.TableInfoHelper initTableFields: Can not find table primary key in Class: "com.huawei.kunpeng.center.entity.meetinginfo.TMeetingCOOEntity". 2025-08-19 19:18:06.331 INFO [ restartedMain] cloud.netflix.eureka.InstanceInfoFactory create: Setting initial instance status as: STARTING 2025-08-19 19:18:06.339 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient <init>: Initializing Eureka in region us-east-1 2025-08-19 19:18:06.350 INFO [ restartedMain] hared.resolver.aws.ConfigClusterResolver getClusterEndpoints: Resolving eureka endpoints via configuration 2025-08-19 19:18:06.363 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient fetchRegistry: Disable delta property : false 2025-08-19 19:18:06.363 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient fetchRegistry: Single vip registry refresh property : null 2025-08-19 19:18:06.363 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient fetchRegistry: Force full registry fetch : false 2025-08-19 19:18:06.363 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient fetchRegistry: Application is null : false 2025-08-19 19:18:06.363 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient fetchRegistry: Registered Applications size is zero : true 2025-08-19 19:18:06.364 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient fetchRegistry: Application version is -1: true 2025-08-19 19:18:06.364 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient getAndStoreFullRegistry: Getting all instance registry info from the eureka server 2025-08-19 19:18:06.564 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient getAndStoreFullRegistry: The response status is 200 2025-08-19 19:18:06.567 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient initScheduledTasks: Starting heartbeat executor: renew interval is: 5 2025-08-19 19:18:06.570 INFO [ restartedMain] netflix.discovery.InstanceInfoReplicator <init>: InstanceInfoReplicator onDemand update allowed rate per min is 4 2025-08-19 19:18:06.574 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient <init>: Discovery Client initialized at timestamp 1755602286573 with initial instances count: 2 2025-08-19 19:18:06.586 INFO [ restartedMain] ka.serviceregistry.EurekaServiceRegistry register: Registering application PARTNER-SERVICE with eureka with status UP 2025-08-19 19:18:06.588 INFO [ restartedMain] com.netflix.discovery.DiscoveryClient notify: Saw local status change event StatusChangeEvent [timestamp=1755602286588, current=UP, previous=STARTING] 2025-08-19 19:18:06.591 INFO [nfoReplicator-0] com.netflix.discovery.DiscoveryClient register: DiscoveryClient_PARTNER-SERVICE/127.0.0.1:8080: registering service... 2025-08-19 19:18:06.591 INFO [ restartedMain] g.apache.coyote.http11.Http11NioProtocol log: Starting ProtocolHandler ["https-jsse-nio-8080"] 2025-08-19 19:18:06.605 WARN [ restartedMain] org.apache.tomcat.util.net.jsse.JSSEUtil log: Some of the specified [protocols] are not supported by the SSL engine and have been skipped: [[TLSv1.3]] 2025-08-19 19:18:06.697 INFO [nfoReplicator-0] com.netflix.discovery.DiscoveryClient register: DiscoveryClient_PARTNER-SERVICE/127.0.0.1:8080 - registration status: 204 2025-08-19 19:18:06.783 INFO [ restartedMain] .tomcat.util.net.NioEndpoint.certificate log: Connector [https-jsse-nio-8080], TLS virtual host [_default_], certificate type [UNDEFINED] configured from keystore [file:/D:/workspace/PartnerCenter/project-configs/certificate/ssl/dev/cert.pfx] using alias [tomcat] with trust store [null] 2025-08-19 19:18:06.796 INFO [ restartedMain] boot.web.embedded.tomcat.TomcatWebServer start: Tomcat started on port(s): 8080 (https) with context path '/partnerservice' 2025-08-19 19:18:06.796 INFO [ restartedMain] ceregistry.EurekaAutoServiceRegistration onApplicationEvent: Updating port to 8080 2025-08-19 19:18:09.299 ERROR [ restartedMain] plugins.DocumentationPluginsBootstrapper scanDocumentation: Unable to scan documentation context default java.lang.NullPointerException: null at springfox.documentation.schema.Example.equals(Example.java:131) ~[springfox-core-3.0.0.jar:3.0.0] at java.util.Objects.equals(Objects.java:59) ~[?:1.8.0_262] at springfox.documentation.service.RequestParameter.equals(RequestParameter.java:132) ~[springfox-core-3.0.0.jar:3.0.0] at java.util.HashMap.putVal(HashMap.java:648) ~[?:1.8.0_262] at java.util.HashMap.put(HashMap.java:612) ~[?:1.8.0_262] at java.util.HashSet.add(HashSet.java:220) ~[?:1.8.0_262] at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) ~[?:1.8.0_262] at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[?:1.8.0_262] at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) ~[?:1.8.0_262] at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[?:1.8.0_262] at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384) ~[?:1.8.0_262] at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) ~[?:1.8.0_262] at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) ~[?:1.8.0_262] at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[?:1.8.0_262] at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:1.8.0_262] at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566) ~[?:1.8.0_262] at springfox.documentation.spring.web.readers.operation.OperationParameterReader.apply(OperationParameterReader.java:93) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.DocumentationPluginsManager.operation(DocumentationPluginsManager.java:144) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.readers.operation.ApiOperationReader.read(ApiOperationReader.java:72) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.scanners.CachingOperationReader.lambda$new$0(CachingOperationReader.java:43) ~[springfox-spring-web-3.0.0.jar:3.0.0] at java.util.HashMap.computeIfAbsent(HashMap.java:1127) ~[?:1.8.0_262] at springfox.documentation.spring.web.scanners.CachingOperationReader.read(CachingOperationReader.java:48) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.scanners.ApiDescriptionReader.read(ApiDescriptionReader.java:72) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.scanners.ApiListingScanner.scan(ApiListingScanner.java:169) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.scanners.ApiDocumentationScanner.scan(ApiDocumentationScanner.java:67) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.scanDocumentation(AbstractDocumentationPluginsBootstrapper.java:96) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.bootstrapDocumentationPlugins(AbstractDocumentationPluginsBootstrapper.java:82) ~[springfox-spring-web-3.0.0.jar:3.0.0] at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100) ~[springfox-spring-web-3.0.0.jar:3.0.0] at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:179) ~[spring-context-5.3.31.jar:5.3.31] at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.31.jar:5.3.31] at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:357) ~[spring-context-5.3.31.jar:5.3.31] at java.lang.Iterable.forEach(Iterable.java:75) ~[?:1.8.0_262] at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:156) ~[spring-context-5.3.31.jar:5.3.31] at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:124) ~[spring-context-5.3.31.jar:5.3.31] at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:946) ~[spring-context-5.3.31.jar:5.3.31] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:594) ~[spring-context-5.3.31.jar:5.3.31] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.18.jar:2.7.18] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[spring-boot-2.7.18.jar:2.7.18] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:409) ~[spring-boot-2.7.18.jar:2.7.18] at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.18.jar:2.7.18] at com.huawei.kunpeng.center.PartnerApplication.main(PartnerApplication.java:72) ~[classes/:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_262] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_262] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_262] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_262] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-2.7.18.jar:2.7.18] 2025-08-19 19:18:09.343 INFO SUCCESS [ restartedMain] huawei.kunpeng.center.PartnerApplication logStarted: Started PartnerApplication in 84.453 seconds (JVM running for 85.594) 2025-08-19 19:18:09.359 INFO SUCCESS [ restartedMain] huawei.kunpeng.center.PartnerApplication loadLocalCache: PartnerApplication.loadLocalCache load cache start!!! 2025-08-19 19:18:09.370 DEBUG SUCCESS [ restartedMain] .excel.ExcelImportTitleMapper.selectList debug: ==> Preparing: SELECT id,sheet_name,parent_title_name,title_name,is_need,is_unique,entity_class_name AS className,service_class_name,field_name,field_enum,field_enum_mapping,split_char,is_public_default,dict_id,del_flag,create_time AS createdTime,last_updated_time FROM t_excel_title_head WHERE (del_flag = ?) 2025-08-19 19:18:09.371 DEBUG SUCCESS [ restartedMain] .excel.ExcelImportTitleMapper.selectList debug: ==> Parameters: 0(Integer) 2025-08-19 19:18:09.489 DEBUG SUCCESS [ restartedMain] .excel.ExcelImportTitleMapper.selectList debug: <== Total: 102 2025-08-19 19:18:09.510 DEBUG SUCCESS [ restartedMain] .excel.ExcelExportTitleMapper.selectList debug: ==> Preparing: SELECT id,row_num,field,title,config_name,row_span,col_span,width,high,align,foreground_color,font_color,is_dict,del_flag AS isDel FROM t_excel_export_head WHERE (del_flag = ?) 2025-08-19 19:18:09.511 DEBUG SUCCESS [ restartedMain] .excel.ExcelExportTitleMapper.selectList debug: ==> Parameters: 0(Integer) 2025-08-19 19:18:09.638 DEBUG SUCCESS [ restartedMain] .excel.ExcelExportTitleMapper.selectList debug: <== Total: 1037 2025-08-19 19:18:09.678 DEBUG SUCCESS [ restartedMain] onaryDetailTreeMapper.selectDictByDictId debug: ==> Preparing: select * from t_dictionary_detail where status_=0 2025-08-19 19:18:09.679 DEBUG SUCCESS [ restartedMain] onaryDetailTreeMapper.selectDictByDictId debug: ==> Parameters: 2025-08-19 19:18:10.074 DEBUG SUCCESS [ restartedMain] onaryDetailTreeMapper.selectDictByDictId debug: <== Total: 8606 2025-08-19 19:18:11.741 DEBUG SUCCESS [ restartedMain] per.TDictionaryTreeMapper.selectDictList debug: ==> Preparing: select t.* from t_dictionary t where 1=1 and status_!=-1 and id_ != 176 2025-08-19 19:18:11.741 DEBUG SUCCESS [ restartedMain] per.TDictionaryTreeMapper.selectDictList debug: ==> Parameters: 2025-08-19 19:18:11.870 DEBUG SUCCESS [ restartedMain] per.TDictionaryTreeMapper.selectDictList debug: <== Total: 532 2025-08-19 19:18:11.895 DEBUG SUCCESS [ restartedMain] mapper.AdaptationProjectMapper.getRegion debug: ==> Preparing: SELECT id_ id, parent_id parentId, name_ `name`, area_level level FROM t_administrative_region WHERE id_ != '86' and status_ = '0' 2025-08-19 19:18:11.895 DEBUG SUCCESS [ restartedMain] mapper.AdaptationProjectMapper.getRegion debug: ==> Parameters: 2025-08-19 19:18:12.016 DEBUG SUCCESS [ restartedMain] mapper.AdaptationProjectMapper.getRegion debug: <== Total: 3671 2025-08-19 19:18:12.066 INFO SUCCESS [ restartedMain] huawei.kunpeng.center.PartnerApplication loadLocalCache: PartnerApplication.loadLocalCache load cache end!!! 2025-08-19 19:18:12.066 INFO SUCCESS [ restartedMain] huawei.kunpeng.center.PartnerApplication loadLocalCache: PartnerApplication.loadLocalCache load cache start!!! 2025-08-19 19:18:12.066 DEBUG SUCCESS [ restartedMain] .excel.ExcelImportTitleMapper.selectList debug: ==> Preparing: SELECT id,sheet_name,parent_title_name,title_name,is_need,is_unique,entity_class_name AS className,service_class_name,field_name,field_enum,field_enum_mapping,split_char,is_public_default,dict_id,del_flag,create_time AS createdTime,last_updated_time FROM t_excel_title_head WHERE (del_flag = ?) 2025-08-19 19:18:12.066 DEBUG SUCCESS [ restartedMain] .excel.ExcelImportTitleMapper.selectList debug: ==> Parameters: 0(Integer) 2025-08-19 19:18:12.129 DEBUG SUCCESS [ restartedMain] .excel.ExcelImportTitleMapper.selectList debug: <== Total: 102 2025-08-19 19:18:12.131 DEBUG SUCCESS [ restartedMain] .excel.ExcelExportTitleMapper.selectList debug: ==> Preparing: SELECT id,row_num,field,title,config_name,row_span,col_span,width,high,align,foreground_color,font_color,is_dict,del_flag AS isDel FROM t_excel_export_head WHERE (del_flag = ?) 2025-08-19 19:18:12.131 DEBUG SUCCESS [ restartedMain] .excel.ExcelExportTitleMapper.selectList debug: ==> Parameters: 0(Integer) 2025-08-19 19:18:12.197 DEBUG SUCCESS [ restartedMain] .excel.ExcelExportTitleMapper.selectList debug: <== Total: 1037 2025-08-19 19:18:12.201 DEBUG SUCCESS [ restartedMain] onaryDetailTreeMapper.selectDictByDictId debug: ==> Preparing: select * from t_dictionary_detail where status_=0 2025-08-19 19:18:12.203 DEBUG SUCCESS [ restartedMain] onaryDetailTreeMapper.selectDictByDictId debug: ==> Parameters: 2025-08-19 19:18:12.499 DEBUG SUCCESS [ restartedMain] onaryDetailTreeMapper.selectDictByDictId debug: <== Total: 8606 2025-08-19 19:18:13.996 DEBUG SUCCESS [ restartedMain] per.TDictionaryTreeMapper.selectDictList debug: ==> Preparing: select t.* from t_dictionary t where 1=1 and status_!=-1 and id_ != 176 2025-08-19 19:18:13.997 DEBUG SUCCESS [ restartedMain] per.TDictionaryTreeMapper.selectDictList debug: ==> Parameters: 2025-08-19 19:18:14.111 DEBUG SUCCESS [ restartedMain] per.TDictionaryTreeMapper.selectDictList debug: <== Total: 532 2025-08-19 19:18:14.133 INFO SUCCESS [ restartedMain] huawei.kunpeng.center.PartnerApplication loadLocalCache: PartnerApplication.loadLocalCache load cache end!!! 2025-08-19 19:18:14.647 INFO [1)-10.165.36.79] e.[Tomcat].[localhost].[/partnerservice] log: Initializing Spring DispatcherServlet 'dispatcherServlet' 2025-08-19 19:18:14.647 INFO [1)-10.165.36.79] gframework.web.servlet.DispatcherServlet initServletBean: Initializing Servlet 'dispatcherServlet' 2025-08-19 19:18:14.647 INFO [1)-10.165.36.79] gframework.web.servlet.DispatcherServlet initServletBean: Completed initialization in 0 ms 2025-08-19 19:18:14.718 INFO [4)-10.165.36.79] com.alibaba.druid.pool.DruidDataSource init: {dataSource-3} inited 2025-08-19 19:18:15.326 INFO [4)-10.165.36.79] com.alibaba.druid.pool.DruidDataSource init: {dataSource-4} inited
最新发布
08-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值