Spring 学习(五)AOP

一、Spring的核心:AOP编程及HelloWorld

面向切面编程(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");//业务方法
    }
}

二、 注解的方式实现AOP

1. 步骤

所需jar文件:

spring-aop-3.2.5.RELEASE.jar
aspectjrt.jar
aspectjweaver.jar
aopalliance.jar

bean.xml

引入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>

开启aop注解扫描(同时也需要开启IOC的注解扫描)



2. 注解实现AOP的HelloWorld

@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

三、 XML方式实现AOP

1. 引入jar包(同上)

2. 编写目标类和AOP类

不需要使用注解,这一阶段只在AOP类编写需要扩展的方法代码即可。

3. 配置XML文件


    <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;配置切面类(重点):

切入点表达式:指定哪些目标方法需要过滤或者扩展
切面配置:需要引用切面类和切入点表达式

所以这里的关键其实是如何书写切入点表达式。

4. 切入点表达式

可以对指定的方法进行拦截,从而给指定的方法所在的类生成代理对象,实现方法扩展。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern))

多个表达式之间用“||”或者“or”分隔,
使用“&i;&i;”或者“&&”分隔是没有意义的,不起拦截作用,且没有产生代理对象
表达式之前使用“!”或者“ not”(注意加空格)表示不拦截某个方法,但是产生代理对象了

你可能感兴趣的:(Spring,spring,aop)