spring当中AOP是OOP的延续,是Aspect Oriented Programming 的缩写,意思是面向切面编程,可以通过预编译的方式原理实现在不修改源代码的情况下给程序动态的添加功能的一种技术;AOP追求的是调用者和被调用者之间的解耦合,AOP可以说是
spring当中AOP的切面代表着n个bean的一个集合,这n个bean都有其共同的特点
spring当中AOP的一些概念
切面(Aspect):官方抽象定义为"一个关注点的模块化,这个关注点可能会横切多个对象。就是横跨应用程序多个模块的功能,被模块化的特殊对象
通知(Advice):横切面必须要完成的工作
目标(Target):被通知的对象
代理(Proxy):向目标对象应用通知之后创建的对象。
连接点(JoinPoint):程序执行过程中的特定位置:如类某个方法调用前、调用后、方法抛出异常后,
切入点(Point):每个类都拥有多个连接点:例如 ArithmeticCalculator的所有方法实际上都是连接点,即连接点时程序类中客观存在的事物。AOP通过切点定位到特定的
连接点。
织入:把切面织入到目标对象当中去。
spring当中 AspectJ中支持的通知类型
@Before:前置通知,在方法执行前执行。
@After:后置通知,在方法执行之后执行
@AfterRunning: 返回通知,在方法犯规结果之后执行
@AfterThrowing: 异常通知,在方法抛出异常之后执行。
@Around:环绕通知,围绕着方法的执行.
下面是具体的程序代码来实现spring当中的AOP:
1、首先新建一个maven项目,选择Archetype的时候选择 maven-archetype-quickstart
2、项目结构目录如下:
3、pom.xml文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springaoptest</groupId> <artifactId>gupaoedu-vip-springaop</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>gupaoedu-vip-springaop</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- dependency versions --> <spring.version>3.2.6.RELEASE</spring.version> <servlet.api.version>2.4</servlet.api.version> </properties> <dependencies> <!-- spring framework start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <!-- spring framework end --> <!-- requied start --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.2</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>3.1</version> </dependency> <!-- other start --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.4</version> </dependency> <!-- other end --> </dependencies>
注意 在 pom.xml 文件当中有关 spring 的依赖部分有:
spring-core、spring-beans、spring-aop、spring-context、spring-context-support、spring-expression、spring-tx、spring-test 包
spring-test是 spring与junit两个整合锁建立的包
pom.xml文件当中除了spring的包还导入了 aopalliance、aspectjweaver、cglib、junit、log4j 这些jar包
导入完成之后,run as ---> maven install
4、在src/main/resource 这个目录下创建 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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"> <!-- 注解扫描 ,扫描有注解所标注的类 --> <context:component-scan base-package="com.zwz.test"/> <!-- 配置注解驱动 --> <context:annotation-config /> <!-- log4j 记录日志的相关配置 --> <bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetClass" value="org.springframework.util.Log4jConfigurer" /> <property name="targetMethod" value="initLogging" /> <property name="arguments"> <list> <value>classpath:log4j-cfg.xml</value> </list> </property> </bean> <!-- 引入三个配置文件 --> <import resource="application-aop.xml"/> </beans>
log4j-cfg.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//LOGGER" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd"> <log4j:configuration> <appender name="stdout" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%-5p] [%d{HH:mm:ss}] %c - %m%n" /> </layout> </appender> <appender name="file" class="org.apache.log4j.DailyRollingFileAppender"> <param name="file" value="/opt/gupaoedu-vip-springaop/logs/webapp.log" /> <param name="append" value="true" /> <param name="datePattern" value="'.'yyyy-MM-dd" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%-5p] [%d{yyyy-MM-dd HH:mm:ss}] %c - %m%n" /> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="AcceptOnMatch" value="true" /> </filter> </appender> <appender name="warn" class="org.apache.log4j.DailyRollingFileAppender"> <param name="file" value="/opt/gupaoedu-vip-springaop/logs/warn.log" /> <param name="append" value="true" /> <param name="datePattern" value="'.'yyyy-MM-dd" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%-5p][ %t ] [%d{yyyy-MM-dd HH:mm:ss}] %c - %m%n" /> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="WARN" /> <param name="LevelMax" value="WARN" /> <param name="AcceptOnMatch" value="true" /> </filter> </appender> <appender name="error" class="org.apache.log4j.DailyRollingFileAppender"> <param name="file" value="/opt/gupaoedu-vip-springaop/logs/error.log" /> <param name="append" value="true" /> <param name="datePattern" value="'.'yyyy-MM-dd" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%-5p][ %t ] [%d{yyyy-MM-dd HH:mm:ss}] %c - %m%n" /> </layout> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="LevelMin" value="ERROR" /> <param name="LevelMax" value="ERROR" /> <param name="AcceptOnMatch" value="true" /> </filter> </appender> <logger name="org.springframework.jdbc.core.JdbcTemplate" additivity="true"> <level value="info" /> </logger> <root> <level value="info" /> <appender-ref ref="stdout" /> </root> </log4j:configuration>
5、接下来编写程序来完成 spring 当中的 AOP 操作:
首先先编写一个要 AOP 要作用的目标对象(这里模仿一个web项目当中的Service层):
@Service public class StudentService { public boolean addStudent(Student stu){ System.out.println("添加学生"); return true; } public void delStudent(int id) throws Exception{ System.out.println("删除学生"); //为了等下测试springAOP当中的异常通知,这里抛出一下自定义的异常 throw new Exception("抛出自己定义的异常!"); } public boolean modifyStudent(Student stu){ System.out.println("修改学生"); return true; } public Student queryStudent(int id){ System.out.println("查询学生"); Student stu = new Student(); stu.setName("小虹"); stu.setAge(25); return stu; } }
Student类如下:
public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
6、接下来需要编写 Aspect 的 AOP 切面 (Aspect的切面有两种方式进行注入: 第一种,使用注解的方式 第二种:使用 在spring的xml当中配置的方式,先使用第二种):
TestAspect的代码如下:
public class TestAspect { //方法的前置通知 public void before(JoinPoint joinPoint){ System.out.println("方法的前置通知"); } //方法的后置通知 public void after(JoinPoint joinPoint){ System.out.println("方法的后置通知"); } //方法的返回通知 public void returnCall(JoinPoint joinPoint){ System.out.println("方法的返回通知"); } //方法的异常通知 public void throwCall(JoinPoint joinPoint){ System.out.println("方法的异常通知"); } }
7、spring当中编写aop的切面(新建一个application-aop.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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd"> <!-- 声明一个需要织入到虚拟切面的逻辑(切面) --> <bean id="testAspect" class="com.zwz.test.aspect.TestAspect"></bean> <aop:config> <aop:aspect ref="testAspect"> <!-- 下面 expression中内容的含义是扫描 com.zwz.test.service下所有的类的所有方法,并且传入参数任意 --> <aop:pointcut expression="execution(* com.zwz.test.service..*(..))" id="logPointcut"/> <aop:before method="before" pointcut-ref="logPointcut"/> <aop:after-returning method="returnCall" returning="boolean" pointcut-ref="logPointcut"/> <aop:after method="after" pointcut-ref="logPointcut"/> <aop:after-throwing method="throwCall" pointcut-ref="logPointcut"/> </aop:aspect> </aop:config> </beans>
8、编写一个测试程序开始对aop进行测试:
TestAOP.java 文件如下:
package com.zwz.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.zwz.test.entity.Student; import com.zwz.test.service.StudentService; //注意这里的两个注解, ContextConfiguration 这里加载的是 spring的核心配置文件 ,使用 ContextConfiguration 即可与junit进行集成,在junit当中加载spring容器 @ContextConfiguration(locations={"classpath*:applicationContext.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class TestAOP { //使用autowired注解进行依赖注入 @Autowired private StudentService studentService; @Test public void testadd(){ Student stu = new Student(); stu.setName("李四"); stu.setAge(11); studentService.addStudent(stu); } @Test public void testdel(){ try { studentService.delStudent(0); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Test public void testModify(){ Student stu = new Student(); stu.setName("李四"); stu.setAge(11); studentService.modifyStudent(stu); } @Test public void testQuery(){ Student st = studentService.queryStudent(0); System.out.println(st.getName()); } }
使用junit测试运行每段代码,即可看到各个方法的通知效果,如图例所示:
/* System.out.println("-----------------------------------"); System.out.println(joinPoint.getKind()); System.out.println(joinPoint.getTarget()); //生成以后的代理对象 System.out.println(joinPoint.getThis()); //当前类本身 System.out.println(joinPoint.getArgs()); //这个方法的作用是获取到被切面对象中方法的参数值 System.out.println(joinPoint.getSignature()); // getSignature 这个方法中有许多的获取被织入对象的引用,从中可以获取方法名称,方法的字节码,方法的声明类型 System.out.println(joinPoint.getStaticPart()); // 获取到执行的方法, 里面包含执行方法返回值的类型,MemberManageService System.out.println("-----------------------------------"); */
注意到在 AOP ,切面编程中的连接点当中,我们可以从中get到下面的信息:
9、然后我们注释掉 application-aop.xml文件当中的有关于Aspect 的部分,接下来使用的是注解的方式进行AOP的操作,首先先注释掉applicaionContext.xml当中配置的testAspect相关部分,并且在 applicaionContext.xml文件中加上这个 <aop:aspectj-autoproxy proxy-target-class="true" /> ,
<aop:aspectj-autoproxy proxy-target-class="true" /> 这句话当中 proxy-target-class 为true时候,代表 在aop的时候使用的是 cglib的代理
<aop:aspectj-autoproxy proxy-target-class="false" /> 这句话当中 proxy-target-class 为false时候,代表 在aop的时候使用的是 JDK的代理
这里再编写一个Aspect:
TestAspect2.java
package com.zwz.test.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component//声明这个类是被SpringIOC容器来管理的,如果不声明,就无法做到自动织入 @Aspect//这个类被声明为是一个需要动态织入到我们的虚拟切面中的类 public class TestAspect2 { //声明切点 //因为要利用反射机制去读取这个切面中的所有的注解信息 @Pointcut("execution(* com.zwz.test.service..*(..))") public void pointcutConfig(){} //方法的前置通知 @Before("pointcutConfig()") public void before(JoinPoint joinPoint){ System.out.println("方法的前置通知"); } //方法的后置通知 @After("pointcutConfig()") public void after(JoinPoint joinPoint){ System.out.println("方法的后置通知"); } //方法的返回通知 @AfterReturning("pointcutConfig()") public void returnCall(JoinPoint joinPoint){ System.out.println("方法的返回通知"); } //方法的异常通知 @AfterThrowing("pointcutConfig()") public void throwCall(JoinPoint joinPoint){ System.out.println("方法的异常通知"); } }
运行 TestAOP.java中的测试程序,最终输出结果与 非注解方式 在xml文件配置的方式输出的结果一样.
AOP到底能够干嘛?
Authentication 权限
Caching 缓存
Debugging 调试
Logging 日志
Transaction Manager 事务管理
Mointer 监听
Intercepter 拦截器
ContextPassing 内容传递