AOP是Spring框架除了IOC之外的另一个核心概念。
我们知道Java是面向对象编程(OOP)的语言,也就是将所有的一切都看成对象,通过对象与对象之间相互作用来解决问题的一种编程思想。而AOP是OOP的一个补充:在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
AOP的优点如下:
- 降低模块之间的耦合度
- 使系统容易扩展
- 更好的进行代码复用
- 非业务代码更加集中不分散,便于统一管理
- 业务代码更加简洁纯粹
下面通过代码展示一下什么是AOP
先创建一个接口模拟添加用户和删除用户
public interface UserDao{
public void addUser();
public void deleteUser();
}
创建接口的实现类UserDaoImpl
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("正在添加用户");
}
public void deleteUser(){
System.out.println("正在删除用户");
}
}
在测试方法中创建UserDaoImpl对象并且调用方法
public class UserDaoTest{
public static void main(String[] args){
UserDao userDao = new UserDaoImpl();
userDao.addUser();
userDao.deleteUser();
}
}
上面写了简单的业务代码,现在添加一个功能,就是在每一个方法执行的时候打印日志信息,可以在每个方法前后输出提示信息这样简单地实现
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("addUser方法执行之前");
System.out.println("正在添加用户");
System.out.println("addUser方法执行之后");
}
public void deleteUser(){
System.out.println("deleteUser方法执行之前");
System.out.println("正在删除用户");
System.out.println("deleteUser方法执行之后");
}
}
这样功能就已经实现,但是这种方式让业务代码和日志代码耦合度非常高,不利于后期维护。
我们可以发现,每个方法中的日志信息相似度非常高,那么有没有可能将这部分代码抽取出来进行封装以便统一维护呢?按照这样的思考角度,我们要做的就是把上面方法中同个位置的代码抽取出来形成一个切面,将这个切面封装成一个对象,将所有的日志代码封装到这个对象中去。这就是AOP的思想。
如何实现AOP呢?
可以使用动态代理的方式来实现。
我们希望UserDaoImpl只进行业务代码,不进行打印日志的工作,那么就需要一个对象来代替UserDaoImpl实现日志功能,这个对象就是代理对象。要想让这个代理对象能打印每个方法的日志功能,首先它要具备被代理对象的所有功能,然后在这基础上扩展日志功能。
首先,删除UserDaoImpl类中打印日志的部分
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("正在添加用户");
}
public void deleteUser(){
System.out.println("正在删除用户");
}
}
然后创建InvocationHandlerImpl类实现InvocationHandler接口,作为一个动态代理类。
public class InvocationHandlerImpl implements InvocationHandler{
//声明目标类接口
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao){
this userDao = userDao;
//类加载器
ClassLoader classLoader = UserDao.class.getClassLoader();
//被代理对象实现的所有接口
Class[] classes = userDao.getClass().getInterfaces();
//返回代理后的对象
return Proxy.newProxyInstance(classLoader,classes,this);
}
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
System.out.println(method.getName()+"方法执行之前");
Object obj = method.invoke(userDao,args);
System.out.println(method.getName()+"方法执行之后");
return obj;
}
}
上面代码中,createProxy方法是InvacationHandlerImpl类提供给外部调用的方法,传入需要被代理的对象,createProxy方法会返回一个代理对象。
createProxy方法完成了两项工作:
1、将外部传进来的被代理对象保存到成员变量中,因为业务方法调用时需要用到被代理对象。
2、通过Proxy.newProxyInstance方法创建一个代理对象。其中Proxy.newProxyInstance方法的参数解释如下:
(1)Java中对象是JVM根据运行时类来创建的,此时需要动态创建一个代理对象,可以使用被代理对象的运行时类来创建代理对象:ClassLoader classLoader = userDao.class.getClassLoader();
获取的是被代理对象的运行时类。
(2)同时代理对象需要具备被代理对象的所有功能,即需要拥有被代理对象的所有接口,Class[] classes = userDao.getClass().getInterfaces();
就是执行这一功能。
method.invoke()是通过反射机制来调用被代理对象的方法,即业务方法。
所以在method.invoke()前后添加打印日志信息,就等同于在被代理对象业务方法前后添加打印日志信息,这样就做到了业务代码与日志代码分离。
创建测试类
public class Test{
public static void main(String[] args){
//被代理对象
UserDao userDao = new UserDaoImpl();
InvovationHandlerImpl ihi = new InvocationHandlerImpl();
//代理对象
UserDao userDao2 = (UserDao)ihi.createProxy(userDao);
userDao2.addUser();
userDao2.deleteUser();
}
}
执行结果如图:
以上是通过jdk动态代理实现AOP的方法。但是在Spring框架中不需要那么复杂,Spring已经对这个过程进行了封装。
在Spring框架中,不需要创建一个动态代理类,而是创建一个切面类,Spring底层会自动根据切面类以及目标类生成一个代理对象。
创建一个切面类
@Aspect
@Component
public class MyAspect{
@Before("execution(* com.aspect.UserDaoImpl.*(..))")
public void before(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行之前");
}
@After("execution(* com.aspect.UserDaoImpl.*(..))")
public void after(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行之后");
}
}
在上面的代码中,以before方法做说明,@Before
注解表明before方法在方法执行之前执行,execution(* com.aspect.UserDaoImpl.*(..))
表示切入点是
com.aspect包下的UserDaoImpl类中的所有方法。After方法同理。同时目标类也需要加注解@Component
@Component(value="userDao")
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("正在添加用户");
}
public void deleteUser(){
System.out.println("正在删除用户");
}
}
在spring.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"
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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!--自动扫描-->
<context:component-scan base-package="com.aspect"></context:component-scan>
<!--使Aspect注解生效,为目标类自动生成代理对象-->
<aop:aspect-autoproxy></aop:aspect-autoproxy>
</beans>
1、将com.aspect包中的类扫描到IOC容器中
2、添加aop:aspect-autoproxy注解,Spring容器会结合切面类和目标类自动生成动态代理对象,Spring框架的AOP底层就是通过动态代理方式完成AOP的。
创建测试类
public class Test{
public static void main(String[] args){
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//获取代理对象
UserDao userDao = (UserDao)applicationContext.getBean("userDao");
userDao.addUser();
userDao.deleteUser();
}
}
执行结果如图
以上就是简单的用注解的方式实现Spring框架的aop。