【Spring学习笔记】AOP编程学习笔记

一、AOP编程使用的场合

        我们在使用Hibernate或者JDBC进行数据库编程的时候,通常在业务层会遇到这样的代码:获取session、打开事物、按业务逻辑执行DAO对象访问,捕获异常回滚操作或者无异常提交操作、关闭事物、关闭session。几乎在每个业务逻辑处理时都会进行类似顺序的处理,而这些代码是重复的并且是无法通过继承关系来进行代码复用,虽然使用模板模式可以进行一定程度的代码重用,但是依旧会有大量的重复代码出现。这种业务层操作数据的代码基本是以下这个样子的:

public boolean hasMatchUser(String userName,String userPwd)
{
	Session session = HibernateSessionFactory.getSession();
	Transaction tx = null;
	try
	{
		tx = session.beginTransaction();
		int count = userDao.getMatchCount(session,userName,userPwd);
		tx.commit();
		return count >= 1;
	}
	catch (Exception e)
	{
		e.printStackTrace();
		if (tx != null)
			tx.rollback();
		return false;
	}
	finally
	{
		session.close();
	}
}
二、如何使用AOP来解决类似数据库事务处理

        AOP(Aspect Oriented Programming)称之为面向切片编程,在Spring当中就是利用JAVA的动态代理技术或者CGLib对JVM二进制码的修改实现对切点(符合条件的目标类的方法)进行增强。AOP编程通常将切点进入前、返回后、围绕切点或者抛出异常后进行一些特殊处理,这样业务类就可以专注于业务,而一些通用的处理则可以交给AOP的增强类来完成。

        针对上面提到的业务层操作数据库的问题,我们可以将创建session、打开事务、发生异常回滚或者无异常提交、关闭事物、关闭session这些每个业务流程里都要完成的过程提取到AOP的增强当中,而业务层中只保存针对业务操作本身的代码。

1.创建一个增强类

创建增强类只需要在一个普通的类上增加一个@Aspect注解即可,例如

@Aspect
public class TransactionAdvisor
{
}

2.编写一个增强方法,并使用@Around注解告知Spring容器,这个增强方法将被用在参数指定的切点周围(切点前和切点后)。与@Around类似的增强类型注解还有:

@Before:增强将被在在切点方法前调用,然后调用切点方法

@AfterReturning:增强将被在切点方法返回后调用,可以使用参数returning来指定增强方法的哪个参数用于接收切点方法的返回值(Object对象)

@AfterThrowing:增强将在切点方法抛出异常后被调用,可以使用参数throwing来指定增强方法的那个参数用于接收被抛出的异常对象

@After:类似于finnal代码段,即无论是正常返回还是抛出异常,这个增强都将被调用,这里没有参数来获得切点方法返回或者抛出的对象

@Around:在切点调用的周围来调用增强方法,这里切点方法的调用由增强方法通过参数ProceedingJoinPoint的使用来完成调用

        所有的增强方法都可以包括一个ProceedingJoinPoint类型的对象作为入参,这个参数包含了切点方法的签名、返回值、入参的信息,同时可以执行切点函数的调用(通常在@Around类型的增强中会这么做)。

        所有增强类型注解都包含一个默认的value参数,这个参数用于标识哪些切点将会被当前增强函数所增强。这个参数通常被称为切点函数,常用的切点函数有:

a) execution("匹配表达式"):使用匹配表达式来表示匹配的切点,可以使用通配符,最细粒度可以至方法。例如:execution(* com.sample.*.get*(..))表示在com.sample包下所有类的以get为前缀包含任意参数返回任意类型的方法的切点。

b) args(“匹配表达式”):使用匹配表达式来表示切面方法的入参,只要连接点方法入参的数量、类型(包括实现类和子类)符合表达式都会被认为是切点。例如args(com.sample.Waiter)表示,所有有且只有一个参数,并且参数为com.sample.Waiter或者其子类的方法都是切点。只允许使用+通配符,但实际无意义。

c) within(匹配表达式):匹配表达式所表示的范围内的类的方法都会是切面,匹配表达式最细粒度为类,因此可以是包或者类的匹配表达式,允许使用所有通配符;

d)target(匹配表达式):匹配表达式所表示的类和其子类的方法会是切面,最细粒度为类,只允许使用+通配符,实际无意义。

         在这些切点函数当中允许使用三种通配符:

a) *表示0~n的字符,但是只能表示一个元素;

b) ..表示0~n的字符,可以表示多个元素,只允许用在类名(必须与*一起使用,例如com..*表示com及com子孙包下的任意类)和方法参数(可以单独使用);

