一、AOP介绍
AOP(Aspect Oriented Programming)即面向切面编程。即在不改变原程序的基础上为代码段增加新的功能。应用在权限认证、日志、事务。
AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
下面用一图来说明AOP横向切面的实现逻辑。
二、AOP的实现机制
AOP通过创建代理,由代理对象来实现对真实对象的管理和调取,其中代理有2种实现方式:
- JDK 的动态代理:针对实现了接口的类产生代理。使用InvocationHandler接口。
- CGlib 的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类 的子类对象。使用MethodInterceptor接口。
下面来列举一下原生的AOP实现方式:
2.1 JDK动态代理实现
(1) 创建接口和对应实现类
public interface UserService {
public void login();
}
public class UserServiceImpl implements UserService {
public void login(){
}
}
(2) 创建JDK动态代理类,实现InvocationHandler接口
// 创建JDK动态代理类,实现InvocationHandler接口
public class agency implements InvocationHandler {
// 属性:目标接口对象(被代理的对象)
private UserService target;
// 构造方法
public agency(UserService target){
this.target = target;
// 定义需要代理类做的事情
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//本方法中的其他输出输入增强
//proxy 代理方法被调用的代理实例
System.out.println("方法触发了");
//通过代理执行被代理类的原方法, 即调取真实对象的方法
Object invoke = method.invoke(target, args);
System.out.println("执行完毕了");
return invoke;
}
}
(3) 测试Demo
@Test
public void test1(){
// 创建真实的被代理对象
UserService us = new UserServiceImpl();
//创建JDK动态代理类的对象
agency ag = new agency(us);
//生成代理对象: 参数1:真实对象的ClassLoader, 真实对象的Interfaces, 真实对象的JDK动态代理类对象, 注意:这里不能转换成一个实际的类,必须是接口类型
UserService uservice = (UserService)Proxy.newProxyInstance(us.getClass().getClassLoader(),us.getClass().getInterfaces(),ag);
//由代理对象调取方法
uservice.login();
}
(4) 测试结果
在调用接口方法的前后都会添加代理类的方法
2.2 CGlib动态代理实现
- 使用JDK创建代理有一个限制, 它只能为接口创建代理实例。这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中可以看的很清楚。
- 其中第二个参数Class[] interfaces就是需要代理实例实现的接口列表。
- 对于没有通过接口定义业务方法的类, 如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷, CGLib作为一个替代者, 填补了这一空缺。
- CGLib采用底层的字节码技术, 可以为一个类创建子类, 在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
(1) 添加依赖包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
(2) 创建普通类
public class Users{
public void login(){
}
}
(3) 创建CGlib动态代理类,实现MethodInterceptor接口
class CgProxy implements MethodInterceptor {
// 定义需要代理类做的事情
//参数:Object o为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[] objects为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
public Object intercept(Object o, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {
System.out.println("输出语句1");
///通过代理执行被代理类的原方法, 即调取真实对象的方法
Object obj = methodProxy.invokeSuper(o,objects);
System.out.println("输出语句2");
return obj;
}
}
(4) 测试
public static void main(String[] args) {
//1.创建真实对象
Users users = new Users();
//2.创建代理对象
Enhancer enhancer = new Enhancer();
//3.通过创建父类的子类来实现代理
enhancer.setSuperclass(users.getClass());
enhancer.setCallback(new CglibProxy());
Users o = (Users) enhancer.create();//代理对象
o.login();
}
(5) 测试结果
在调用接口方法的前后都会添加代理类的方法
2.3 两种代理方式的区别总结
(1) jdk动态代理生成的代理类和委托类实现了相同的接口;
(2) cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被final关键字 修饰的方法;
(3) jdk采用反射机制调用委托类的方法,cglib采用类似索引的方式直接调用委托类方法;
三、在spring中使用AOP
spring同时使用了以上两种代理方式,框架底层会自行判断应该使用哪种代理实现。
(1) 在pom.xml中添加依赖
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
(2)添加项目原有的调取过程:Bean —— Dao —— Service
(3)创建一个增强类(本质上就是一个普通类)
共有5种代理形式
- 前置通知:目标方法运行之前调用 aop:before
- 后置通知(如果出现异常不会调用):在目标方法运行之后调用 aop:after-returning
- 环绕通知:在目标方法之前和之后都调用 aop:around
- 最终通知(无论是否出现 异常都会调用):在目标方法运行之后调用 aop:after
- 异常增强:程序出现异常时执行(要求:程序代码中不要处理异常) aop:after-throwing
Demo:
public class MyAop {
//前置增强-调取目标方法之前执行
public void before(JoinPoint joinPoint){
System.out.println("日志开始");
System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
System.out.println("方法信息:"+joinPoint.getSignature());
System.out.println("参数信息:"+joinPoint.getArgs());
}
//后置增强-调取目标方法之后执行
public void after(){
System.out.println("日志结束");
}
//环绕增强,需要一个切入点作为其参数
public void myaround(ProceedingJoinPoint joinPoint){
System.out.println("环绕开始");
try {
joinPoint.proceed(); //调取目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕结束");
}
//异常增强
public void myex(){
System.out.println("异常增强");
}
//最终增强-无论是否有异常都要执行
public void aftera(){
System.out.println("最终增强");
}
}
(4) 在spring.xml中添加aop命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
(5)设置spring.xml配置文件
<!--1.创建目标类对象-->
<bean id="udao" class="com.maven.dao.impl.UsersDaoImpl"></bean>
<bean id="uservice" class="com.maven.service.impl.UsersServiceImpl">
<!-- 配置uservice对象的属性-->
<property name="usersDao" ref="udao"></property>
</bean>
<!--2.创建增强类的对象-->
<bean id="myaop" class="com.maven.util.MyAop"></bean>
<!--3.建立增强类和目标方法之间的关系-->
<aop:config>
<!--确定目标方法(切点方法): pointcut-->
<aop:pointcut id="mypc" expression="execution(* com.maven.service.*.*(..))"></aop:pointcut>
</aop:advisor>
<aop:aspect ref="myaop">
<!--前置增强-->
<aop:before method="before" pointcut-ref="mypc"></aop:before>
<!--后置增强-->
<aop:after-returning method="after" pointcut-ref="mypc"></aop:after-returning>
<!--环绕增强-->
<aop:around method="myaroud" pointcut-ref="mypc"></aop:around>
<!--异常增强-->
<aop:after-throwing method="myex" pointcut-ref="mypc"></aop:after-throwing>
<!--最终增强-->
<aop:after method="aftera" pointcut-ref="mypc"></aop:after>
</aop:aspect>
</aop:config>
这里有两个点要注意:
(1) 环绕增强需要使用ProceedingJoinPoint作为参数
(2) 注意标签顺序
四、切入点方法(pointcut:expression)的定义
(1) " * " 表示匹配所有类型的返回值。
public * addUser(com.pb.entity.User)
示例:
public int addUser(User u);
public String addUser(User u);
(2) " * " 表示匹配所有方法名。
public void *(com.pb.entity.User)
示例:
public void selectUser(User u);
public void a(User u);
(3) " … " 表示匹配所有参数个数和类型。
public void addUser (..)
示例:
public void addUser(int a)
public void addUser(int b,int c)
(4) 匹配com.pb.service 包下所有类的所有方法。
* com.pb.service.*.*(..)
示例:
public void com.pb.service.A.a();
public String com.pb.service.B.a();
(5) 匹配com.pb.service 包及子包下所有类的所有方法
* com.pb.service..*(..)
(6) 如何获取切入点信息
可以通过JoinPoint对象获取信息:
System.out.println("切入点对象:"+jp.getTarget().getClass().getSimpleName());
System.out.println("切入点方法:"+jp.getSignature());
System.out.println("切入点的参数:"+jp.getArgs()[0]);
示例:
public void before(JoinPoint joinPoint){
System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
System.out.println("切入点方法信息:"+joinPoint.getSignature());
System.out.println("切入点参数信息:"+joinPoint.getArgs());
}
(7) 特殊的前置增强–>Advisor前置增强实现步骤, 一般用于事务管理
- 创建增强类, 要求该类实现MethodBeforeAdvice接口
public class BeforeAop implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("特殊的前置增强");
System.out.println("目标方法:"+method.getName());
System.out.println("参数:"+objects);
System.out.println("对象:"+o);
}
}
- 修改spring.xml文件
- 创建增强类对象
- 定义增强和切入点的关系
<!---创建增强类的对象-->
<bean id="beforeaop" class="com.maven.util.BeforeAop"></bean>
<!--建立增强类和目标方法之间的关系-->
<aop:config>
<!-- 表达式是被切入的方法的表达式 -->
<aop:pointcut id="mypoint" expression="execution(* biz.impl.*.*(..))"
></aop:pointcut>
<!-- 建立Advisor与pointcut之间的联结 -->
<aop:advisor advice-ref="增强类对象的id" pointcut-ref="切入点对象的id"/>
</aop:config>
示例:
<!---创建增强类的对象-->
<bean id="beforeaop" class="com.maven.util.BeforeAop"></bean>
<!--建立增强类和目标方法之间的关系-->
<aop:config>
<aop:pointcut id="mypc" expression="execution(* com.maven.service.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="beforeaop" pointcut-ref="mypc"></aop:advisor>
五、使用AspectJ依赖注解开发
(1) spring AOP的注解方式:
- 增强类也需要创建对象(使用@Component)
- 要启动扫描spring注解包的代码
<!--使用context命名空间需要,扫描注解包-->
<context:component-scan base-package="com.maven"></context:component-scan>
<!--启用aop注解-->
<aop:aspectj-autoproxy/>
(2) 在切面类(增强类)上添加:@Aspect @Component
(3) 在切面类(增强类)中定义一个任意方法
注意:为什么要定义一个任意方法??? 因为@Pointcut注解必须修饰方法,所以这里需要定义一个空方法。
@Pointcut("execution(* com.*.*(..))")
public void anyMethod(){}
(4) 用法
//定义切点,由于注解必须修饰方法,所以这里需要定义一个空方法
@Pointcut("execution( * com.maven.service.*.*(..))")
public void abc(){}
//前置增强-调取目标方法之前执行
@Before("abc()")
public void before(JoinPoint joinPoint){
System.out.println("日志开始-前置增强");
// System.out.println(new Date()+"切点对象信息:"+joinPoint.getTarget().getClass().getSimpleName());
// System.out.println("方法信息:"+joinPoint.getSignature());
// System.out.println("参数信息:"+joinPoint.getArgs());
}
//后置增强-调取目标方法之后执行
@AfterReturning("abc()")
public void after(){
System.out.println("日志结束--后置增强");
}
@Around("abc()")
public void myaroud(ProceedingJoinPoint joinPoint){
System.out.println("环绕开始");
try {
joinPoint.proceed(); //调取目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕结束");
}
//异常
@AfterThrowing("abc()")
public void myex(){
System.out.println("异常增强");
}
//最终增强-无论是否有异常都要执行
@After("abc()")
public void aftera(){
System.out.println("最终增强");
}
(5) 注意注解方式中注解的顺序问题
1.没有异常情况下
环绕开始。。。。
前置增强开始执行
insert-----------
环绕结束。。。。
最终增强
后置增强开始执行
相对顺序固定,注解换位置时不影响结果顺序
2.有异常
前置增强开始执行
insert-----------
最终增强
异常增强
注意:不要使用环绕增强,使用的话,异常增强不执行
AOP的应用场景:
事务底层实现,日志,权限控制, mybatis中sql绑定,性能检测