AOP适用范围
AOP是面向切面编程,对于不例如日志,权限等穿插整个项目但是不是核心逻辑功能的,我们可以将其提出作为单独的模块管理
图可以看出,每个关注点与核心业务模块分离,作为单独的功能,横切几个核心业务模块,这样的做的好处是显而易见的,每份功能代码不再单独入侵到核心业务类的代码中,即核心模块只需关注自己相关的业务,当需要外围业务(日志,权限,性能监测、事务控制)时,这些外围业务会通过一种特殊的技术自动应用到核心模块中,这些关注点有个特殊的名称,叫做“横切关注点”,上图也很好的表现出这个概念,另外这种抽象级别的技术也叫AOP(面向切面编程),正如上图所展示的横切核心模块的整面,因此AOP的概念就出现了,而所谓的特殊技术也就面向切面编程的实现技术,AOP的实现技术有多种,其中与Java无缝对接的是一种称为AspectJ的技术AspectJ-AOP
AOP在Java中的Spring中已经有了,可以直接在idea中新建一个Aspect,注意关键字为aspect(MyAspectJDemo.aj,其中aj为AspectJ的后缀),含义与class相同,即定义一个AspectJ的类。AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联.
//定义main文件
public class HelloWord{
public void sayHello{
System.out.println("hello");
}
public static void main(String args[]){
HelloWord hello=new HelloWord();
hello.sayHello();
}
}
//定义切面
public aspect MyAspectJDemo {
/**
* 定义切点,日志记录切点
*/
pointcut recordLog():call(* HelloWord.sayHello(..));
/**
* 定义切点,权限验证(实际开发中日志和权限一般会放在不同的切面中,这里仅为方便演示)
*/
pointcut authCheck():call(* HelloWord.sayHello(..));
/**
* 定义前置通知!
*/
before():authCheck(){
System.out.println("sayHello方法执行前验证权限");
}
/**
* 定义后置通知
*/
after():recordLog(){
System.out.println("sayHello方法执行后记录日志");
}
}
结果图
我们使用aspect关键字定义了一个类,这个类就是一个切面,它可以是单独的日志切面(功能),也可以是权限切面或者其他,在切面内部使用了pointcut定义了两个切点,一个用于权限验证,一个用于日志记录,而所谓的切点就是那些需要应用切面的方法,如需要在sayHello方法执行前后进行权限验证和日志记录,那么就需要捕捉该方法,而pointcut就是定义这些需要捕捉的方法(常常是不止一个方法的),这些方法也称为目标方法,最后还定义了两个通知,通知就是那些需要在目标方法前后执行的函数,如before()即前置通知在目标方法之前执行,即在sayHello()方法执行前进行权限验证,另一个是after()即后置通知,在sayHello()之后执行,如进行日志记录。到这里也就可以确定,切面就是切点和通知的组合体,组成一个单独的结构供后续使用。这里一般日志和权限都是要单独定义切面的,这里示例不规范。 对于结构表达式pointcut authCheck():call(* HelloWord.sayHello(..))
关键字为pointcut,定义切点,后面跟着函数名称,最后编写匹配表达式,此时函数一般使用call()或者execution()进行匹配,这里我们统一使用call()
pointcut 函数名 : 匹配表达式
recordLog()是函数名称,自定义的,*表示任意返回值,接着就是需要拦截的目标函数,sayHello(..)的..,表示任意参数类型。这里理解即可,后面Spring AOP会有关于切点表达式的分析,整行代码的意思是使用pointcut定义一个名为recordLog的切点函数,其需要拦截的(切入)的目标方法是HelloWord类下的sayHello方法,参数不限
before():authCheck(){ System.out.println("something"); }
before这个处于函数名之前的方法成为通知方法,共有5种通知方法
- before 目标方法执行前执行,前置通知
- after 目标方法执行后执行,后置通知
- after returning 目标方法返回时执行 ,后置返回通知
- after throwing 目标方法抛出异常时执行 异常通知
- around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知
切入点切面织入点等概念
切入点(pointcut)和通知(advice)的概念已比较清晰,而切面则是定义切入点和通知的组合如上述使用aspect关键字定义的MyAspectJDemo,把切面应用到目标函数的过程称为织入(weaving)。在前面定义的HelloWord类中除了sayHello函数外,还有main函数,以后可能还会定义其他函数,而这些函数都可以称为目标函数,也就是说这些函数执行前后也都可以切入通知的代码,这些目标函数统称为连接点,切入点(pointcut)的定义正是从这些连接点中过滤出来的织入简单原理及过程
织入的简单理解就是怎么把AspectJ应用到目标函数中,对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如JavaJDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),SpringAOP采用的就是基于运行时增强的代理技术,这点后面会分析,这里主要重点分析一下静态织入,ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
关于ajc编译器,是一种能够识别aspect语法的编译器,它是采用java语言编写的,由于javac并不能识别aspect语法,便有了ajc编译器,注意ajc编译器也可编译java文件。Spring 中的AOP
Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势。
同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。
请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。下面我们先通过一个简单的案例来演示Spring的AOP使用
//实现类
public interface UserDao {
int addUser();
void updateUser();
void deleteUser();
void findUser();
}
//实现类
import com.zejian.spring.springAop.dao.UserDao;
import org.springframework.stereotype.Repository;
/**
* Created by zejian on 2017/2/19.
* Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
*/
@Repository
public class UserDaoImp implements UserDao {
@Override
public int addUser() {
System.out.println("add user ......");
return 6666;
}
@Override
public void updateUser() {
System.out.println("update user ......");
}
@Override
public void deleteUser() {
System.out.println("delete user ......");
}
@Override
public void findUser() {
System.out.println("find user ......");
}
}
//Aspect类
@Aspect
public class MyAspect {
/**
* 前置通知
*/
@Before("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void before(){
System.out.println("前置通知....");
}
/**
* 后置通知
* returnVal,切点方法执行后的返回值
*/
@AfterReturning(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",returning = "returnVal")
public void AfterReturning(Object returnVal){
System.out.println("后置通知...."+returnVal);
}
/**
* 环绕通知
* @param joinPoint 可用于执行切点的类
* @return
* @throws Throwable
*/
@Around("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知前....");
Object obj= (Object) joinPoint.proceed();
System.out.println("环绕通知后....");
return obj;
}
/**
* 抛出通知
* @param e
*/
@AfterThrowing(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")
public void afterThrowable(Throwable e){
System.out.println("出现异常:msg="+e.getMessage());
}
/**
* 无论什么情况下都会执行的方法
*/
@After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void after(){
System.out.println("最终通知....");
}
}
//配置文件,交给Spring IOC容器管理
<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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 启动@aspectj的自动代理支持,这里开着可以使用Aspect的注解功能-->
<aop:aspectj-autoproxy />
<!-- 定义目标对象 -->
<bean id="userDaos" class="com.zejian.spring.springAop.dao.daoimp.UserDaoImp" />
<!-- 定义aspect类 -->
<bean name="myAspectJ" class="com.zejian.spring.springAop.AspectJ.MyAspect"/>
</beans>
//测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= "classpath:spring/spring-aspectj.xml")
public class UserDaoAspectJ {
@Autowired
UserDao userDao;
@Test
public void aspectJTest(){
userDao.addUser();
}
}
基于注解的Spring AOP 开发
- 定义切入点开发 切入点的定义形式如上,采用
@After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
public void after(){
System.out.println("最终通知....");
}
这里也可以采用 和AspectJ 类似的Pointcut切入点
/**
* 使用Pointcut定义切点
*/
@Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut(){}
/**
* 应用切入点函数
*/
@After(value="myPointcut()")
public void afterDemo(){
System.out.println("最终通知....");
}
- 切入点指示符
为了方法通知应用到相应过滤的目标方法上,SpringAOP提供了匹配表达式,这些表达式也叫切入点指示符
-
通配符(包含 * , .. ,+ )
- ..代表匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
//任意返回值,任意名称,任意参数的公共方法 execution(public * *(..)) //匹配com.zejian.dao包及其子包中所有类中的所有方法 within(com.zejian.dao..*)
- +代表匹配给定类的任意子类
//匹配实现了DaoUser接口的所有子类的方法 within(com.zejian.dao.DaoUser+)
- *代表匹配任意数量的字符
//匹配com.zejian.service包及其子包中所有类的所有方法 within(com.zejian.service..*) //匹配以set开头,参数为int类型,任意返回值的方法 execution(* set*(int))
- ..代表匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
-
类型签名表达式 为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下
within ()
也就是后面跟上包名或者类名
//匹配com.zejian.dao包及其子包中所有类中的所有方法 @Pointcut("within(com.zejian.dao..*)") //匹配UserDaoImpl类中所有方法 @Pointcut("within(com.zejian.dao.UserDaoImpl)") //匹配UserDaoImpl类及其子类中所有方法 @Pointcut("within(com.zejian.dao.UserDaoImpl+)") //匹配所有实现UserDao接口的类的所有方法 @Pointcut("within(com.zejian.dao.UserDao+)")
-
方法签名表达式
//scope :方法作用域,如public,private,protect //returnt-type:方法返回值类型 //fully-qualified-class-name:方法所在类的完全限定名称 //parameters 方法参数 execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
-
其他指示符 @bean,@this,@target,@within,@annotation
-
注意
在配置文件中启动@Aspect支持后,Spring容器只会尝试自动识别带@Aspect的Bean,前提是任何定义的切面类都必须已在配置文件以Bean的形式声明。
<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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--<context:component-scan base-package=""-->
<!-- 启动@aspectj的自动代理支持-->
<aop:aspectj-autoproxy />
<!-- 定义目标对象 -->
<bean id="userDaos" class="com.zejian.spring.springAop.dao.daoimp.UserDaoImp" />
<!-- 定义aspect类 -->
<bean name="myAspectJ" class="com.zejian.spring.springAop.AspectJ.MyAspect"/>
<bean name="aspectOne" class="com.zejian.spring.springAop.AspectJ.AspectOne" />
<bean name="aspectTwo" class="com.zejian.spring.springAop.AspectJ.AspectTwo" />
</beans>