c) +表示类、接口的本身以及所有实现/继承类;

       在这个实例当中,我们编写了一个@Around增强类型的织面来实现对业务类的session管理、事物管理的功能:下面的代码分别表示业务类和增强类

//业务类
public class UserService
{
	public User userLogin(String userName,Session session)
	{
		System.out.println("UserService::userLogin");
		UserDao dao = new UserDao();
		User user = dao.loadUser(userName,session);
		
		if (user != null)
			dao.updateUser(user,session);
		
		return user;
	}
	
	public void insertUser(Session session)
	{
		System.out.println("UserService::insertUser");
		UserDao dao = new UserDao();
		dao.insertUser(null,session);
	}
	
	public void ExceptionOperate(Session session) throws Exception
	{
		System.out.println("UserService::ExceptionOperate");
		throw new Exception("Throw a exception");
	}
}

//增强类
@Aspect
public class TransactionAdvisor
{
	@Around("execution(* com.baobaotao.service.*Service.*(..,com.baobaotao.service.Session))")
	public Object transactionHandle(ProceedingJoinPoint pjp)
	{
		Session session = new Session();
		Transaction tx = null;
		try
		{
			tx = session.createTransaction();
			tx.beginTransaction();
			Object object = pjp.proceed();
			tx.commit();
			return object;
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			if (tx != null)
				tx.roolback();
			return null;
		}
		finally
		{
			session.destoryTransaction(tx);
		}
	}
}
3.修改配置文件,使Spring容器能够自动搜索增强类,并使用增强类方法对切面进行增强。这里主要有两点需要做

a)使用使Spring能够处理aspectj的注解

b)使用匿名bean的模式来注册所有的增强类

另外需要注意,由于使用了aop命名空间,在xmlns和xsi:schemaLocale处做相应的处理




	
	
	
4.在应用中按正常习惯使用Bean
public class MainEntry
{
	public static void main(String[] args) throws Exception
	{
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		UserService userService = (UserService) ctx.getBean("userService");
		User user = userService.userLogin("admin",null);
		if (user != null)
			System.out.println("User login success!" + user);
		else
		{
			System.out.println("User login failed!");
		}
		
		System.out.println("==================");
		userService.insertUser(null);
		
		System.out.println("====================");
		userService.ExceptionOperate(null);
	}
}
三、命名切点的定义

        对于一些在应用中多次使用的切点函数,我们可以用命名切点的方法来进行定义,以避免重复输入。定义命名切点的方法是使用@Pointcut注解,该注解的默认值使用通常的切点函数来表示,用一个例子来表示这个功能:

public class NamedPointcut
{
	@Pointcut("* com.baobaotao.service.*.*(..)")
	private void allFunctionInClass(){ }
	
	@Pointcut("* com.baobaotao.service.*.*(..)")
	protected void allFunctionInPacket(){ }
	
	@Pointcut("* com.baobaotao.service.*.*(..)")
	public void allFunctionInAllScope(){ }
}
类中的方法名即为这些命名切点的名字,并且正如这些方法名的含义所示,方法的访问修饰符(private/protected/public)表示了这个命名切点可以使用的范围。在使用这些命名切点时,只需要使用命名切点的名字来代替原本切点函数的位置即可,例如:@Around("allFunctionInAllScope")。

四、性能相关

        在Spring的AOP框架中,使用JDK的动态代理和CGLib两种方式来实现AOP功能。当目标类是通过实现一个接口的方式出现时,Spring选择使用JDK的动态代理模式实现横切面增强,否则则使用CGLib方式来实现。通过查询资料发现,由于CGLib是通过修改类二进制代码的方式来实现动态代理的,因此在创建类的性能上会低于JDK的动态代理方式,而JDK的动态代理则是在调用目标类方法时进行拦截并处理增强,因此在调用方法时的性能会低于CGLib实现的动态代理类。基于这样的性能对比,我们可以对Bean做这样的处理,如果生命周期为singleton的,即只需要创建一次的AOP类,不要使用实现接口的方式来定义,使得Spring框架使用CGLib的模式来实现动态代理。而生命周期为prototype的Bean,则使用实现接口的方法来定义,使得Spring框架使用JDK的动态代理模式来生成动态代理对象,以提高动态代理创建速度。

        我们还可以在节点中增加proxy-target-class="true"属性来强制Spring框架将所有的动态代理使用CGLib的模式来实现

你可能感兴趣的:(JAVA)