1.传统编程模式的弊端
通过以下案例你将会发现,虽然在调用addUser()方法时实现了日志的输出,但这个程序有一个问题,
就是不得不在addUser()方法的主业务代码前面添加"log.info(“开始添加用户。。。”)语句,
以及在主业务代码后面添加"log.info("完成添加用户。。。)"语句。
这些语句以硬编码的方式混入主业务代码中,难以分割,可移植性差。
解决问题的思路是将这些日志之类的功能独立出来,作为独立的一个或多个类,在需要时调用,
而且最好不是显示调用,因为如果时显示调用,仍然要在目标方法前面或后面嵌入代码,改进效果有限,最好能自动调用。
这种设想就是一种面向切面编程(AOP)的思想。
应用实例:创建一个分层架构项目,在添加用户的同时输出日志。
项目目录结构图
IUserDao.java
package com.xiaochen.dao;
public interface IUserDao {
public void addUser();
}
UserDaoImpl.java
package com.xiaochen.dao;
import org.springframework.stereotype.Component;
//使用注解方式定义一个Bean,其id为userDao
@Component("userDao")
public class UserDaoImpl implements IUserDao{
@Override
public void addUser() {
System.out.println("新增一个用户到数据库中");
}
}
UserService.java
package com.xiaochen.service;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import com.sun.istack.internal.logging.Logger;
import com.xiaochen.dao.IUserDao;
//使用注解方式定义Bean,其id为userService
@Component("userService")
public class UserService {
//使用注解注入在UserDaoImpl定义好的Bean
@Resource(name="userDao")
private IUserDao userDao;
//用于实现在addUser操作时记录日志
private static final Logger log=Logger.getLogger(UserService.class);
//给域属性赋值
public void setUserDao(IUserDao userDao) {
this.userDao=userDao;
}
//调用IUserDao的addUser()方法
public void addUser() {
log.info("使用传统方式测试开始添加用户。。。");
userDao.addUser();
log.info("使用传统方式测试完成添加用户。。。");
}
}
Test.java
package com.xiaochen.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xiaochen.service.UserService;
public class Test {
public static void main(String[] args) {
//1.加载配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
//2.从配置文件中获取Bean
UserService userService=(UserService)context.getBean("userService");
//3.使用Bean
userService.addUser();
}
}
log4j.properties
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.com.xiaochen=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
applicationContext.xm
<?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: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">
<!-- 配置组件扫描器,在指定的基本包中扫描注解 -->
<context:component-scan base-package="com.xiaochen"/>
</beans>
运行结果图
2.AOP的一些基本概念
a.切面:
一个单独的类,通常在此类种定义一些辅助功能或系统功能的方法。
b.切点:
主业务类的有些方法只想专注于完成核心业务逻辑,不想混入一些辅助性的功能,
可以把这些方法定义为切点,切点就是确定什么位置放置切面。
c.通知:
切点确定了使用切面的位置,但什么时候应用切面就由通知来确定。
d.织入:
切面和切点都是独立的功能类,通过织入才能让切面切入切点,
所以织入就是一种配置过程,让切面能准确的切入指定的位置。
应用实例:通过面向切面编程方法改造上面这个项目。
项目目录结构图
关键代码
<!--定义切面-->
<bean id="loggerBefore" class="com.xiaochen.aop.LoggerBefore"/>
<bean id="loggerAfter" class="com.xiaochen.aop.LoggerAfter"/>
<aop:config>
<!-- 定义切点 -->
<aop:pointcut expression="execution(* com.xiaochen.service.UserService.addUser())" id="pointcut"/>
<!-- 通知切点,切入advice-ref指定的bean(切面)里面的方法,切入位置在前还是在后由切面的接口决定 -->
<aop:advisor pointcut-ref="pointcut" advice-ref="loggerBefore"/>
<aop:advisor pointcut-ref="pointcut" advice-ref="loggerAfter"/>
</aop:config>
需要修改的地方与主要步骤
1.删除日志相关代码
2.新建包aop用于定义两个类作为切面(一个类实现前置通知接口,一个类实现后置通知接口)
3.在spring配置文件中引入aop命名空间,依次完成:定义切面,定义切点,通知切点
改动的代码只有1处(即删除UserService中addUser()方法前后的两句日志相关代码),其他代码同上一个传统项目
applicationContext.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置组件扫描器,在指定的基本包中扫描注解 -->
<context:component-scan base-package="com.xiaochen"/>
<!--定义切面-->
<bean id="loggerBefore" class="com.xiaochen.aop.LoggerBefore"/>
<bean id="loggerAfter" class="com.xiaochen.aop.LoggerAfter"/>
<aop:config>
<!-- 定义切点 -->
<aop:pointcut expression="execution(* com.xiaochen.service.UserService.addUser())" id="pointcut"/>
<!-- 通知切点,切入advice-ref指定的bean(切面)里面的方法,切入位置在前还是在后由切面的接口决定 -->
<aop:advisor pointcut-ref="pointcut" advice-ref="loggerBefore"/>
<aop:advisor pointcut-ref="pointcut" advice-ref="loggerAfter"/>
</aop:config>
</beans>
LoggerBefore.java
package com.xiaochen.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import com.sun.istack.internal.logging.Logger;
//实现前置通知接口,意味着将来这个方法会作用到目标的开始位置
public class LoggerBefore implements MethodBeforeAdvice{
private static final Logger log=Logger.getLogger(LoggerBefore.class);
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
log.info("aop方式,开始添加学生。。。");
}
}
LoggerAfter.java
package com.xiaochen.aop;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import com.sun.istack.internal.logging.Logger;
public class LoggerAfter implements AfterReturningAdvice{
private static final Logger log=Logger.getLogger(LoggerAfter.class);
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
log.info("aop方式,完成添加学生。。。");
}
}
运行结果图