1. AOP的相关概念
1.1 AOP概述
1.1.1 什么是AOP
AOP:全程是Aspect Oriented Programming 即面向切面编程。是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.1.2 AOP的作用及优势
作用:在程序运行期间,不需要修改源码,对已有的程序方法进行增强
优势:减少重复代码,提高开发效率,维护方便
1.1.3 AOP的实现方式
使用动态代理技术(CGLIB,Proxy)
1.2 AOP的具体应用
1.2.1 动态代理回顾
因AOP主要通过动态代理实现,在学习AOP之前,先来回顾动态代理的相关知识。
1.2.1.1 动态代理的特点
-
-
- 字节码随用随创建,随用随加载。
- 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
- 装饰者模式就是静态代理的一种体现。
1.2.1.2 动态代理的实现方式
代理方式
|
提供者
|
要求
|
基于接口的动态代理
|
JDK官方的Proxy类
|
被代理类最少实现一个接口
|
基于子类的动态代理
|
第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar
|
被代理类不能用final修饰的类(最终类)
|
/**原方法**/
package com.zycom.serviceimpl;
import com.zycom.service.ICustomerService;
public class CustomerServiceImpl implements ICustomerService{
@Override
public void save() {
System.out.println("服务层:保存...");
}
}
/**增强方法**/
package com.zycom.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.zycom.service.ICustomerService;
public class MyProxy {
public static ICustomerService getProxyCustomerService(final ICustomerService c){
ICustomerService cs = (ICustomerService)Proxy.newProxyInstance(MyProxy.class.getClassLoader(),c.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("Proxy对save方法进行增强...");
}
return method.invoke(c, args);
}
});
return cs;
}
}
/**测试方法*/
public void t1(){
CustomerServiceImpl c = new CustomerServiceImpl();
System.out.println("增强前:");
c.save();
System.out.println("---------------------------------------");
System.out.println("增强后:");
ICustomerService cs = MyProxy.getProxyCustomerService(c);
cs.save();
}
该方法需要导入第三方jar包:CGlib包已包含在Spring的核心包内,如果导入Spring不需要在单独导入CGLib的jar包
/**源代码**/
package com.zycom.domain;
public class User {
public void eat(){
System.out.println("用户在吃饭...");
}
}
/**增强代码**/
/**
* 通过CGLib的方式增强类的方法
*/
public static User getProxyUser(final User u) {
User user = (User) Enhancer.create(u.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
if("eat".equals(method.getName())){
System.out.println("我来记录user吃了什么(增强)");
}
return methodProxy.invoke(u, args);
}
});
return user;
}
/**测试代码**/
public void t2(){
User u = new User();
System.out.println("增强前:");
u.eat();
System.out.println("--------------------------------------");
System.out.println("增强后");
User user = MyProxy.getProxyUser(u);
user.eat();
}
1.3 Spring中的AOC
1.3.1 动态代理的选择
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
1.3.2AOP相关术语
Joinpoint(连接点--类中所有的方法):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点--需要被增强的方法):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强--增强的功能):
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象--被增强的对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
1.3.3 学习spring中的AOP要明确的事
a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
b、运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
2.基于XML的AOP配置
2.1 环境搭建
2.1.1 导包
AOP相关包
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aspects-4.2.4.RELEASE.jar
spring-aop-4.2.4.RELEASE.jar
日志包
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.apache.log4j-1.2.15.jar
Spring基本包
spring-beans-4.2.4.RELEASE.jar
spring-context-4.2.4.RELEASE.jar
spring-core-4.2.4.RELEASE.jar
spring-expression-4.2.4.RELEASE.jar
spring-test-4.2.4.RELEASE.jar
2.1.2 创建接口和实现类
/**接口**/
package com.zycom.service;
public interface ICustomerService {
public abstract void save();
}
/**实现类**/
package com.zycom.serviceimpl;
import com.zycom.service.ICustomerService;
public class CustomerServiceImpl implements ICustomerService{
@Override
public void save() {
System.out.println("服务层:保存...");
}
}
2.1.3 创建切面类和通知(增强方法所在的类和要增强的功能方法)
package com.zycom.proxy;
public class MyProxy {
public void log(){
System.out.println("记录日志...");
}
}
2.1.4 在配置文件增加约束
<?xml version="1.0" encoding="UTF-8"?>
</beans>
2.1.5 将实现类交由IOC管理
<bean id="customerService" class="com.zycom.serviceimpl.CustomerServiceImpl"></bean>
2.2配置步骤
2.2.1 将切面类交由IOC管理
<bean id="myProxy" class="com.zycom.proxy.MyProxy"></bean>
2.2.2 配置AOP
方式一:
<aop:config>
<aop:aspect id="proxy" ref="myProxy">
<aop:before method="log" pointcut="execution(public void com.zycom.serviceimpl.CustomerServiceImpl.save())"/>
</aop:aspect>
</aop:config>
方式二:
<aop:config>
<aop:pointcut expression="execution(* com.zycom.serviceimpl.CustomerServiceImpl.save(..))" id="save"/>
<aop:aspect id="proxy" ref="myProxy">
<aop:after-returning method="log" pointcut-ref="save"/>
</aop:aspect>
</aop:config>
2.3 切入点表达式说明
execution:
匹配方法的执行(常用)
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
访问修饰符可以省略
void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
返回值可以使用*号,表示任意返回值
* com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.CustomerServiceImpl.saveCustomer()
使用..来表示当前包,及其子包
* com..CustomerServiceImpl.saveCustomer()
类名可以使用*号,表示任意类
* com..*.saveCustomer()
方法名可以使用*号,表示任意方法
* com..*.*()
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
2.4 常用标签
标签
|
作用
|
属性
|
说明
|
<aop:config>
|
用于声明开始aop的配置
|
|
|
<aop:aspect>
|
用于配置切面。
|
id:给切面提供一个唯一标识。
ref:引用配置好的通知类bean的id。
|
|
<aop:pointcut>
|
用于配置切入点表达式
|
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识。
|
|
<aop:before>
|
用于配置前置通知
|
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
|
用于配置前置通知。前置通知的执行时间点:切入点方法执行之前执行
|
<aop:after-returning>
|
用于配置后置通知
|
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
|
用于配置后置通知。后置通知的执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行
|
<aop:after-throwing>
|
用于配置异常通知
|
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
|
用于配置异常通知。异常通知的执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。
|
<aop:after>
|
用于配置最终通知
|
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
|
用于配置最终通知。最终通知的执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。
|
<aop:around>
|
用于配置环绕通知
|
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
|
用于配置环绕通知。他和前面四个不一样,他不是用于指定通知方法何时执行的。
|
2.5 通知类型
2.5.1 普通类型配置(<aop:before>|<aop:after-returning>|<aop:after-throwing>|<aop:after>)
<aop:before method="beforePrintLog" pointcut-ref="pt1"/>
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
<aop:around method="aroundPringLog" pointcut-ref="pt1"/>
2.5.2 <aop:around>标签的配置
首先需要对切面类的增强方法有特殊要求:
public void aroundMethod(ProceedingJoinPoint pp){
try {
System.out.println("方法执行前增强功能...");
pp.proceed();
System.out.println("方法执行后增强功能...");
} catch (Throwable e) {
e.printStackTrace();
}
}
配置文件:
<aop:config>
<aop:pointcut expression="execution(* com.zycom.serviceimpl.CustomerServiceImpl.find(..))" id="find"/>
<aop:aspect id="proxy" ref="myProxy">
<aop:around method="aroundMethod" pointcut-ref="find"/>
</aop:aspect>
</aop:config>
3.基于注解的AOP配置
3.1环境搭建
3.1.1 编写业务层接口和业务层实现类(并通过注解将实现类交由Sping的ioc来管理)
业务层接口
package com.zycom.service;
public interface IUserService {
public abstract void login();
public abstract void quit();
}
业务层实现类
package com.zycom.serviceimpl;
import org.springframework.stereotype.Service;
import com.zycom.service.IUserService;
@Service("userService")
public class UserServiceImpl implements IUserService {
@Override
public void login() {
System.out.println("服务层:登录");
}
@Override
public void quit() {
System.out.println("服务层:退出");
}
}
3.1.2 导包
与上方xml配置的包相同
3.1.3 xml配置文件导入约束
与上方xml配置的约束相同
3.1.4 配置xml开启扫描
<!-- 1.开启包扫描 -->
<context:component-scan base-package="com.zycom"></context:component-scan>
3.2 配置步骤
3.2.1 将通知类通过注解交给spring管理,在方法上通过注解表明通知方式和切入点
package com.zycom.proxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
//将通知类交由IOC管理
@Component("myProxy")
// 声明切面类
@Aspect
public class MyProxy {
@AfterReturning("execution(* com.zycom.serviceimpl.UserServiceImpl.login())")
public void enhanceLogin() {
System.out.println("xxxx先生,欢迎您的登录...");
}
@Around("execution(* com.zycom.serviceimpl.UserServiceImpl.quit() )")
public void enhanceQuit(ProceedingJoinPoint pp) {
try {
System.out.println("您确定要退出吗?");
pp.proceed();
System.out.println("已退出,欢迎再来...");
} catch (Throwable e) {
System.out.println("退出失败,请重试");
e.printStackTrace();
}
}
}
3.2.2 在spring配置文件中开启spring对注解AOP的支持
<!-- 2.开启注解模式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.3 常用通知注解
注解
|
作用
|
属性
|
与xml配置文件对应标签
|
@Aspect
|
把当前类声明为切面类
|
|
<aop:aspect>
|
@Before
|
把当前方法看成是前置通知
|
value:用于指定切入点表达式,还可以指定切入点表达式的引用
|
<aop:before>
|
@AfterReturning
|
把当前方法看成是后置通知
|
value:用于指定切入点表达式,还可以指定切入点表达式的引用
|
<aop:after-returning>
|
@AfterThrowing
|
把当前方法看成是异常通知
|
value:用于指定切入点表达式,还可以指定切入点表达式的引用
|
<aop:after-throwing>
|
@After
|
把当前方法看成是最终通知
|
value:用于指定切入点表达式,还可以指定切入点表达式的引用
|
<aop:after>
|
@Around
|
把当前方法看成是环绕通知
|
value:用于指定切入点表达式,还可以指定切入点表达式的引用
|
<aop:around>
|
@Pointcut
|
指定切入点表达式
|
value:指定表达式的内容
|
<aop:pointcut>
|
3.4 不使用XML的配置方式
package com.zycom.springconfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages={"com.zycom"})
@EnableAspectJAutoProxy
public class SpringCfg {
}
测试类加载:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringCfg.class})
public class Demo {
@Resource(name="userService")
private IUserService us;
@Test
public void t1(){
us.login();
}
@Test
public void t2(){
us.quit();
}
}