在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理是使用了JDK的动态代理技术
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
STS(Spring Tool Suite)中自带AJDT插件,可以直接创建项目。一般先较新版本的IDE工具都带有AJDT插件,如果没有需要自行安装。
1、打开Eclipse“Help”-->"Eclipse Marketplace..."
2、搜索AJDT,出现搜索结果
3、选择安装的版本,点击“Install”(我这里安装过了,所以是Installed)
1、“New”-->"Project..."
2、找到“AspectJ Project”
3、点击“Next”,然后在弹出的创建项目窗口中输入项目名称,点击“Finish”即可
首先实现一个简单的切面,由一个业务类和一个切面类组成
业务类的代码如下
package com.codestd.aspectj.test;
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sleep(){
System.out.println(this.name+"睡着了");
}
public static void main(String[] args){
People p = new People();
p.setName("Jaune");
p.sleep();
}
}
接下来定义一个切面类,用于捕获sleep方法,在调用sleep方法前打印”xxx要睡觉了”
package com.codestd.aspectj.test;
import java.lang.reflect.Field;
import java.util.Date;
public aspect PeopleInterceptor {
pointcut callSleep():call(void com.codestd.aspectj.test.People.sleep());
before() : callSleep(){
Object obj = thisJoinPoint.getTarget();
try {
Field field = obj.getClass().getDeclaredField("name");
field.setAccessible(true);
String name = (String)field.get(obj);
System.out.println(name+"要睡觉了。"+(new Date()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
pointcut:定义切面,语法 pointcut
用反编译工具查看编译后的People类,代码如下
//people类
package com.codestd.aspectj.test;
import java.io.PrintStream;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.reflect.Factory;
public class People
{
private String name;
private static final JoinPoint.StaticPart ajc$tjp_0;
private static void ajc$preClinit()
{
Factory localFactory = new Factory("People.java", People.class);ajc$tjp_0 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("1", "sleep", "com.codestd.aspectj.test.People", "", "", "", "void"), 22);
}
public String getName()
{
return this.name;
}
public void setName(String name)
{
this.name = name;
}
public void sleep()
{
System.out.println(this.name + "睡着了");
}
public static void main(String[] args)
{
People p = new People();
p.setName("Jaune");
People localPeople1 = p;
JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, null, localPeople1);
PeopleInterceptor.aspectOf().ajc$before$com_codestd_aspectj_test_PeopleInterceptor$1$68d231da(localJoinPoint);
localPeople1.sleep();
}
static {}
}
//切面类
package com.codestd.aspectj.test;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Date;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class PeopleInterceptor
{
public static PeopleInterceptor aspectOf()
{
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com_codestd_aspectj_test_PeopleInterceptor", ajc$initFailureCause);
}
return ajc$perSingletonInstance;
}
public static boolean hasAspect()
{
return ajc$perSingletonInstance != null;
}
static
{
try
{
}
catch (Throwable localThrowable)
{
ajc$initFailureCause = localThrowable;
}
}
@Before(value="callSleep()", argNames="")
public void ajc$before$com_codestd_aspectj_test_PeopleInterceptor$1$68d231da(JoinPoint thisJoinPoint)
{
Object obj = thisJoinPoint.getTarget();
try
{
Field field = obj.getClass().getDeclaredField("name");
field.setAccessible(true);
String name = (String)field.get(obj);
System.out.println(name + "要睡觉了。" + new Date());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
通过代码可以看到sleep方法之前多了一些代码,但是这些代码并不是我们写的。这些代码是aspectJ编译器添加的,aspectJ是编译期的AOP框架,所以aspectJ项目在编译的时候aspectJ编译器会将横切代码自动加入到业务代码中,从而实现AOP编程。
AspectJ的使用不再深入讲解,这里我们重点讲解的是Spring的AOP,所以下面我们把重心放到Spring AOP中。
Spring AOP是运行时的AOP框架,采用的是JDK动态代理技术
1、添加AOP依赖
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.7version>
dependency>
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.1version>
dependency>
2、Spring AOP Schema
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"
3、AOP配置
<aop:aspectj-autoproxy />
有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,
当配为时,表示使用CGLib动态代理技术织入增强。
不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
创建一个业务类,这里我们采用注解的方式。如非特殊需求,后面的Spring讲解中将一律用Spring注解的方式配置Bean
package com.codestd.springstudy.lesson03;
import org.springframework.stereotype.Component;
@Component
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void sleep(){
System.out.println(this.name+"睡着了");
}
}
创建切面
package com.codestd.springstudy.lesson03;
import java.lang.reflect.Field;
import java.util.Date;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class PeopleInterceptor {
@Pointcut("execution(* com.codestd.springstudy.lesson03.People.sleep(..))")
public void callSleep(){}
@Before(value="callSleep()")
public void beforeSleep(JoinPoint joinPoint){
Object obj = joinPoint.getTarget();
try {
Field field = obj.getClass().getDeclaredField("name");
field.setAccessible(true);
String name = (String)field.get(obj);
System.out.println(name+"要睡觉了。"+(new Date()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里我们使用@Aspect注解来表示这是一个横切类,使用@Pointcut注解定义切入点,@Before注解表示是在方法前执行
测试AOP
package com.codestd.springstudy.lesson03;
import javax.annotation.Resource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:lesson03/applicationContext.xml"})
public class PeopleTest {
@Resource
private People people;
@Before
public void setUp(){
people.setName("Jaune");
}
@Test
public void testSleep() {
people.sleep();
}
}