Spring结合AspectJ的研究
本文阐述以下内容:
1、AspectJ是什么及使用方式
2、Spring AOP和AspectJ的区别
3、Spring结合AspectJ的使用方法和原理
4、Spring注解方式使用AspectJ遇到的问题
5、总结
一、AspectJ是什么
提到面向切面编程(AOP,Aspect Oriented Programming),大家首先想到的是Spring AOP,或许有人也会想到AspectJ,也有人搞不清楚这两者的区别。
AOP是一种编程思想,AOP的作用是在不修改源程序的情况下修改源程序的动态执行流和静态属性。AspectJ是一种基于Java平台的面向切面编程的语言,兼容Java平台,可以无缝扩展,易学易用。干净的模块化横切关注点(也就是说单纯,基本上无侵入),如错误检查和处理,同步,上下文敏感的行为,性能优化,监控和记录,调试支持,多目标的协议。
AspectJ有单独的语法,AspectJ项目的源文件经AspectJ Compiler(ajc)编译后生成完全兼容Java语法的Class文件,可以看出AspectJ可以在编译期将Advice功能织入连接点。AspectJ还可以在类加载期切面织入(Load Time Weaver,LTW),此时就需要在jvm启动参数配置 -javaagent:[path to aspectj-weaver.jar],这样在类加载时,AspectJ就可以将Advice功能织入连接点。比起编译期织入更方便,不需要用专用的ajc编译器编译,利用java.lang.Instrument包提供的工具在Java程序运行时动态修改系统中的Class类型。
二、Spring AOP和AspectJ的区别
Spring AOP是常用的AOP实现方式,使用方便,无需做项目工程之外的配置和操作,纯代码实现,深受广大开发人员的喜爱。Spring AOP是一个基于代理的AOP框架。运行时通过创建目标对象的代理类,对目标对象进行增强,主要使用JDK动态代理或CGLIB代理的方式。而AspectJ在运行时不做任何事情,在编译阶段或类加载阶段,Advice就织入切面到代码中了。这是和Spring AOP的本质区别。由Spring AOP使用的动态代理模式可知,通过实现目标的父接口或生成目标的子类来实现增强,因此只能对Spring管理范围内的bean的方法执行进行连接。AspectJ就要灵活很多,不仅能对方法执行进行连接,此外还支持方法调用、构造器调用、构造器执行、对象初始化、字段引用、字段赋值、类静态初始化、异常处理执行这些连接点。
此外,在Spring AOP中,切面不适用于同一个类中调用的方法。当我们在同一个类中调用一个方法时,我们并没有调用Spring AOP提供的代理的方法。解决这个问题,可以在不同的beans中定义一个独立的方法,或者获取到本身的代理对象,或者使用AspectJ。
性能方便,运行前织入比运行时织入快很多。Spring AOP是基于代理的框架,因此应用运行时会有目标类的代理对象生成。另外,每个切面还有一些方法调用,这会对性能造成影响。AspectJ不同于Spring AOP,是在应用执行前织入切面到代码中,没有额外的运行时开销。
简而言之,选择很大程度上取决我们的需求:
●框架:如果应用程序不使用Spring框架,那么我们别无选择,只能放弃使用Spring AOP的想法,因为它无法管理任何超出spring容器范围的东西。但是,如果我们的应用程序完全是使用Spring框架创建的,那么我们可以使用Spring AOP,因为它很直接便于学习和应用。
●灵活性:鉴于有限的连接点支持,Spring AOP并不是一个完整的AOP解决方案,但它解决了程序员面临的最常见的问题。 如果我们想要深入挖掘并利用AOP达到其最大能力,并希望获得来自各种可用连接点的支持,那么AspectJ是最佳选择。
●性能:如果我们使用有限的切面,那么性能差异很小。但是,有时候应用程序有数万个切面的情况。在这种情况下,我们不希望使用运行时织入,所以最好选择AspectJ。已知AspectJ比Spring AOP快8到35倍。
●共同优点:这两个框架是完全兼容的。我们可以随时利用Spring AOP,并且仍然使用AspectJ来获得前者不支持的连接点。
三、Spring结合AspectJ的使用方法和原理
Spring AOP和AspectJ都可以使用AspectJ注解的方式来实现,实际上,Spring AOP为了遵循规范,或者和AspectJ保持兼容,借助了AspectJ的注解风格和AOP联盟定义的部分底层接口,不能想当然的认为Spring AOP是借助AspectJ来实现的,在原理上Spring AOP和AspectJ没有关系。
Spring AOP如何使用就不细说了,下面说下Spring结合AspectJ注解方式的使用。
切面声明:
@Aspect
public class MyAspect { @Pointcut("execution(* com.mzsea.spring.service.*.*(..))")
private void pointCut() { } @Around("pointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before");
Object obj = joinPoint.proceed();
System.out.println("after");
return obj;
}
业务逻辑:
@Component
public class UserServiceImpl { public void add() {
System.out.println("hello user!");
}
Spring配置:
<context:load-time-weaver/>
<context:component-scan base-package="com.mzsea.spring"/>
AspectJ配置(class目录下META-INF/aop.xml):
<aspectj>
<weaver options="-verbose -debug -showWeaveInfo">
<include within="com.mzsea.spring..*"/>
</weaver>
<aspects>
<aspect name="com.mzsea.spring.aspectj.MyAspect"/>
</aspects>
</aspectj>
Main方法:
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
UserServiceImpl userServiceImpl = applicationContext.getBean(UserServiceImpl.class);
userServiceImpl.add();
}
运行结果:
before
hello user!
after
以上就是一个简单的AspectJ应用,和Spring AOP相比,配置略有不同。
<aop:aspectj-autoproxy/>换成<context:load-time-weaver/>
还多了META-INF/aop.xml配置,并且在运行时需要指定java agent参数-javaagent:spring-instrument-{version}.jar
@Aspect注解的类并不在spring的bean管辖之内,完全由AspectJ使用。Spring只是整合了AspectJ的入口,让AspectJ在类加载时改变Class字节码,然后就与AspectJ无关了。
接下来分析下spring是如何结合AspectJ的。
Java引入了java.lang.Instrument包,该包提供了一些工具帮助开发人员在Java程序运行时,动态修改系统中的Class类型,并不需要自定义类加载器,使用该软件包的一个关键组件就是Java agent。Java agent的使用规范就是在指定的jar包内的MANIFEST.MF文件指定Premain-Class项,Premain-Class指定的那个类必须实现premain()方法。
打开spring-instrument.jar,找到Premain-Class指定的类,代码如下:
private static volatile Instrumentation instrumentation;
public static void premain(String agentArgs, Instrumentation inst) {
instrumentation = inst;
}
发现超级简单啊,就是把Instrumentation参数赋值给静态变量了,一看感觉没起任何作用啊,怎么可能就这么简单就实现了,确实如你所想,spring在下一盘大棋,这边只是暴露出Instrumentation,后面如何对Instrumentation操作就是关键了。
Spring启动过程中,会解析<context:load-time-weaver/>这个配置。
我们知道这个xml元素会由ContextNamespaceHandler处理,打开此类:
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
果然看到load-time-weaver对应的解析器,LoadTimeWeaverBeanDefinitionParser。
解析器通过配置属性或者自动识别有无META-INF/aop.xml来判断是否开启AspectJWeaving,开启了就会注册相关的BeanDefinition。这里会注册
AspectJWeavingEnabler、DefaultContextLoadTimeWeaver这2个BeanDefinition。
Spring配置xml的解析在著名的refresh()方法的obtainFreshBeanFactory()阶段就已经完成,此时xml中定义的配置都已经转换成各种BeanDefinition对象存储在BeanFactory中,接着在prepareBeanFactory()中有这样的代码:
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
意思就是如果存在名为“loadTimeWeaver”的bean,则注册LoadTimeWeaverAwareProcessor这个BeanPostProcessor,这个很好理解,就是实现了LoadTimeWeaverAware接口的bean会自动注入“loadTimeWeaver”这个bean,和其他的Aware接口一个道理。setTempClassLoader这个操作是创建一个临时ClassLoader,ContextTypeMatchClassLoader,从名字大概能看出是上下文类型匹配类加载器的意思,但是到底有何用意暂时还不得而知。暂且留下疑问。
refresh()在执行完prepareBeanFactory()后,接着执行postProcessBeanFactory()、invokeBeanFactoryPostProcessors()、registerBeanPostProcessors()等关键操作,最后阶段还会初始化不是懒加载的单例bean。
invokeBeanFactoryPostProcessors()是执行BeanFactory后处理器,此时BeanFactory已经加载结束,正式触发BeanFactory后处理器的时候,这里是非常重要的扩展点,包括Spring本身,都利用这个扩展点干很多事。
回看AspectJWeavingEnabler,其实就是BeanFactoryPostProcessor的实现类之一,找到postProcessBeanFactory方法:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader);
}
public static void enableAspectJWeaving(LoadTimeWeaver weaverToUse, ClassLoader beanClassLoader) {
if (weaverToUse == null) {
if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader);
}
else {
throw new IllegalStateException("No LoadTimeWeaver available");
}
}
weaverToUse.addTransformer(
new AspectJClassBypassingClassFileTransformer(new ClassPreProcessorAgentAdapter()));
}
在执行postProcessBeanFactory时,作为bean的AspectJWeavingEnabler已经实例化了,已经注入loadTimeWeaver为DefaultContextLoadTimeWeaver,DefaultContextLoadTimeWeaver中会根据不同的应用环境(Tomcat、GlassFish、JBoss、WebSphere、WebLogic、默认)创建相应的LoadTimeWeaver,因为不同的应用容器都实现了自己的类加载器,注册transformer的方式各有差异,默认环境中使用的类加载器是sun.misc.Launcher.AppClassLoader,DefaultContextLoadTimeWeaver包装了实际的LoadTimeWeaver,是装饰器模式的应用,LoadTimeWeaver提供统一的接口屏蔽了不同类加载器注册transformer的差异,实现一致处理。这里实际创建了InstrumentationLoadTimeWeaver,上述代码13行调用了addTransformer(),最终调用了Instrumentation.addTransformer(),参数为通过委托模式包装过的AspectJ的ClassPreProcessorAgentAdapter,至此,这就是通过Spring整合AspectJ的过程,至于具体的类加载阶段的处理则由AspectJ接管。
可见Spring通过BeanFactoryPostProcessor对AspectJ进行了整合,在这个阶段,用户自定义的Bean还没有初始化,对应的Class也大概率未加载,整合后,加载Class大部分情况下就会被AspectJ拦截,根据配置进行字节码修改,实现切面增强。如果需要增强的Class在Spring和AspectJ整合之前就已经加载过了,根据ClassLoader的加载规则可知,对于同一个ClassLoader,同一Class很少情况下会加载两次(Class被gc回收的条件苛刻),此时需要增强的Class就错过了切面织入过程,AOP就失效了。所以需要尽早的对AspectJ进行了整合,BeanFactoryPostProcessor是Spring初始化过程中比较靠前的扩展点,AspectJ在此整合不失为一个合理的时机。
四、Spring注解方式使用AspectJ遇到的问题
从上节中了解的Spring结合AspectJ的原理,在使用时,更倾向于简单的配置。
现在流行的Spring Boot项目结构,提倡简洁配置,舍弃xml,Spring Boot针对一些常见配置做了默认处理,用户无需配置过多,结合Spring提供的xml与注解对应关系,使应用结构大为简化。
<context:load-time-weaver/>这个配置,与之对应的注解配置为@EnableLoadTimeWeaving,只要在@Configuration声明的类上配置上@EnableLoadTimeWeaving,应用启动过程中就可以解析注解,生成对应的BeanDefinition。
ConfigurationClassPostProcessor这个类为识别Spring Class注解的入口,@Configuration、@ComponentScans、@ImportResource、@Import等等这些注解,都由ConfigurationClassPostProcessor负责解析,生成BeanDefinition,具体的解析过程,可以跟踪postProcessBeanFactory观得全貌。
查看@EnableLoadTimeWeaving代码:
@Import(LoadTimeWeavingConfiguration.class)
public @interface EnableLoadTimeWeaving {
…
}
发现对这个注解又声明了@Import注解,@Import的作用就和xml配置中的import标签类似,用于加载指定参数中的bean配置,查看LoadTimeWeavingConfiguration代码:
@Bean(name = ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public LoadTimeWeaver loadTimeWeaver() {
LoadTimeWeaver loadTimeWeaver = null; if (this.ltwConfigurer != null) {
// The user has provided a custom LoadTimeWeaver instance
loadTimeWeaver = this.ltwConfigurer.getLoadTimeWeaver();
} if (loadTimeWeaver == null) {
// No custom LoadTimeWeaver provided -> fall back to the default
loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader);
} AspectJWeaving aspectJWeaving = this.enableLTW.getEnum("aspectjWeaving");
switch (aspectJWeaving) {
case DISABLED:
// AJ weaving is disabled -> do nothing
break;
case AUTODETECT:
if (this.beanClassLoader.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) == null) {
// No aop.xml present on the classpath -> treat as 'disabled'
break;
}
// aop.xml is present on the classpath -> enable
AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader);
break;
case ENABLED:
AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader);
break;
} return loadTimeWeaver;
}
看到了熟悉的“loadTimeWeaver”配置。
loadTimeWeaver实例化过程中,从代码16行开始,通过判断配置的aspectJWeaving属性来决定启用AspectJWeaving,代码27行,和前一小节提及的AspectJWeavingEnabler中postProcessBeanFactory调用了同样的方法,由此可见,xml配置方式和注解方式最后都殊途同归,只不过开始阶段的解析不一样。
实践,舍去xml,换上注解:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig { }
启动项目,预计结果应该和之前一样,可是事与愿违,发现切面没被织入,业务代码没有增强。
配置仔细推敲了好几遍,还是不行,为什么xml方式配置就可以,注解方式就失效了呢。
猜想,可能是需要被增强的Class在AspectJ生效前被加载了!
打开JVM参数-verbose,控制台会打印class加载信息,观察class加载情况。
打开AspectJ调试参数:<weaver options="-verbose -debug -showWeaveInfo">
观察两种配置下的UserServiceImpl.class加载位置。
AspectJ调试日志:
[AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.8.13 built on Wednesday Nov 15, 2017 at 19:26:44 GMT
[AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[AppClassLoader@18b4aac2] info using configuration /D:/Workspaces/eclipse/aspectj/target/classes/META-INF/aop.xml
意外的是,xml配置和注解配置在AspectJ加载之前都加载了UserServiceImpl.class,但是加载日志有所区别:
Xml配置下UserServiceImpl.class加载:
[Loaded com.mzsea.spring.service.UserServiceImpl from __JVM_DefineClass__]
注解配置下UserServiceImpl.class加载:
[Loaded com.mzsea.spring.service.UserServiceImpl from file:/D:/Workspaces/eclipse/aspectj/target/classes/]
比较发现一个是从JVM内部加载,一个是从class文件加载,这可能就是产生问题所在。
反复调试加载位置上下文,发现在调用ConfigurationClassPostProcessor.postProcessBeanFactory()中有加载UserServiceImpl.class,问题浮出水面,因为同一个ClassLoader对同一个Class只加载一次的规则,还没有执行到LoadTimeWeaver相关的代码,UserServiceImpl.class就已经被加载了,后面AspectJ加载后,自然不会再对UserServiceImpl.class拦截增强。
虽然两种不同的配置都有加载UserServiceImpl.class,但是从日志上看就是有区别的。
深入UserServiceImpl.class初始加载的代码:org.springframework.beans.factory.support.AbstractBeanFactory#doResolveBeanClass
private Class<?> doResolveBeanClass(RootBeanDefinition mbd, Class<?>... typesToMatch)
throws ClassNotFoundException { ClassLoader beanClassLoader = getBeanClassLoader();
ClassLoader classLoaderToUse = beanClassLoader;
if (!ObjectUtils.isEmpty(typesToMatch)) {
// When just doing type checks (i.e. not creating an actual instance yet),
// use the specified temporary class loader (e.g. in a weaving scenario).
ClassLoader tempClassLoader = getTempClassLoader();
if (tempClassLoader != null) {
classLoaderToUse = tempClassLoader;
if (tempClassLoader instanceof DecoratingClassLoader) {
DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader;
for (Class<?> typeToMatch : typesToMatch) {
dcl.excludeClass(typeToMatch.getName());
}
}
}
}
String className = mbd.getBeanClassName();
if (className != null) {
Object evaluated = evaluateBeanDefinitionString(className, mbd);
if (!className.equals(evaluated)) {
// A dynamically resolved expression, supported as of 4.2...
if (evaluated instanceof Class) {
return (Class<?>) evaluated;
}
else if (evaluated instanceof String) {
return ClassUtils.forName((String) evaluated, classLoaderToUse);
}
else {
throw new IllegalStateException("Invalid class name expression result: " + evaluated);
}
}
// When resolving against a temporary class loader, exit early in order
// to avoid storing the resolved Class in the bean definition.
if (classLoaderToUse != beanClassLoader) {
return ClassUtils.forName(className, classLoaderToUse);
}
}
return mbd.resolveBeanClass(beanClassLoader);
}
发现此处在对class加载时会选择不同的ClassLoader,beanClassLoader和tempClassLoader。getTempClassLoader()即是上文
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
如果tempClassLoader为null,就使用beanClassLoader,beanClassLoader显然是加载bean class用的,如果没有tempClassLoader,bean class就被提前加载了,导致AspectJ失效。
查看方法栈,发现调用doResolveBeanClass的外层方法有:
AbstractBeanFactory#isFactoryBean()
ListableBeanFactory#getBeanNamesForType()
可以看到,在spring启动过程中,经常需要通过判定bean的类型去做处理,如何知道bean的类型,就需要再加bean对应的Class,这就是问题所在。
启动过程中,prepareBeanFactory()中判断beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)的目的就是如果存在“loadTimeWeaver”,那么就设置tempClassLoader,而使用注解方式配置,使“loadTimeWeaver”的定义延迟到invokeBeanFactoryPostProcessors()中了,虽然spring也在invokeBeanFactoryPostProcessors()中进行了补救:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
第一行调用中处理了各种注解,过程中调用了isFactoryBean()、getBeanNamesForType()等判断类型的方法,由于TempClassLoader为null,使用beanClassLoader加载了大部分bean class,虽然生成了“loadTimeWeaver”,所以接下来的setTempClassLoader也没多大作用了。
那如果用注解配置下是不是及时检查“loadTimeWeaver”设置TempClassLoader就可以了呢?
AspectJWeavingEnabler#enableAspectJWeaving()这是结合的关键,这个方法不执行,AspectJ就不起作用。xml配置的时候,这个方法在postProcessBeanFactory就执行了,还算靠前;但是注解配置的时候,这个方法却延迟到“loadTimeWeaver”实例化时进行,bean实例化已经是spring启动过程的尾声了,在refresh()方法末尾的finishBeanFactoryInitialization()中才进行,而且bean的实例化先后顺序不固定,有可能需要织入增强逻辑的bean先实例化,而“loadTimeWeaver”后实例化,此时AspectJ是彻底不起作用了。
总之,要使AspectJ起作用,必须尽早发现“loadTimeWeaver”,setTempClassLoader,尽早加载AspectJ,postProcessBeanFactory()是比较早的机会,但是也不能百分百避免bean class在AspectJ前被beanClassLoader加载,此处为Spring的一个bug,修复需要重新调整代码结构,有空会提交PR。
五、总结
通过分析,Spring将AspectJ融入体系不是容易的事,绕了很多路,稍不注意AspectJ就不起作用。使用另一个ClassLoader去加载Class用于做bean的类型的判定,而不影响本身bean class的加载,AspectJ的加载点很重要,尽早加载越好。
Spring结合AspectJ的研究的更多相关文章
- Spring中WebApplicationContext的研究
Spring中WebApplicationContext的研究 ApplicationContext是Spring的核 心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些 ...
- Spring AOP + AspectJ annotation example
In this tutorial, we show you how to integrate AspectJ annotation with Spring AOP framework. In simp ...
- Spring AOP + AspectJ Annotation Example---reference
In this tutorial, we show you how to integrate AspectJ annotation with Spring AOP framework. In simp ...
- Spring 使用AspectJ的三种方式
Spring 使用AspectJ 的三种方式 一,使用JavaConfig 二,使用注解隐式配置 三,使用XML 配置 背景知识: 注意 使用AspectJ 的 时候 要导入相应的Jar 包 嗯 昨天 ...
- Spring学习之旅(八)Spring 基于AspectJ注解配置的AOP编程工作原理初探
由小编的上篇博文可以一窥基于AspectJ注解配置的AOP编程实现. 本文一下未贴出的相关代码示例请关注小编的上篇博文<Spring学习之旅(七)基于XML配置与基于AspectJ注解配置的AO ...
- 学习 Spring (十七) Spring 对 AspectJ 的支持 (完结)
Spring入门篇 学习笔记 @AspectJ 的风格类似纯 java 注解的普通 java 类 Spring 可以使用 AspectJ 来做切入点解析 AOP 的运行时仍旧是纯的 Spring AO ...
- 关于 Spring AOP (AspectJ) 该知晓的一切
关联文章: 关于Spring IOC (DI-依赖注入)你需要知道的一切 关于 Spring AOP (AspectJ) 你该知晓的一切 本篇是年后第一篇博文,由于博主用了不少时间在构思这篇博文,加上 ...
- Spring学习(十八)----- Spring AOP+AspectJ注解实例
我们将向你展示如何将AspectJ注解集成到Spring AOP框架.在这个Spring AOP+ AspectJ 示例中,让您轻松实现拦截方法. 常见AspectJ的注解: @Before – 方法 ...
- 10 Spring框架 AOP (三) Spring对AspectJ的整合
上两节我们讲了Spring对AOP的实现,但是在我们的开发中我们不太使用Spring自身的对AOP的实现,而是使用AspectJ,AspectJ是一个面向切面的框架,它扩展了Java语言.Aspect ...
随机推荐
- poj2104 划分树 区间K大 在线 无修改
博主sbit....对于高级数据结构深感无力,然后这些东西在OI竟然烂大街了,不搞就整个人都不好了呢. 于是我勇猛的跳进了这个大坑 ——sbit 区间K大的裸题,在线,无修改. 可以用归并树(\(O( ...
- Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---工厂模式之简单工厂
简单工厂:工厂依据传进的参数创建相应的产品. http://www.cnblogs.com/DelphiDesignPatterns/archive/2009/07/24/1530536.html { ...
- C# CreateProcess的测试
很奇怪的一个现象,在C#中使用Process来启动进程,启动文件名必须是系统指定的扩展名.EXE,而我使用原生的Win32API ::CreateProcess ()并没有这个限制,以后遇到类似的问题 ...
- 洛谷——P1724 东风谷早苗
P1724 东风谷早苗 题目描述 在幻想乡,东风谷早苗是以高达控闻名的高中生宅巫女.某一天,早苗终于入手了最新款的钢达姆模型.作为最新的钢达姆,当然有了与以往不同的功能了,那就是它能够自动行走,厉害吧 ...
- gwy常识
其实公务员考试是一门艺术,七分靠水平,三分凭发挥,充分而又细致的准备则是取得优秀成绩的前提.考生若想在笔试中成功上岸,还需苦练内功,凭技巧和真才实学在考场上一较高下.那么针对历年上海公务员考试笔试考情 ...
- 杭电oj 1001
#include<iostream> using namespace std; int main() { , sum; while (cin>>n) { sum = ; // ...
- 【推导】【找规律】【二分】hdu6154 CaoHaha's staff
题意:网格图.给你一个格点多边形的面积,问你最少用多少条边(可以是单位线段或单位对角线),围出这么大的图形. 如果我们得到了用n条边围出的图形的最大面积f(n),那么二分一下就是答案. n为偶数时,显 ...
- 【主席树】Gym - 101237A - MEX-Query
主席树里每个值的位置存当前该值出现的最右位置. 如果root[r]的前缀主席树中,某值最右位置大于等于l,说明该值出现在了l,r中. 所以主席树维护区间最小值,如果左半值域的最小值<l,则说明左 ...
- [转]Java五个最常用的集合类之间的区别和联系
Map<String, ?>只能是只读模式,不能增加,因为增加的时候不知道该写入什么类型的值:Map<String, Object>可以读和写,只要是所有Object类的子类都 ...
- css3背景属性 background-size 对背景图进行缩小放大
background-size需要两个值,它的类型可以是像素(px).百分比(%)或是auto,还可以是cover和contain.第一个值为背景图的width,另外一个值用于指定背景图上的heigh ...