面向切面编程(AOP,aspect object programming)是一种编程范式,目的是通过分离关注点(cross-cutting concerns)提高模块化(modularity)程度。通俗地讲,就是将核心业务逻辑代码和关注点代码分离,便于维护,重构和复用。所谓关注点代码,就是重复代码,而关注点形成的类就是切面类,例如:日志记录,性能统计,安全控制,事务处理,异常处理等代码。
因此,关注点就是重复代码,切面就是重复代码所在的类;AOP就是编写切面类,并且使用代理,在运行时将关注点动态植入目标对象,实现对象方法的扩展或者过滤。也就是说,在目标对象的方法非常多时,我们可以通过切入点表达式,指定拦截哪些方法,植入哪些切面类方法。
我们使用一个简单例程来模拟事务处理的AOP处理
public class UserDao{
public void save(){
System.out.println("begin transaction");
System.out.println("save into database");
System.out.println("commit transaction");
}
}
这段代码中模拟save方法的三个过程,其中save into database是业务代码,而开启和提交事务是切面代码。我们通过将这两段代码抽出,并使用spring注解来处理对象间关系。
@Component
public class UserDao {
@Resource
private Aop aop;
public void save(){
aop.begin(); //切面方法
System.out.println("save into database");//业务方法
aop.commit(); //切面方法
}
}
@Component
public class Aop {
public void begin(){
System.out.println("begin transaction");
}
public void commit(){
System.out.println("commit transaction");
}
}
//bean.xml文件
version="1.0" encoding="UTF-8"?>
"http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName">
package="com.cityu.aop">
public class App {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/cityu/aop/bean.xml");
@Test
public void testname() throws Exception {
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.save();
}
}
好吧,虽然看起来代码量多了不止一倍并且结构更复杂了,但是在项目中,这种结构是远远优于前面的,因为代码之间的耦合度降低了:业务方法和切面方法之间解耦了,对象之间的依赖关系也解耦了。
更进一步,我们引入动态代理
public class ProxyFactory {
private static Object target;
private static Aop aop;
public static Object getProxyInstance(Object target_,Aop aop_){
target = target_;
aop = aop_;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
aop.begin();
Object returnValue = method.invoke(target, args);
aop.commit();
return null;
}
});
}
}
public class App {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/cityu/aop/bean.xml");
@Test
public void testname() throws Exception {
IUserDao proxyInstance = (IUserDao) ProxyFactory.getProxyInstance(ac.getBean("userDao"), (Aop)ac.getBean("aop"));
proxyInstance.save();
}
}
此时,我们的目标对象(委托对象)可以进一步简化为只处理业务方法:
@Component
public class UserDao implements IUserDao{
public void save(){
System.out.println("save into database");//业务方法
}
}
spring-aop-3.2.5.RELEASE.jar
aspectjrt.jar
aspectjweaver.jar
aopalliance.jar
引入aop名称空间
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
beans>
@Component
public class UserDao implements IUserDao {
@Override
public void save() {
System.out.println("Save data into database!");
}
}
@Component
@Aspect//切面类
public class Aop {
//关注点方法,切入点表达式
@Before("execution(* com.cityu.aop_anno.UserDao.*(..))")
public void begin(){
System.out.println("begin transaction");
}
@After("execution(* com.cityu.aop_anno.UserDao.*(..))")
public void commit(){
System.out.println("commit transaction");
}
}
public class App {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/cityu/aop_anno/bean.xml");
@Test
public void testname() throws Exception {
IUserDao proxy = (IUserDao) ac.getBean("userDao");
proxy.save();
}
}
注意:spring会根据目标对象是否实现了接口来自动选择是使用JDK动态代理还是Cglib子类代理方式。测试:其中plainDao是一般类,没有实现接口
@Test
public void testname() throws Exception {
IUserDao proxy = (IUserDao) ac.getBean("userDao");
System.out.println(proxy.getClass());
PlainDao proxyDao = (PlainDao) ac.getBean("plainDao");
System.out.println(proxyDao.getClass());
}
输出:
class com.cityu.aop_anno.UserDao
class com.cityu.aop_anno.PlainDao$$EnhancerByCGLIB$$a0ebc021
同时,如果核心方法相同,切入点表达式可以抽取:
@Component
@Aspect//切面类
public class Aop {
//切入点,代表UserDao委托对象中的所有方法
@Pointcut("execution(* com.cityu.aop_anno.UserDao.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void begin(){
System.out.println("begin transaction");
}
@After("pointCut()")
public void commit(){
System.out.println("commit transaction");
}
}
几个注解的区别:
//前置通知
@Before
//最终通知:无论是否出现异常都会执行
@After
//返回后通知,目标方法出现异常不执行
@AfterReturning
//异常通知:目标方法出现异常执行
@AfterThrowing
//环绕通知
@Around("pointCut()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("before Around");
Object returnValue = pjp.proceed();
System.out.println("after Around");
}
输出
before Around
begin transaction
Save data into database! by PlainDao
after Around
commit transaction
不需要使用注解,这一阶段只在AOP类编写需要扩展的方法代码即可。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userDao" class="com.cityu.aop_xml.UserDao">bean>
<bean id="plainDao" class="com.cityu.aop_xml.PlainDao">bean>
<bean id="aop" class="com.cityu.aop_xml.Aop">bean>
<aop:config>
<aop:pointcut expression="execution(* com.cityu.aop_xml.*.*(..))" id="pc"/>
<aop:aspect ref="aop">
<aop:before method="begin" pointcut-ref="pc"/>
<aop:after method="commit" pointcut-ref="pc"/>
<aop:around method="around" pointcut-ref="pc"/>
aop:aspect>
aop:config>
beans>
配置XML文件的步骤:目标类bean,切面类bean;配置切面类(重点):
切入点表达式:指定哪些目标方法需要过滤或者扩展
切面配置:需要引用切面类和切入点表达式
所以这里的关键其实是如何书写切入点表达式。
可以对指定的方法进行拦截,从而给指定的方法所在的类生成代理对象,实现方法扩展。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern))
多个表达式之间用“||”或者“or”分隔,
使用“&i;&i;”或者“&&”分隔是没有意义的,不起拦截作用,且没有产生代理对象
表达式之前使用“!”或者“ not”(注意加空格)表示不拦截某个方法,但是产生代理对象了