一、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)使用
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的动态代理模式来生成动态代理对象,以提高动态代理创建速度。
我们还可以在