AOP
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。是一种通过动态代理实现程序功能扩展和统一维护的一种技术。
动态代理 原理
代理设计模式的原理:使用一个代理将原本对象包装起来,然后用该代理对象”取代”原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。类似于日志,单独独立于代码块外
举例计算机
单独一个代码 运行代码块和日志
public interface Calculator {
int add(int a, int b);
//实现接口类
public class CalculatorImpl2 implements Calculator{
@Override
public int add(int a, int b) {
//开始日志
System.out.println("[Logging]The method add begins with ["+a+","+b+"]");
int result = a + b;
//返回日志
System.out.println("[Logging]The method add returns "+result);
return result;
}
分离日志和代码块
- 使用proxy类
public class LoggingProxy {
private Object target;
public LoggingProxy(Object target) {
this.target = target;
}
public Object getProxy(){
//类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//获取被代理对象实现的接口们
Class<?>[] interfaces = target.getClass().getInterfaces();
//获取代理方法的对象
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
/*
代理对象要实现的功能的代码要写在invoke方法中
invoke方法参数说明:
proxy:传入的代理对象,在inovke方法中不使用
method:要调用的方法
args:调用方法时传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名
String name = method.getName();
Object result = null;
try {
//反射中的invoke方法,因为接口中方法有返回值,返回给result
result = method.invoke(target, args);
//返回日志
System.out.println("[★Logging]The method " + name + "returns" + result);
} catch (Exception e) {
//异常日志
System.out.println("[★Logging]The method " + name + "occur" + e);
}finally {
//结束日志
System.out.println("[★Logging]The method "+name+" ends");
}
return result;
}
});
return proxy;
}
}
测试类
@Test
public void test() {
Calculator calculator=new CalculatorImpl();
Calculator proxy= (Calculator) new LoggingProxy(calculator).getProxy();
System.out.println(proxy.getClass().getName());
int add = proxy.add(10,2);
}
编译
com.sun.proxy.$Proxy4
[★Logging]The method addreturns12
[★Logging]The method add ends
12
使用spring 来操作 AOP
AOP术语
-
横切关注点:从每个方法中抽取出来的同一类非核心业务,比如日志
-
切面:封装横切关注点的类,每个横切面 都体现为一个通知方法。
-
通知:切面必须要完成各个具体工作 j结束通知,异常通知,
-
目标:被通知的对象
-
代理:向目标对象应用通知之后创建的代理对象
-
连接点(Joinpoint)
横切关注点在程序代码中的具体位置 字面意思,方法执行顺序的位置,切入点需要找到连接点 插入在方法前 后 异常,结束等位置。 -
切入点(pointcut)
定位连接点的方式。每个类的方法中都包含多个连接点,如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。理解为 查找的条件
Java社区里最完整最流行的AOP框架
AspectJ 通知
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行,不管方法是否发生异常
@AfterReturning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围绕着方法执行,相当于动态代理的全过程
使用基于AspectJ注解或xml配置的aop 不属于spring的。
使用注解的方式,导入俩个jar包
配置aspect.xml
扫描注解 和开启 aspectj 注解支持
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.atguigu.spring.aop"/>
<aop:aspectj-autoproxy/>
</beans>
注解写法
@Before(value=“execution(public int com.spring.aspectj.类名.方法(方法的类型))”
如果在一个包下,public修饰符可以用代替,全类名路径也可以用代替,方法可以特指,也可以用* 表示全部方法都使用,方法的形参类型… 表示全部,也可以特质某个类型
接口类不变
实现类
@Component("cac")
public class CalculatorImpl implements Calculator{
@Override
public int add(int a, int b) {
int result = a + b;
return result;
}
切面类
是上文的proxy代理类中的方法执行前后异常结束的方法总类
/*此处虽然没有被getbean 但是交给asject 内部处理 类似于proxy 类处理方法好理解
将方法返回给了 comonent,需要使用到ioc管理器,所以需要声明注解
因为aspectj 也需要交给spring ioc容器处理,所以要声明 让ioc可以处理。
*/
@Component //切面交给spring ioc容器管理 ,
@Aspect //声明当前类是一个切面
public class LoggingAspectJ{
//通过连接点找到方法使用前在方法执行之前执行
@Before(value="execution(public int com.spring.aop.Calculator.add(int,int))")
public void before(){
System.out.println("在方法执行之前执行!");
}
}
或者用around 代替全部
@Around("execution(* Calculator.*(..))")//加工
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
//获取方法名
String methodName = proceedingJoinPoint.getSignature().getName();
//获取调用方法时传入的参数
Object[] args = proceedingJoinPoint.getArgs();
Object result = null;
try {
//前置通知
System.out.println("[★★★Logging]The method "+methodName+" begins with "+ Arrays.toString(args));
//将方法的调用转到原始对象上
result = proceedingJoinPoint.proceed();
//返回通知
System.out.println("[★★★Logging]The method "+methodName+" returns "+result);
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常通知
System.out.println("[★★★Logging]The method "+methodName+" occurs "+throwable);
}finally {
//后置通知
System.out.println("[★★★Logging]The method "+methodName+" ends");
}
return result;
}
测试类
public void test(){
ApplicationContext ioc= new ClassPathXmlApplicationContext("aspectj.xml");
Calculator cac=(Calculator) ioc.getBean("cac");
String name=cac.getClass().getName();
system.out.println("name");
cac.add(6,2);
}
结果
cac类的名字是proxy,加入注解后, aspectj 处理计算机类传入的对象,返回了一个proxy对象,前面讲过 动态代理的介绍。
基于XML配置aop
除了使用aspectj 还可以使用spring自己的方式声明切面,xml元素完成
AspectJ更多应用
<bean class="com.spring.aop.CalculatorImpl" id="calculator"></bean>
<bean class="com.spring.aop.LoggingAspectJ2" id="aspectJ"></bean>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring.aop.Calculator.*(..))" />
<aop:aspect ref="aspectJ">
<aop:before method="beforeAdvice" pointcut-ref="pointcut"></aop:before>
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowable" pointcut-ref="pointcut" throwing="e"/>
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
类中无需标注注解了
测试类
@Test
public void test2() {
ApplicationContext ioc=new ClassPathXmlApplicationContext("bean-xml.xml");
Calculator cac = (Calculator) ioc.getBean("calculator");
cac.add(10, 2);
}
}
结果