AOP简介
AOP:Aspect-Oriented Programming,面向切面编程。作为OOP的一种补充。
传统的OOP采用的时纵向继承关系,难免会后很多重复的代码。而AOP的思想可以完美解决这个问题,AOP采用横向抽取机制,把重复的代码抽取出来,单独建立一个类,这个类就是切面类,当某个类需要时(目标类),会创建目标类的代理类(Proxy),然后把切面类中的某个方法(Advice),切入到目标类中。
Java中有两种情况的AOP
- JDK动态代理
JDK动态代理是基于目标类接口创建的,也就是说目标类必须实现某个接口。
- CGLIB代理
CGLIB代理可以直接对某个类进行代理,不需要目标类实现某个接口。
JDK动态代理
JDK动态代理是通过java.lang.reflect包中的Proxy类实现的,通过调用它的方法newProxyInstance()来创建代理对象。此外还需要让代理类实现InvocationHandler接口,该接口的invoke()方法正是执行目标类增强方法的入口。
创建目标类
- 创建目标类的接口
public interface UserDao{
public void addUser();
public void deleteUser();
}
- 创建目标类接口的实现类
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
// TODO Auto-generated method stub
System.out.println("添加用户!");
}
@Override
public void deleteUser() {
// TODO Auto-generated method stub
System.out.println("删除用户!");
}
}
创建切面类
package com.syl.aspect;
/**
* 切面类aspect
* @author x1c
*
*/
public class MyAspect {
/*
* 两个Advice
*/
public void check_Permissions() {
System.out.println("模拟权限检查....");
}
public void log() {
System.out.println("模拟记录日志...");
}
}
创建代理类
- createProxy()方法
此方法是自主创建的,通过反射获取实现目标类接口的所有类,然后利用Proxy类的newProxyInstance(classLoader,clazz,this)三个参数分别为本类的加载器、实现目标类接口的实现类,本类。
- InvocationHandler接口
该接口有一个invoke()方法,此方法是完成Advice的关键。
package com.syl.jdk;
/**
* JDK代理类proxy
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.syl.aspect.MyAspect;
public class JdkProxy implements InvocationHandler {
//声名目标类接口
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao) {
this.userDao = userDao;
//类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
// 目标类实现的所有接口即UserDao接口的实现类
Class[] clazz = userDao.getClass().getInterfaces();
//返回代理后的对象
return Proxy.newProxyInstance(classLoader, clazz, this);
}
/**
* createProxy类,只完成了对类的代理
* 具体对目标类的增强还是需要invoke()方法来完成的
*/
@Override
/**
* proxy : 被代理后的对象
* method: 将要被执行的方法信息
* args: 执行方法时需要的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声名切面类
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check_Permissions();
//调用目标类方法,并传入参数
//需要目标类对象和执行方法时需要的参数
Object obj = method.invoke(userDao, args);
//后增强
myAspect.log();
return obj;
}
}
测试
package com.syl.jdk;
public class JdkTest {
public static void main(String args[]) {
//创建目标类对象
UserDao userDao = new UserDaoImpl();
//创建代理类
JdkProxy jdk = new JdkProxy();
//传入目标类对象参数,获取代理类
UserDao userDaoProxy = (UserDao)jdk.createProxy(userDao);
//执行增强后的方法
userDaoProxy.addUser();
userDaoProxy.deleteUser();
//执行原本的方法
userDao.addUser();
userDao.deleteUser();
}
}
CGLIB代理
CGLIB代理是一个高性能开源的代码生成包,对目标类生成一个子类,然后对子类进行加强。
目标类
package com.syl.target;
public class UserDao {
public void addUser() {
System.out.println("添加用户!");
}
public void deleteUser() {
System.out.println("删除用户!");
}
}
切面类
同JDK动态代理
代理类
- createProxy()方法
此方法创建动态类对象Enhancer,并将目标类设置其父类。
- MethodInterceptor接口
此接口的intercept()方法,完成对目标类方法的增强。
package com.syl.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.syl.aspect.MyAspect;
/**
* CGLIB代理是创建目标类的子类,对子类进行增强
* @author x1c
*
*/
public class CglibProxy implements MethodInterceptor{
//创建代理类方法
public Object createProxy(Object target) {
//创建动态类对象
Enhancer enhancer = new Enhancer();
//设置其父类
enhancer.setSuperclass(target.getClass());
//回调函数
enhancer.setCallback(this);
//创建代理类
return enhancer.create();
}
@Override
/**
* proxy : CGLIB根据父类生成的代理对象
* method: 拦截的方法,即切入点
* args : 切入点方法的参数
* methodProxy: 方法的代理对象,用于执行父类的方法
*/
public Object intercept(Object proxy, Method method,
Object[] args, MethodProxy methodProxy) throws Throwable {
//创建切面类
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check_Permissions();
Object obj = methodProxy.invokeSuper(proxy, args);
//后增强
myAspect.log();
return obj;
}
}
测试
package com.syl.cglib;
import com.syl.target.UserDao;
public class Test {
public static void main(String args[]) {
//目标对象
UserDao user = new UserDao();
//创建代理类
CglibProxy cg = new CglibProxy();
//获取代理对象
UserDao user2 = (UserDao)cg.createProxy(user);
//执行增强后的方法
user2.addUser();
user2.deleteUser();
}
}
Spring中的AOP
Spring中的AOP代理默认使用JDK动态代理方式实现。基于ProxyFactoryBean类开发AOP,是创建AOP的最基本方式
另外需要导入Spring如下几个JAR包
Spring AOP通知类型
- org.aopalliance.intercept.MethodInterceptor(环绕通知)
- org.springframework.aop.MethodBeforeAdvice(前置通知)
- org.springframework.aop.AfterReturningAdvice(后置通知)
- org.springframework.aop.ThrowsAdvice(异常通知)
- org.springframework.aop.IntroductionInterceptor(引介通知)
ProxyFactoryBean类
ProxyFactoryBean是FactoryBean接口的实现类,前者负责实例化一个Bean,后者负责为其他Bean创建代理实例。
常用属性如下:
属性 | 描述 |
---|---|
target | 代理的目标对象 |
proxyInterfaces | 代理需要实现的接口 |
proxyTargetClass | true,表示CGLIB代理。默认false |
interceptorNames | 切面类 |
singleton | 返回代理是否为单实例,默认true |
optimize | true,强制使用CGLIB代理 |
目标类
package com.syl.target;
public class UserDao {
public void addUser() {
System.out.println("添加用户!");
}
public void deleteUser() {
System.out.println("删除用户!");
}
}
切面类
Spirng AOP中的通知类型,直接在切面类继承通知类型的接口
package com.syl.springaop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
//invoke()方法用来执行方法
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
check_Permissions();
//环绕通知,用来执行目标类的方法
Object obj = mi.proceed();
log();
return obj;
}
//Advice
public void check_Permissions() {
System.out.println("模拟检查权限...");
}
public void log() {
System.out.println("模拟记录日志...");
}
}
基于XML使用ProxyFactoryBean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目标类 -->
<bean id="userDao"
class="com.syl.target.UserDao"/>
<!-- 切面类 -->
<bean id="myAspect"
class="com.syl.springaop.MyAspect"/>
<!-- 使用Spring代理工厂创建代理对象 -->
<bean id="userDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置目标类 -->
<property name="target" ref="userDao" />
<!-- 切面类 -->
<property name="interceptorNames" value="myAspect"/>
<!-- 是否使用CGLIB代理 -->
<property name="proxyTargetClass" value="true"/>
<!-- JDK动态代理 -->
<!-- <property name="proxyInterfaces" value="目标类接口路径"/> -->
<!-- 单实例 -->
<!-- <property name="singleton" value="true"/> -->
</bean>
</beans>
测试
package com.syl.springaop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.syl.target.UserDao;
public class ProxyFactoryBeanTest {
public static void main(String args[]) {
//Bean容器
ApplicationContext a =
new ClassPathXmlApplicationContext("applicationContext.xml");
//获取Bean
UserDao userdao = (UserDao)a.getBean("userDaoProxy");
//执行代理后的对象的方法
userdao.addUser();
userdao.deleteUser();
}
}
AspectJ
使用AspectJ框架可以更加简答的配置AOP,组件如下
......
......
<aop:config>
<!--配置切面-->
<aop:aspect >
<!--配置切入点-->
<aop:poincut>
<!--配置通知-->
<!--前置通知-->
<aop:before />
<!--后置通知-->
<aop:after-returning />
<!--环绕通知-->
<aop:around />
<!--异常通知-->
<aop:after-throwing />
<!--最终通知-->
<aop:after />
</aop:poincut>
</aop:aspect>
</aop:config>
配置切面
< aop:aspect >元素用来配置切面,他有如下两个属性
- id:该切面唯一标识
- ref:引用Bean作为切面
配置切入点(目标类)
< aop:poincut >元素用来配置切入点,他有如下两个属性
- id:该切入点唯一标识
- expression:指定切入点表达式
expression="execution(* com.syl.jdk. * . * (…))
此切入点表达式:任意方法的返回类型、com.syl.jdk下的任意类、任意方法、任意方法参数
配置通知
配置通知的元素有如下几个元素
- < aop:before>:前置通知
- < aop:after-returning>:后置通知
- < aop:around>:环绕通知
- < aop:after-throwing>:异常通知
- < aop:after>:最终通知
通知又有如下几个属性
属性 | 描述 |
---|---|
pointcut | 指定切入点完整路径 |
pointcut-ref | 指定切入点的Bean |
method | 指定使用切面的哪个方法作为增强通知 |
throwing | 异常通知有效。用于指定一个形参名,可通过该形参访问抛出的异常 |
returning | 最终通知有效。用于指定一个形参名,可通过该形参访问返回值 |
基于XML的AspectJ
导入如下JAR包
- 定义切面
package com.syl.aspectJ;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面
* @author x1c
*
*/
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知:模拟权限检查");
System.out.println("目标类是:"+joinPoint.getTarget());
System.out.println("切入点:"+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturing(JoinPoint joinPoint) {
System.out.println("后置通知:模拟记录日志");
System.out.println("切入点:"+joinPoint.getSignature().getName());
}
//环绕通知
/*
* ProceedingJoinPoint 是JoinPoint的子接口
* 1. 必须Object返回值类型
* 2.必须抛出异常
* 3.ProceedingJoinPoint用来执行切入点方法
*/
public Object around(ProceedingJoinPoint joinPoint)throws Throwable {
System.out.println("环绕开始");
System.out.println("执行目标方法前:模拟开启事务");
//执行目标方法
Object obj = joinPoint.proceed();
System.out.println("环绕结束");
System.out.println("执行目标方法后:模拟关闭事务");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
System.out.println("异常通知: "+"出错了 "+e.getMessage());
}
//最终通知
public void myAfter() {
System.out.println("最终通知:模拟释放资源 ");
}
}
- xml
<!-- AspectJ的切面类 -->
<bean id="myAspectJ"
class="com.syl.aspectJ.MyAspect"/>
<!-- AspectJ -->
<aop:config>
<!-- 切面 -->
<aop:aspect ref="myAspectJ">
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.syl.target.*.*(..))" id="myPointCut"/>
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 后置通知 -->
<aop:after-returning method="myAfterReturing"
pointcut-ref="myPointCut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
基于注解式的AspectJ
基于注解的AspectJ,切入点表达式直接写在切面类里。有如下几种注解:
- @Aspect:定义切面
- @Pointcut:定义切入点,需要在切面类中定义一个void返回类型的方法来表示切入点。
- @Before:前置通知
- @AfterReturning:后置通知
- @Around:环绕通知
- @AfterThrowing:异常通知
- @After:最终通知
切面类
在此定义切面、切入点表达式、通知类型
package com.syl.aspectJ;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面类
* @author x1c
*
*/
@Aspect
@Component//默认将类首字母小写作为Bean实例名
public class MyAspectOnAnnoation {
//定义切入点表达式
@Pointcut("execution(* com.syl.target.*.*(..))")
//空方法表示切入点
private void myPointCut() {
}
//前置通知,指定对哪一个切入点有效
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知:模拟权限检查...");
System.out.println("目标类是:"+joinPoint.getTarget());
System.out.println("增强处理的方法:"+joinPoint.getSignature().getName());
}
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("后置通知:模拟记录日志");
}
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
Object obj = joinPoint.proceed();
System.out.println("环绕结束:模拟关闭事务...");
return obj;
}
//含有参数的情况下
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
System.out.println("异常通知:出错了"+e.getMessage());
}
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知");
}
}
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: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-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- AspectJ -->
<!-- 指定需要扫描的包,普通注解生效 -->
<context:component-scan base-package="com.syl" />
<!-- 基于AspectJ的注解生效 -->
<aop:aspectj-autoproxy />
</beans>
测试
package com.syl.aspectJ;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.syl.target.UserDao;
public class MyAspectJOnAnnotation {
public static void main(String args[]) {
ApplicationContext a =
new ClassPathXmlApplicationContext("applicationContext.xml");
//目标类需要使用@Repository注解,首字母小写的Bean
UserDao user = (UserDao)a.getBean("userDao");
user.addUser();
}
}
@Syl 2021/09/01 周三 22:08 回宿舍,跑会步