面向切面编程(AOP) 是 面向对象编程的补充(OOP)
传统的业务处理代码中,通常会惊醒事务处理、日志处理等操作。虽然可以使用OOP的组合或继承来实现代码重用,但如果要实现某个功能,同样的代码还是会分散到各个方法中。
如果想要关闭某个功能,或者修改,就必须修改所有相关方法,增加了工作量和出错率。
AOP采用横向抽取机制,将重复代码抽取出来,在程序编译或运行时将代码应用到需要执行的地方。
AOP可以使开发人员编写业务逻辑时专心于核心业务,提高了开发效率,增强了代码的可维护性。
常用框架: Spring AOP 和 AspectJ
转:
静态AOP
编译阶段对程序源代码进行修改,生成静态AOP代理类(生成的*.class已经被改掉,需要特定的编译器) 如 AspectJ
动态AOP
运行阶段动态生成Proxy对象
Aspect(切面):实际应用中,切面通常指封装的用于横向插入系统功能的类,当然也要先通过
Joinpoint(连接点):指方法的调用
Pointcut(切入点):类或方法名 满足某一条件的方法
Advice(通知/增强处理):切入点要执行的程序代码,切面的具体实现
Target Object(目标对象):被增强对象
Weaving(织入):将切面代码插入到目标对象上,生成代理对象的过程
JDK动态代理通过 java.lang.reflect.Proxy实现,我们可以使用Proxy.newProxyInstance()来创建代理对象
//切片类
public class MyAspect {
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志…");
}
}
//代理类
public class JdkProxy implements InvocationHandler {
//声明目标类接口
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao){
this.userDao = userDao;
// 1.类加载器
ClassLoader classLoader = UserDao.class.getClassLoader();
// 2.被代理对象实现的所有接口
Class[] clazz = userDao.getClass().getInterfaces();
// 3.使用代理类,进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader, clazz,this);
}
/*
所有动态代理类的方法调用都会交给invoke() 方法来处理
proxy 被代理后的对象
method 将要被执行的方法信息(反射)
args 执行方法时需要的参数
*/
@Override
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;
}
}
//调用类
public class JdkTest {
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
UserDao userDao = new UserDaoImp1();
UserDao userDao1 = (UserDao)jdkProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}
JDK动态代理要求使用动态代理的对象必须实现一个或多个接口
CGLIB(Code Generation Library)使用底层字节码技术,对指定的目标类生成一个子类,并对子类进行增强
//目标类
public class UserDao {
public void addUser() {
System.out.println("增加用户…");
}
public void deleteUser() {
System.out.println("删除用户...");
}
}
//代理类
public class CglibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object target){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/*
proxy CGlib生成的代理对象
method 拦截的方法
args 拦截方法的参数数组
methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
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;
}
}
//测试类
public class CglibTest {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserDao userDao = new UserDao();
UserDao userDao1 = (UserDao)cglibProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}
运行结果
模拟检查权限
增加用户…
模拟记录日志…
模拟检查权限
删除用户…
模拟记录日志…
演示在Spring中,使用在配置文件中创建ProxyFactoryBean,并使用它创建一个AOP环绕通知案例
首先需要两个JAR包
pom.xml
切面类 (环绕通知需要实现MethodInterceptro接口)
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
this.check_Permissions();
Object obj = methodInvocation.proceed();
this.log();
return obj;
}
public void check_Permissions(){
System.out.println("模拟检查权限…");
}
public void log(){
System.out.println("模拟记录日志…");
}
}
applicationContext.xml 配置文件
<!-- 1 目标类-->
<bean id="userDao" class="com.itheima.jdk.UserDaoImp1"/>
<!-- 2 切面类-->
<bean id="myAspect" class="com.itheima.factorybean.MyAspect"/>
<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口-->
<property name="proxyInterfaces" value="com.itheima.jdk.UserDao"/>
<!-- 3.2 指定目标对象-->
<property name="target" ref="userDao"/>
<!-- 3.3 指定切面,植入环绕通知 -->
<property name="interceptorNames" value="myAspect"/>
<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
//测试类
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
userDao.addUser();
userDao.deleteUser();
}
}
常用元素配置代码
<!-- 3.配置通知-->
<!-- 前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 后置通知-->
<aop:after-returning method="myAfterReturning" 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>
1.Spring的通知类型
2.配置切面
3.配置切入点
通过
当作为
execution(* com.itheima.jdk.*.*(..)) 第一个*表示返回类型,表示所有类型;com.itheima.jdk表示需要拦截的包名,第二个*表示类名;第三个*表示方法名;(..)表示参数 ,..表示任意方法
给出表达式基本格式
excution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
4.配置通知
5.环境配置
spring-aspects-4.3.6RELEASE.jar
aspectjweaver-1.8.10 AspectJ框架提供的规范 http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.10
注意:aspectjweaver最好使用新版本下面也给出目前的最新版maven,不然使用注解时可能会报 error at ::0 can't find referenced pointcut XXX
pom.xml
6.实施代码
/*
切面类此页编写通知
*/
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查…,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.print("后置通知:模拟记录日志…,");
System.out.println("被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
/*环绕通知
ProceedingJoinPoint 是JoinPoint的子接口,表示可以执行目标方法
1.必须是Object类型的返回值
2.必须接收一个参数,类型为 ProceedingJoinPoint
3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务…");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务…");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源…");
}
}
//测试类
public class TestXmlAspectj {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}
运行结果
前置通知:模拟执行权限检查…,目标类是:com.itheima.jdk.UserDaoImp1@791f145a,被植入增强处理的目标方法为:addUser
环绕开始:执行目标方法之前,模拟开启事务…
增加用户…
最终通知:模拟方法结束后释放资源…
环绕结束:执行目标方法之后,模拟关闭事务…
后置通知:模拟记录日志…,被植入增强处理的目标方法为:addUser
模拟出错
public void addUser() {
int i=1/0;
System.out.println("增加用户…");
}
//输出
前置通知:模拟执行权限检查…,目标类是:com.itheima.jdk.UserDaoImp1@791f145a,被植入增强处理的目标方法为:addUser
环绕开始:执行目标方法之前,模拟开启事务…
最终通知:模拟方法结束后释放资源…
异常通知:出错了/ by zero
总结:后置通知只有在目标方法执行成功后才会被织入,而最终通知无论如何都会被织入。
演示代码
/*
切面类,在此类中编写通知
*/
@Aspect
@Component
public class MyAspect {
//定义切入点表达式
@Pointcut("execution(* com.itheima.jdk.*.*(..))")
//使用一个返回值为void、方法体为空的方法来命名切入点
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查…,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.print("后置通知:模拟记录日志…,");
System.out.println("被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
//环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务…");
Object obj = proceedingJoinPoint.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("最终通知:模拟方法结束后释放资源…");
}
}
配置文件
手机扫一扫
移动阅读更方便
你可能感兴趣的文章