Spring实现AOP其实本质是有两种方式.
- 1 基于XML配置的方式,
基于XML的方式又细分为两种
- 基于代理实现的AOP
- 纯pojo切面,基于SpringAop容器实现的
- 2 基于注解的方式
现在就来说基于XML形式的第一种:基于代理模式的实现.
如果说我们要实现aop通知的那个类实现的有其他接口的话,那么使用的代理模式就是JDK自带的动态代理模式,无实现接口的话是用CGIB动态代理模式.
这里使用有实现接口的业务类.上代码,
- (1)可睡觉的接口,任何可以睡觉的人或机器都可以实现它
public interface Sleepable {
public void sleep();
}
- (2)接口实现类,“Me”可以睡觉,“Me”就实现可以睡觉的接口。
public class Me implements Sleepable{
public void sleep() {
System.out.println("\n睡觉!不休息哪里有力气学习!\n");
}
}
- (3)Me关注于睡觉的逻辑,但是睡觉需要其他功能辅助,比如睡前脱衣服,起床脱衣服,这里开始就需要AOP替“Me”完成!解耦!首先需要一个SleepHelper(切面类)类。因为一个是切入点前执行、一个是切入点之后执行,所以实现对应接口(这是与另外一种XML方式区别的地方,纯pojo,Spring容器实现的方法,切面类没有实现这些接口)。
public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("睡觉前要脱衣服!");
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("起床后要穿衣服!");
}
}
- (4)Spring核心配置文件application.xml配置AOP。
<!-- 定义被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定义切入点位置 -->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*sleep"></property>
</bean>
<!-- 使切入点与通知相关联,完成切面配置 -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="sleepHelper"></property>
<property name="pointcut" ref="sleepPointcut"></property>
</bean>
<!-- 设置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的对象,有睡觉能力 -->
<property name="target" ref="me"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="sleepHelperAdvisor"></property>
<!-- 代理接口,睡觉接口 -->
<property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property>
</bean>
</beans>
现在就来说基于XML形式的第二种:
- (1)修改后的SleepHelper类,很正常的类,所以这种方式的优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置。
public class SleepHelper{
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
public void afterSleep(){
System.out.println("睡醒了要穿衣服!");
}
}
- (2) 业务类不做修改
- (3)配置文件:
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定义被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
<aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
</aop:aspect>
</aop:config>
</beans>
- (4)测试 ,结果一致.
基于注解实现的方式
这种方式简单,代码简洁.
- (1)同样的例子,修改后的SleepHelper:
/**
* @Aspect
* 定义这个类为切面类
*/
@Aspect
public class SleepHelper{
public SleepHelper(){
}
/**
* @Pointcut("execution(* *.sleep())")
* 定义这个方法为切入点,括号里面是execution()表达式
* 表示切入到的位置为-->返回值为任何形式,任何包的任何类的sleep()方法上.
* 注意是无参的
*
*/
@Pointcut("execution(* *.sleep())")
public void sleeppoint(){}
/**
* @Before("sleeppoint()")
* 定义这个方法作为前置通知.
* 并在sleeppoint()所指定的切点上执行这个前置通知
*/
@Before("sleeppoint()")
public void beforeSleep(){
System.out.println("睡觉前要脱衣服!");
}
@AfterReturning("sleeppoint()")
public void afterSleep(){
System.out.println("睡醒了要穿衣服!");
}
}
- (3)然后修改配置为:
<!-- 开启支持@AspectJ风格的切面声明 -->
<aop:aspectj-autoproxy />
<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
<!-- 定义被代理者 -->
<bean id="me" class="com.springAOP.bean.Me"></bean>
- (4)最后测试,一样的结果!
PS:
@Component注解
如果切面类上除了@Aspect注解外还加上一个@Component注解,那么XML配置就不用写
<bean id="" class=""> </bean>
之类的bean组件.但是XML需要加入扫描注解的配置:
<context:component-scan base-package="com.qiushiju">
<!-- 扫描包括这些路径下的类 -->
<context:include-filter type="annotation" expression=""/>
<!-- 告诉主容器不要扫描加有@Controller 这些类,这些bean 是由web容器进行管理的 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
因为@Component注解就是声明这个类是Spring容器托管的类,会扫描到自动加载的.与@Controller @Service @Repository功能一样.
@Pointcut(@annotation)注解
@Pointcut(@annotation)与 @Pointcut(“execution(* .(..)”)的区别
@Pointcut(“execution(* .(..)”)是在指定的返回参数类型,指定路径的指定方法上切入进行各种通知操作,如上面的代码.
@Pointcut(@annotation)是适用于自定义注解的情况.我自定义一个一个注解类
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
/**
* 日志描述
*/
String desc() default "";
/**
* 操作表类型
*/
int type() default "";
}
如果要在这个注解所在的方法(一般是Controller层的方法上)使用AOP的通知操作(一般做日志处理),那就把这个注解类定义为切点.即在切面类中定义切点的时候使用@Pointcut(@annotation)方式声明
@Pointcut("@annotation(com.qiushiju.demo.common.annotion.log.MyLog)")
public void cutService() {
}
这样在调用加了@MyLog注解的controller层方法时,@MyLog最为切入点,那么这个方法在执行前就会被SpringAop所拦截,并首先执行切面类中定义的各种通知方法,比如前置通知,异常通知等.然后再执行controller层的方法,之后还会执行Aop的后置通知,当然是你配置了后置通知的前提下…
@Controller的controller层不能被AOP拦截的问题
因为controller被JDK的动态代理代理了,所以配置文件中就应该设置成强制使用CGLB代理,配置文件如下
<!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller->
<aop:aspectj-autoproxy proxy-target-class="true" />
利用Aop注解做日志管理
http://blog.csdn.net/czmchen/article/details/42392985
需要的类:一个log实体类/log注解类/aop类.在aop类里面进行Log的具体操作,比如进行持久化的功能等.可以实现拦截controller层和service层的方法进行做日志记录.