切面编程,其实就是动态代理,使用反射动态的构造一个新的Class,新的Class包含旧的Class,可以在旧的Class执行前后和中间插入代码。
基于XML Schema的AOP:
AOP代理就是AOP框架通过代理模式创建的对象,Spring AOP默认首先使用JDK动态代理来代理目标对象,如果目标对象没有实现任何接口将使用CGLIB代理,如果需要强制使用CGLIB代理,对于Schema风格配置切面使用如下方式来指定使用CGLIB代理:
<aop:config proxy-target-class="true">
</aop:config>
例子:
//目标接口
package cn.javass.spring.chapter6.service;
public interface IHelloWorldService {
public void sayHello();
}
//目标接口实现
package cn.javass.spring.chapter6.service.impl;
import cn.javass.spring.chapter6.service.IHelloWorldService;
public class HelloWorldService implements IHelloWorldService {
@Override
public void sayHello() {
System.out.println("============Hello World!");
}
}
//切面支持类
package cn.javass.spring.chapter6.aop;
public class HelloWorldAspect {
//前置通知
public void beforeAdvice() {
System.out.println("===========before advice");
}
//后置最终通知
public void afterFinallyAdvice() {
System.out.println("===========after finally advice");
}
}
//在XML中进行配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
</beans>
<bean id="helloWorldService" class="cn.javass.spring.chapter6.service.impl.HelloWorldService"/>
<bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/>
<aop:aspect ref="aspect">
<aop:before pointcut-ref="pointcut" method="beforeAdvice"/>
<aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/>
</aop:aspect>
</aop:config>
//测试
package cn.javass.spring.chapter6;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.javass.spring.chapter6.service.IHelloWorldService;
import cn.javass.spring.chapter6.service.IPayService;
public class AopTest {
@Test
public void testHelloworld() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("chapter6/helloworld.xml");
IHelloWorldService helloworldService =
ctx.getBean("helloWorldService", IHelloWorldService.class);
helloworldService.sayHello();
}
}
其他后置通知,后置返回通知,后置异常通知,环绕通知等参考:http://jinnianshilongnian.iteye.com/blog/1418598
基于@AspectJ的AOP:
在Spring和SpringMVC的配置文件中启用切面,注意Spring和SpringMVC分别起用。
Spring配置文件 applicationContext.xml(JDK本身提供的DynamicProxy):
<beans .....>
<aop:aspectj-autoproxy/>
</beans>
SpringMVC配置文件 spring-mvc.xml(由CGLib提供):
<beans .....>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
正如开篇提到,所谓切面编程就是动态代理,那么动态代理有两种方式:
- JDK本身提供的DynamicProxy。JDK的动态代理是针对接口的,某个Class首先要实现某个接口,可以对接口方法进行代理。
- CGLib提供的。CGLib是针对具体的Class的,不需要你的Object去implement某个接口。在SpringMVC配置中使用cglib进行代理,这里注意如果你的pom需要有cglib的依赖。
选择哪种代理:
一般在Service切入AOP,使用JDK本身的动态代理,即<aop:aspectj-autoproxy/>
。因为按照习惯我们编写的Service都会继承接口。
在Controller切入AOP,使用CGLib,即<aop:aspectj-autoproxy proxy-target-class="true"/>
。Controller一般不继承接口。
下面以在Service层前后打印日志为例:
servie层代码:
package com.tengj.demo.service
public interface UserService {
public void sayHello(String name);
}
servie实现类代码:
package com.tengj.demo.service.impl;
@Service("userService")
public class UserServiceImpl implements UserService{
@Override
public void sayHello(String name) {
System.out.println("hello,"+name);
}
}
切点:
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component //注入依赖
@Aspect //该注解标示该类为切面类
public class LogAspect {
@Pointcut("execution(* com.tengj.demo.service.impl.UserServiceImpl.*(..))")
public void logAop(){}
@Before("logAop() && args(name)")
public void logBefore(String name){
System.out.println(name+"前置通知Before");
}
/**
* 定义返回值的型参名称为r,并传入切面
*/
@AfterReturning(value = "logAop()", returning = "r")
public void logAfterReturning(JoinPoint joinPoint, Object r) {
log.info("方法调用结束 返回值:{}",r.toString());
}
@After("logAop() && args(name)")
public void logAfter(String name){
System.out.println(name+"后置通知After");
}
/**
* 定义抛出类的型参名称为ex,并传入切面
*/
@AfterThrowing(value = "logAop()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
String message = (ex.getMessage() == null)?ex.getClass().getName():ex.getMessage();
log.warn("方法抛出异常: {} ",message);
}
}
上面方法index()就是我们定义的切点,表示在哪里切入AOP:
后边的Before和After就很好理解,就是以下方法在动态代理的方法前还是之后执行,除了@Before和@After还有@Around和@AfterThrowing、@AfterReturning等。
@Pointcut注解是为了定义切面内重用的切点,也就是说把公共的东西抽出来,定义了任意的方法名称logAop,这样下面用到的各种类型通知就只要写成例如:
@Before("logAop() && args(name)")
。也可以直接在其余注解中使用表达式例如:@Before(value = “execution(public*com.company.service.impl..*.*(..))”)
。@Before("logAop() && args(name)")这里多出来个&& args(name)
,这个是用来传递参数的,定义要跟Service里的方法sayHello参数名称一样就可以。
applicationContext配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
default-lazy-init="true">
<context:component-scan base-package="com.tengj.demo"/>
<mvc:resources location="/WEB-INF/pages/" mapping="/pages/**"/>
<!-- 默认的注解映射的支持 -->
<mvc:annotation-driven/>
<!--启用AspectJ自动代理-->
***<aop:aspectj-autoproxy/>***
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
参考:
http://www.jianshu.com/p/d35e46f27187
http://alanli7991.github.io/2016/10/21/%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B%E4%B8%89AspectJ%E4%B8%8EShiro%E4%B8%8D%E5%85%BC%E5%AE%B9%E5%92%8CSpring%E4%BA%8C%E6%AC%A1%E4%BB%A3%E7%90%86%E9%94%99%E8%AF%AF%E5%88%86%E6%9E%90/
网友评论