AOP中有 @Before , @After , @Around , @AfterRunning 注解等等。
首先上下自己的代码,定义了切点的定义
@Before , @After , @Around 注解的区别大家可以自行百度下。
总之就是 @Around 可以实现 @Before 和 @After 的功能,并且只需要在一个方法中就可以实现。
首先我们来测试一个方法用于获取数据库一条记录的
以下是控制台打印的日志
可以看到,因为没有匹配 @Around 的规则,所以没有进行环绕通知。(PS:我定义的环绕通知意思是要符合是 controller 包下的方法并且方法必须带有参数,而上述方法没有参数,所以只走了 @before 和 @after 方法,不符合 @Around 的匹配逻辑)
我们再试一下另一个带有参数的方法
以下是该部分代码的console打印
显而易见,该方法符合 @Around 环绕通知的匹配规则,所以进入了 @Around 的逻辑,但是发现了问题,所有的方法都被执行了2次,不管是切面层还是方法层。(有人估计要问我不是用的自定义注解 @RedisCache(type = Responseclass) 么。为什么会符合 @Around 的匹配规则呢,这个等会在下面说)
我们分析日志的打印顺序可以得出,在执行环绕方法时候,会优先进入 @Around 下的方法。 @Around 的方法再贴一下代码。
打印了前两行代码以后,转而去执行了 @Before 方法,是因为中途触发了 ProceedingJoinPointproceed() 方法。
这个方法的作用是执行被代理的方法,也就是说执行了这个方法之后会执行我们controller的方法,而后执行 @before , @after ,然后回到@Around执行未执行的方法,最后执行 @afterRunning ,如果有异常抛出能执行 @AfterThrowing
也就是说环绕的执行顺序是 @Around @Before @After @Around 执行 ProceedingJoinPointproceed() 之后的 *** 作 @AfterRunning (如果有异常 @AfterThrowing )
而我们上述的日志相当于把上述结果执行了2遍,根本原因在于 ProceedingJoinPointproceed() 这个方法,可以发现在@Around 方法中我们使用了2次这个方法,然而每次调用这个方法时都会走一次 @Before @After @Around 执行 ProceedingJoinPointproceed() 之后的 *** 作 @AfterRunning (如果有异常 @AfterThrowing )。
因此问题是出现在这里。所以更改 @Around 部分的代码即可解决该问题。更改之后的代码如下:
更改代码之后的运行结果
回到上述未解决的问题,为什么我定义了切面的另一个注解还可以进入@Around方法呢?
因为我们的方法仍然在controller下,因此满足该需求,如果我们定义了controller包下的某个controller才有用。
例如:
而如果我们刚才定义的方法是写在 TestController 之下的,那么就不符合 @Around 方法的匹配规则了,也不符合 @before 和 @after 的注解规则,因此不会匹配任何一个规则,如果需要匹配特定的方法,可以用自定义的注解形式或者特性controller下的方法
①:特性的注解形式
然后在所需要的方法上加入 @RedisCache 注解,在 @Before , @After , @Around 等方法上添加该切点的方法名(“ annoationPoint() ”),如果有多个注解需要匹配则用 || 隔开
②:指定controller或者指定controller下的方法
该部分代码是指定了 comlmxblogcontroller 包下的UserController下的所有方法。
第一个 代表的是返回类型不限
第二个 代表的是该controller下的所有方法, () 代表的是参数不限
当方法符合切点规则不符合环绕通知的规则时候,执行的顺序如下
@Before @After @AfterRunning(如果有异常 @AfterThrowing)
当方法符合切点规则并且符合环绕通知的规则时候,执行的顺序如下
@Around @Before @Around @After执行 ProceedingJoinPointproceed() 之后的 *** 作 @AfterRunning(如果有异常 @AfterThrowing)
一、组件注解
1、 @Component(“xxx”)
指定某个类是容器的bean, @Component(value="xx") 相当于 ,其中 value 可以不写。
用于标注类为spring容器bean的注解有四个, 主要用于区别不同的组件类,提高代码的可读性:
a、 @Component, 用于标注一个普通的bean
b、 @Controller 用于标注一个控制器类(控制层 controller)
c、 @Service 用于标注业务逻辑类(业务逻辑层 service)
d、 @Repository 用于标注DAO数据访问类 (数据访问层 dao)
对于上面四种注解的解析可能是相同的,尽量使用不同的注解提高代码可读性。
注解用于修饰类,当不写value属性值时,默认值为类名首字母小写。
2、 @Scope(“prototype”)
该注解和 @Component 这一类注解联合使用,用于标记该类的作用域,默认 singleton 。
也可以和 @Bean 一起使用,此时 @Scope 修饰一个方法。关于@Bean稍后有说明
3、 @Lazy(true)
指定bean是否延时初始化,相当于 ,默认false。@Lazy可以和@Component这一类注解联合使用修饰类,也可以和@Bean一起使用修饰方法
注 :此处初始化不是指不执行 init-method ,而是不创建bean实例和依赖注入。只有当该bean(被@Lazy修饰的类或方法)被其他bean引用(可以是自动注入的方式)或者执行getBean方法获取,才会真正的创建该bean实例,其实这也是BeanFactory的执行方式。
4、 @DepondsOn({“aa”,“bb”})
该注解也是配合 @Component 这类注解使用,用于强制初始化其他bean
上面的代码指定,初始化bean “userAction"之前需要先初始化“aa”和“bb”两个bean,但是使用了@Lazy(true)所以spring容器初始化时不会初始化"userAction” bean。
5、 @PostConstructor和@PreDestroy
@PostConstructor 和 @PreDestroy 这两个注解是j2ee规范下的注解。这两个注解用于修饰方法,spring用这两个注解管理容器中spring生命周期行为。
a、 @PostConstructor 从名字可以看出构造器之后调用,相当于 。就是在依赖注入之后执行
b、 @PreDestroy 容器销毁之前bean调用的方法,相当于
6、 @Resource(name=“xx”)
@Resource 可以修饰成员变量也可以修饰set方法。当修饰成员变量时可以不写set方法,此时spring会直接使用j2ee规范的Field注入。
@Resource有两个比较重要的属性,name和type
a、 如果指定了name和type,则从Spring容器中找到唯一匹配的bean进行装配,找不到则抛出异常;
b、 如果指定了name,则从spring容器查找名称(id)匹配的bean进行装配,找不到则抛出异常;
c、 如果指定了type,则从spring容器中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常;
d、 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配
如果没有写name属性值时
a、 修饰成员变量,此时name为成员变量名称
b、 修饰set方法,此时name 为set方法的去掉set后首字母小写得到的字符串
7、 @Autowired(required=false)
@Autowired可以修饰构造器,成员变量,set方法,普通方法。@Autowired默认使用byType方式自动装配。required标记该类型的bean是否是必须的,默认为必须存在(true)。
可以配合 @Qualifier(value="xx") ,实现按beanName注入:
a、 required=true(默认),为true时,从spring容器查找和指定类型匹配的bean,匹配不到或匹配多个则抛出异常
b、 使用 @Qualifier("xx") ,则会从spring容器匹配类型和 id 一致的bean,匹配不到则抛出异常
@Autowired会根据修饰的成员选取不同的类型:
a、 修饰成员变量。该类型为成员变量类型
b、 修饰方法,构造器。注入类型为参数的数据类型,当然可以有多个参数
8、demo
业务逻辑层:
数据访问层:
测试类:
输出结果:
可以看到虽然UserDao 使用@Lazy,但是还是在spring容器初始化的时候还是创建了UserDao实例。原因很简单,因为在UserService中需要注入UserDao,所以在此时创建的UserDao实例也属于延时初始化。
在上面我们还使用了两个接口InitializingBean 和DisposableBean,这两个接口用于管理 singleton 作用域的bean的生命周期,类似init-method和destroy-method。不同之处就是调用的循序不一致:
a、 初始化调用顺序 :@PostConstructor > InitializingBean > init-method 用于指定bean依赖注入后的行为
b、 销毁调用顺序 @PreDestroy > DisposableBean > destroy-method 用于定制bean销毁之前的行为
该注解是AspectJ中的注解,并不是spring提供的,所以还需要导入aspectjweaverjar,aspectjrtjar,除此之外还需要依赖aopalliancejar
依赖包:
UserDaojava
配置文件 applicationContextxml:
测试类:
1、 @Aspect
修饰Java类,指定该类为切面类。当spring容器检测到某个bean被@Aspect修饰时,spring容器不会对该bean做增强处理(bean后处理器增强,代理增强)
2、 @Before
修饰方法,before增强处理。用于对目标方法(切入点表达式表示方法)执行前做增强处理。可以用于权限检查,登陆检查。
常用属性:
value: 指定切入点表达式 或者引用一个切入点
对comexampleaop 包下所有的类的所有方法做 before增强处理:
结果:
如果同一条切入点表达式被使用多次,可以使用更友好的方式。定义一个切入点:
增强方法可以接受一个JoinPoint 类型的参数,用于获取被执行目标方法的一下属性。
结果:
3、 @AfterReturning
修饰方法,afterreturning增强处理。目标方法正常结束后做增强处理。
常用属性:
a、 pointcut/value:指定切入点表达式
b、 returning:指定一个参数名,用于接受目标方法正常结束时返回的值。参数名称需要在增强方法中定义同名的参数。
注意:
a、 如果使用了returning 。那么增强方法中的数据类型必须是返回结果的类型或者父类型,否则不会调用该增强处理。
b、 使用了returning 还可以用来 修改返回结果 。
以上面的例子来说,目标方法返回结果类型应该满足下面的条件
修改返回值:
结果:
可以看到 AfterReturning 修改了返回结果。
4、 @AfterThrowing
修饰方法,afterthrowing增强处理。当目标程序方法抛出 异常或者异常无法捕获时,做增强处理。
常用属性:
a、 pointcut/value :指定切入点表达式
b、 throwing:指定一个形参,在增强方法中定义同名形参,用于访问目标方法抛出的异常
参数类型必须是 Throwable 的子类,同样也会有上面@AfterReturning 参数类型匹配的问题。
5、 @After
修饰方法 ,after增强处理。无论方法是否正常结束,都会调用该增强处理(@After= @AfterReturning+@AfterThrowing)。但是该增强方式无法获取目标方法的返回结果,也获取目标方法抛出的异常。所以一般用于进行释放资源,功能类似于 finally。
常用属性:
a、 value :指定切入点表达式
结果:
从上面的结果来看 After 增加处理 ,因为不能接受返回结果作为参数,所以不能修改返回结果。
6、 @Around
修饰方法, around增强处理。该处理可以目标方法执行之前和执行之后织入增强处理(@Before+@AfterReturning)。
Around增强处理通常需要在线程安全的环境下使用,如果@Before和@AfterReturning可以处理就没必要使用@Around。
常用属性:
a、 value :指定切入点表达式
当定义一个Aound增前处理时,增强方法第一形参需要时ProceedingJoinPoint类型。ProceedingJoinPoint有一个Object proceed()方法,用于执行目标方法。当然也可以为目标方法传递数组参数,来修改目前方法的传入参数。
around小结:
a、 Around增强处理通常需要 在线程安全 的环境下使用
b、 调用 proceed()可以获取返回结果,所以可以修改目标方法的返回值
c、 proceed(Object[] var1) 可以修改入参,修改目标方法的入参
d、 可以进行目标方法执行之前和执行之后织入增强处理
around 和 afterReturning 都可以修改返回结果。不过两者的原理不同:
a、 around:可以任意修改,或者返回不相关的值。这个返回值完全可以自主控制
b、 afterReturning,通过方法参数 ,使用对象引用的方式来修改对象。修改对象引用地址那么修改时无效的
除此之外从输出结果来看,增强处理是有序的:
around 和 afterReturning小结:
a、 只有 around 和 afterReturning 可以获取并修改返回结果。需要注意两种方式修改的区别。
b、 around 需要线程安全
c、 虽然增强处理都需要 切入点表达式,并不是都支持 pointcut 属性,所以最好都是用value 属性指定。当注解只需要value属性时,value可以省略
7、 @Pointcut
修饰方法,定义一个切入点表达式用于被其他增强调用。使用该方式定义切入点方便管理,易复用。
切入点方法定义和测试方法定义类似,具有以下特点:
a、 无返回值 (void)
b、 无参数
c、 方法体为空
d、 方法名就是切入点名称
e、 方法名不能为 execution
切入点表达式
切入点表达式可以通过 && 、 || 、 ! 连接
1)、execution 表达式:
2)、within 表达式:
a、匹配指定类下的所有方法。
b、匹配执行包及其子包下所有类的所有方法。
所以within可以看做execution的简写,不需要指定返回类型、方法名、参数( 最小作用单位是类 )
3)、 @annotation:匹配使用指定注解修饰的目标方法;
匹配使用@CustomMethodAnnotation注解的目标方法。
4)、 @within: 用于匹配使用指定注解修饰的类下的所有方法
within 作用范围是类,@within的作用范围与其一致。不同的是@within 指定的不是类而是注解
匹配使用@ResponseBody 注解的类 下的所有方法。
AOP小结:
1)、 Around增强处理通常需要 在线程安全 的环境下使用
2)、 使用 around 和 afterReturning 可以获取并修改返回结果
3)、 增强处理指定 切入点表达式时,最好使用value 属性
4)、 切入点 名称(方法名)不能为 execution
5)、 AfterReturning 指定了 returning 属性接受目标方法返回结果,注意 参数类型需要和返回结果类型一致(满足 resutType instanceof argsType )
增强方式的顺序:
1、 @Bean(name=“xxx”)
修饰方法,该方法的返回值为spring容器中管理的bean。当然该注解和上面的@Component效果一样,主要用于做区分。
@Bean 通常使用在 @Configuration 修饰的配置类中,该注解功能相当于 元素
常用的属性:
a、 name:bean id 。name可以省略,省略时bean名称为方法名。也可以指定多个名称(逗号隔开)。
b、 autowire: 是否自动注入,默认AutowireNO
c、 initMethod:bean的初始化方法。在依赖注入之后执行
d、 destroyMethod: spring容器关闭时bean调用的方法
当然 @Bean 还可以配合 @Scope 指定bean的作用域
2、 @ConfigurationProperties
用于从属性文件中获取值 applicationproperties 或者 applicationyml 。当然了 如果在配置文件中引入其他配置文件,也可以获取到属性值。
包含的属性:
a、 value | prefix 两者互为别名。指定前缀,默认为""
b、 ignoreUnknownFields:默认为true。是否忽略未知字段,当实体中的字段在配置文件中不存在时,是忽略还是抛出异常
c、 ignoreInvalidFields: 默认false。 是否忽略不合法的字段,此处的不合法是指类型不合适,配置文件中存在改配置但是无法转化为指定的字段类型。
Mybatis属性配置
applicationproperties:
ConfigurationProperties 可以配置前缀,然后会根据实体的变量名拼接前缀,去配置文件中查询配置。
3、 @Configuration
修饰一个Java类,被修饰的类相当于一个xml配置文件。功能类似于 。在springboot中大量使用了该注解,该注解提供了一种使用Java类方式配置bean。
可以发现 @Configuration使用了@Component 注解修饰。
实例 :
配置Mybatis会话工厂
4、 @Import
功能和 类似,修饰Java类,用于向当前类导入其他配置类。 可以导入多个配置文件,通常用于导入不在包扫描范围内的配置文件。可以被扫描的配置类可以直接访问,没有必要使用@Import 导入。
比如 SpringBoot的启动类指定的包扫描路径为 comexample
数据库的配置文件在 com包下。
在MyBatisConfig 中引入 DataSourceConfig, 就会解析DataSourceConfig。将解析出的Bean交给容器管理
5、 @ImportResource
修饰Java类,用于向类引入xml配置文件。
用于导入包含bean定义的配置文件,功能和 类似。默认情况下可以处理后缀为 groovy 和xml 的配置文件
6、 @Value("${expression}")
修饰成员变量或者 方法、构造器的参数,用于属性值注入(在配置文件中配置的值)。
注意: @Value不能对 static 属性注入。
如果的确需要注入到静态变量,可以通过以下方式间接进行注入:
1)、设置一个私有静态 实例
2)、通过构造函数或者 @PostConstruct 注解为 静态实例 赋值,指向本身(this)
3)、对成员属性注入内容
4)、提供静态方法,使用静态实例获取成员属性
7、@PropertySource(value=“classpath:jdbcproperties”)
该注解用来加载属性文件。
常用属性:
a、 ignoreResourceNotFound: 当资源文件找不到的时候是否会忽略该配置,而不是抛出错误。一般用于可选项
b、 encoding : 资源文件使用什么编码方式
c、 value : 指定属性文件位置。可以配置多个属性文件,不可以使用通配符。
在 PropertySource 中可以指定多个路径,并且会将属性文件中的值加载到 Environment 中。
@ConfigurationProperties 和 @PropertySource
它们的使用有一些差异:
1)、 @PropertySource 使用该注解加载的是 相对独立的属性文件,可以同时加载多个文件 (xxxproperties),而且 不支持自动注入 , 不支持前缀注入
2)、 @ConfigurationProperties 用于加载配置文件(applicationproperties | applicationyml)。该注解功能更强大:
a、 支持前缀注入 ( prefix )
b、 相同属性名的自动注入
c、 $("") 支持EL表达式注入
应用实例:
在以往的开发中通常会将数据库连接信息存放在单独的属性文件中(jdbcproperties)。而在spring boot 中我们会将数据库的信息存放在配置文件中,这会极大便利开发工作。
jdbcproperties:
可以通过 @Value 注解将配置文件的值注入到实体类中
也可以注入Environment ,通过Environment 获取值
1、 @ResponseBody
控制器方法返回值会使用 >
你好,Spring使用的AOP注解分为三个层次:
前提条件是在xml中放开了
<aop:aspectj-autoproxy proxy-target-class="true"/><!--
开启切面编程功能 -->
@Aspect放在类头上,把这个类作为一个切面。
@Pointcut放在方法头上,定义一个可被别的方法引用的切入点表达式。
5种通知。
@Before,前置通知,放在方法头上。
@After,后置finally通知,放在方法头上。
@AfterReturning,后置try通知,放在方法头上,使用returning来引用方法返回值。
@AfterThrowing,后置catch通知,放在方法头上,使用throwing来引用抛出的异常。
@Around,环绕通知,放在方法头上,这个方法要决定真实的方法是否执行,而且必须有返回值。
希望对你有帮助
AOP配置,@EnableAspectJAutoProxy,@Before,@After,@AfterReturning,@AfterThrowing
AOP:动态代理
指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
1导入aop模块;Spring AOP:
<dependency>
<groupId>orgspringframeworkboot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2定义一个业务逻辑类(CalculateController);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
3定义一个日志切面类(LogAop):切面类里面的方法需要动态感知CalculateControllercalculateNum运行到哪里然后执行;
通知方法:
前置通知(@Before):logStart:在目标方法(calculateNum)运行之前运行
后置通知(@After):logEnd:在目标方法(calculateNum)运行结束之后运行(无论方法正常结束还是异常结束)
返回通知(@AfterReturning):logReturn:在目标方法(calculateNum)正常返回之后运行
异常通知(@AfterThrowing):logException:在目标方法(calculateNum)出现异常以后运行
环绕通知(@Around):动态代理,手动推进目标方法运行(joinPointprocced())
4给切面类的目标方法标注何时何地运行(通知注解);
5将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
6必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
7给配置类中加 @EnableAspectJAutoProxy 开启基于注解的aop模式
在Spring中很多的 @EnableXXX;
三步:
1)将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
2)在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
3)开启基于注解的aop模式;@EnableAspectJAutoProxy
配置
// @EnableAspectJAutoProxy 开启基于注解的aop模式
@EnableAspectJAutoProxy
@Configuration
public class MyAopConfig {
@Bean
public CalculateController calculateController(){
return new CalculateController();
}
@Bean
public LogAop logAop(){
return new LogAop();
}
}
/
切面类
/
// @Aspect: 告诉Spring当前类是一个切面类
@Aspect
public class LogAop {
//抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用
@Pointcut("execution(public int comexamplestudyworkworkcontrollerCalculateController())")
public void pointCut(){};
@Before(value ="pointCut()")
public void logStart(JoinPoint joinPoint){
Systemoutprintln(joinPointgetSignature()getName()+"方法运行前。。。参数列表是:{"+ ArraysasList(joinPointgetArgs())+"}");
}
// 外部切面类引用可以用全类名
@After("comexamplestudyworkworkaopLogAoppointCut()")
public void logEnd(JoinPoint joinPoint){
Systemoutprintln(joinPointgetSignature()getName()+"方法结束。。。");
}
//JoinPoint一定要出现在参数表的第一位
@AfterReturning(value = "pointCut()",returning = "obj")
public void logReturn(JoinPoint joinPoint,Object obj){
Systemoutprintln(joinPointgetSignature()getName()+"方法正常返回。。。运行结果是:{"+obj+"}");
}
@AfterThrowing(value = "pointCut()",throwing = "e")
public void logxception(JoinPoint joinPoint,Exception e){
Systemoutprintln(joinPointgetSignature()getName()+"方法异常返回。。。异常结果是:{"+e+"}");
}
}
// 业务
public class CalculateController {
public int calculateNum(int i, int j){
Systemoutprintln("CalculateController类的calculateNum方法正在运行");
return i/j;
}
}
输出
@Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyAopConfigclass);
CalculateController bean = applicationContextgetBean(CalculateControllerclass);
beancalculateNum(1,1);
}
输出结果
异常输出
@Test
public void test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyAopConfigclass);
CalculateController bean = applicationContextgetBean(CalculateControllerclass);
beancalculateNum(1,0);
}
输出结果
之前记录了@AfterThrowing,当切面的类或者方法有异常时,今天看到@ExceptionHandler,对于 两者的优先级存在疑问 ,遂记录。
先记录@ExceptionHandler的作用以及使用场景。
1、如果单使用@ExceptionHandler,只能在当前Controller中处理异常。但当配合@ControllerAdvice一起使用的时候,就可以在任意地方使用。
2、@ExceptionHandler和@ControllerAdvice能够集中异常,使异常处理与业务逻辑分离。
大概就像下面这样的例子吧
优先级的话,运行程序截图就能说明问题。
结论:AfterThrowing 优先于 ExceptionHandler。
web中常见的通过回调的方式实现的aop有Filter(过滤器)和Interceptor(拦截器)。详情看这位大佬链接即可,每个步骤非常详细。
>
先说注解,使用注解配置Spring AOP总体分为两步,第一步是在xml文件中声明激活自动扫描组件功能,同时激活自动代理功能(同时在xml中添加一个UserService的普通服务层组件,来测试AOP的注解功能):
<xml version="10" encoding="UTF-8">
<beans xmlns=">
第二步是为Aspect切面类添加注解:
package cnyshstudiospringaopaspect;
import orgapachecommonsloggingLog;
import orgapachecommonsloggingLogFactory;
import orgaspectjlangJoinPoint;
import orgaspectjlangProceedingJoinPoint;
import orgaspectjlangannotationAfter;
import orgaspectjlangannotationAfterReturning;
import orgaspectjlangannotationAfterThrowing;
import orgaspectjlangannotationAround;
import orgaspectjlangannotationAspect;
import orgaspectjlangannotationBefore;
import orgaspectjlangannotationPointcut;
import orgspringframeworkstereotypeComponent;
/
系统服务组件Aspect切面Bean
@author Shenghany
@date 2013-5-28
/
//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
public class ServiceAspect {
private final static Log log = LogFactorygetLog(ServiceAspectclass);
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution( cnyshstudiospringaopservice())")
public void aspect(){ }
/
配置前置通知,使用在方法aspect()上注册的切入点
同时接受JoinPoint切入点对象,可以没有该参数
/
@Before("aspect()")
public void before(JoinPoint joinPoint){
if(logisInfoEnabled()){
loginfo("before " + joinPoint);
}
}
//配置后置通知,使用在方法aspect()上注册的切入点
@After("aspect()")
public void after(JoinPoint joinPoint){
if(logisInfoEnabled()){
loginfo("after " + joinPoint);
}
}
//配置环绕通知,使用在方法aspect()上注册的切入点
@Around("aspect()")
public void around(JoinPoint joinPoint){
long start = SystemcurrentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint)proceed();
long end = SystemcurrentTimeMillis();
if(logisInfoEnabled()){
loginfo("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
}
} catch (Throwable e) {
long end = SystemcurrentTimeMillis();
if(logisInfoEnabled()){
loginfo("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + egetMessage());
}
}
}
//配置后置返回通知,使用在方法aspect()上注册的切入点
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint){
if(logisInfoEnabled()){
loginfo("afterReturn " + joinPoint);
}
}
//配置抛出异常后通知,使用在方法aspect()上注册的切入点
@AfterThrowing(pointcut="aspect()", throwing="ex")
public void afterThrow(JoinPoint joinPoint, Exception ex){
if(logisInfoEnabled()){
loginfo("afterThrow " + joinPoint + "\t" + exgetMessage());
}
}
}
测试代码:
package cnyshstudiospringaop;
import orgapachecommonsloggingLog;
import orgapachecommonsloggingLogFactory;
import orgspringframeworkcontextApplicationContext;
import orgspringframeworkcontextsupportClassPathXmlApplicationContext;
import cnyshstudiospringaopserviceUserService;
import cnyshstudiospringmvcbeanUser;
/
Spring AOP测试
@author Shenghany
@date 2013-5-28
/
public class Tester {
private final static Log log = LogFactorygetLog(Testerclass);
public static void main(String[] args) {
//启动Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContextxml");
//获取service组件
UserService service = (UserService) contextgetBean("userService");
//以普通的方式调用UserService对象的三个方法
User user = serviceget(1L);
servicesave(user);
try {
servicedelete(1L);
} catch (Exception e) {
if(logisWarnEnabled()){
logwarn("Delete user : " + egetMessage());
}
}
}
}
控制台输出如下:
INFO [springaopaspectServiceAspect:40] before execution(User cnyshstudiospringaopserviceUserServiceget(long))
INFO [springaopserviceUserService:19] getUser method
INFO [springaopaspectServiceAspect:60] around execution(User cnyshstudiospringaopserviceUserServiceget(long)) Use time : 42 ms!
INFO [springaopaspectServiceAspect:48] after execution(User cnyshstudiospringaopserviceUserServiceget(long))
INFO [springaopaspectServiceAspect:74] afterReturn execution(User cnyshstudiospringaopserviceUserServiceget(long))
INFO [springaopaspectServiceAspect:40] before execution(void cnyshstudiospringaopserviceUserServicesave(User))
INFO [springaopserviceUserService:26] saveUser method
INFO [springaopaspectServiceAspect:60] around execution(void cnyshstudiospringaopserviceUserServicesave(User)) Use time : 2 ms!
INFO [springaopaspectServiceAspect:48] after execution(void cnyshstudiospringaopserviceUserServicesave(User))
INFO [springaopaspectServiceAspect:74] afterReturn execution(void cnyshstudiospringaopserviceUserServicesave(User))
INFO [springaopaspectServiceAspect:40] before execution(boolean cnyshstudiospringaopserviceUserServicedelete(long))
INFO [springaopserviceUserService:32] delete method
INFO [springaopaspectServiceAspect:65] around execution(boolean cnyshstudiospringaopserviceUserServicedelete(long)) Use time : 5 ms with exception : spring aop ThrowAdvice演示
INFO [springaopaspectServiceAspect:48] after execution(boolean cnyshstudiospringaopserviceUserServicedelete(long))
INFO [springaopaspectServiceAspect:74] afterReturn execution(boolean cnyshstudiospringaopserviceUserServicedelete(long))
WARN [studiospringaopTester:32] Delete user : Null return value from advice does not match primitive return type for: public boolean cnyshstudiospringaopserviceUserServicedelete(long) throws javalangException
可以看到,正如我们预期的那样,虽然我们并没有对UserSerivce类包括其调用方式做任何改变,但是Spring仍然拦截到了其中方法的调用,或许这正是AOP的魔力所在。
再简单说一下xml配置方式,其实也一样简单:
<xml version="10" encoding="UTF-8">
<beans xmlns=">
个人觉得不如注解灵活和强大,你可以不同意这个观点,但是不知道如下的代码会不会让你的想法有所改善:
//配置前置通知,拦截返回值为cnyshstudiospringmvcbeanUser的方法
@Before("execution(cnyshstudiospringmvcbeanUser cnyshstudiospringaopservice())")
public void beforeReturnUser(JoinPoint joinPoint){
if(logisInfoEnabled()){
loginfo("beforeReturnUser " + joinPoint);
}
}
//配置前置通知,拦截参数为cnyshstudiospringmvcbeanUser的方法
@Before("execution( cnyshstudiospringaopservice(cnyshstudiospringmvcbeanUser))")
public void beforeArgUser(JoinPoint joinPoint){
if(logisInfoEnabled()){
loginfo("beforeArgUser " + joinPoint);
}
}
//配置前置通知,拦截含有long类型参数的方法,并将参数值注入到当前方法的形参id中
@Before("aspect()&&args(id)")
public void beforeArgId(JoinPoint joinPoint, long id){
if(logisInfoEnabled()){
loginfo("beforeArgId " + joinPoint + "\tID:" + id);
}
}
附上UserService的代码(其实很简单):
package cnyshstudiospringaopservice;
import orgapachecommonsloggingLog;
import orgapachecommonsloggingLogFactory;
import cnyshstudiospringmvcbeanUser;
/
用户服务模型
@author Shenghany
@date 2013-5-28
/
public class UserService {
private final static Log log = LogFactorygetLog(UserServiceclass);
public User get(long id){
if(logisInfoEnabled()){
loginfo("getUser method ");
}
return new User();
}
public void save(User user){
if(logisInfoEnabled()){
loginfo("saveUser method ");
}
}
public boolean delete(long id) throws Exception{
if(logisInfoEnabled()){
loginfo("delete method ");
throw new Exception("spring aop ThrowAdvice演示");
}
return false;
}
}
应该说学习Spring AOP有两个难点,第一点在于理解AOP的理念和相关概念,第二点在于灵活掌握和使用切入点表达式。概念的理解通常不在一朝一夕,慢慢浸泡的时间长了,自然就明白了,下面我们简单地介绍一下切入点表达式的配置规则吧。
通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:
execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)
modifiers-pattern:方法的 *** 作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution( comspringservice())表示comspringservice包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
最后说一下通知参数
可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下:
<aop:config>
<aop:aspect id="TestAspect" ref="aspectBean">
<aop:pointcut id="businessService"
expression="execution( comspringservice(String,)) and args(msg,)" />
<aop:after pointcut-ref="businessService" method="doAfter"/>
</aop:aspect>
</aop:config>上面的代码args(msg,)是指将切入点方法上的第一个String类型参数添加到参数名为msg的通知的入参上,这样就可以直接使用该参数啦。
以上就是关于面试官:Spring 注解 @After,@Around,@Before 的执行顺序是全部的内容,包括:面试官:Spring 注解 @After,@Around,@Before 的执行顺序是、spring常用注解、怎么使用@pointcut 和@before等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)