1.AOP编程
1.1 AOP概念
AOP (Aspect Oriented Programing) 面向切面编程 Spring动态代理开发
以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建
切面 = 切入点 + 额外功能
OOP (Object Oritened Programing) 面向对象编程 Java
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建
POP (Producer Oriented Programing) 面向过程(方法、函数)编程 C
以过程(方法、函数) 为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建
1.2 AOP编程的开发步骤
- 原始对象
- 额外功能 (MethodInterceptor)
- 切入点
- 组装切面 (额外功能+切入点)
1.3 切面的名词解释
切面 = 切入点 + 额外功能
几何学
面 = 点 + 相同的性质
2. 动态代理类的创建
2.1 JDK的动态代理
public class TestJDKProxy {
public static void main(String[] args) {
//1 创建原始对象
UserService userService = new UserServiceImpl();
//2 JDK创建动态代理
InvocationHandler handler = new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------proxy log --------");
//原始方法运行
Object ret = method.invoke(userService, args);
return ret;
}
};
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
userServiceProxy.login("suns", "123456");
userServiceProxy.register(new User());
}
}
2.2 CGlib的动态代理
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证2者方法一致,同时在代理类中提供新的实现(额外功能+原始方法)
public void test4(){
AdminService adminService=new AdminService();
Enhancer enhancer=new Enhancer();
//设置类加载器
enhancer.setClassLoader(String.class.getClassLoader());
//设置父类
enhancer.setSuperclass(adminService.getClass());
MethodInterceptor methodInterceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("===== cglib log =====");
Object ret = method.invoke(adminService,objects);
return ret;
}
};
//设置额外功能
enhancer.setCallback(methodInterceptor);
//通过字节码技术动态创建子类实例
AdminService adminServiceProxy = (AdminService) enhancer.create();
adminServiceProxy.login("xiaohahaha","123123");
adminServiceProxy.register(new User("lilei","666666"));
}
总结:
- JDK动态代理 Proxy.newProxyInstance() 通过接口创建代理的实现类
- Cglib动态代理 Enhancer 通过继承父类创建的代理类
3. AOP的底层实现原理(简易版)
3.1 核心问题
- AOP如何创建动态代理类
借用了其他类的类加载器,通过动态字节码技术,进行创建
- Spring工厂如何加工创建代理对象
通过原始对象的id值,获得的是代理对象
底层是Bean的后置处理技术(BeanPostProcessor)对对象进行处理生成代理对象, 且最终是返回的代理对象
3.2 Spring工厂如何加工原始对象
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler invocationHandler=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====BeanPostProcessor====");
Object ret = method.invoke(bean, args);
return ret;
}
};
Object proxyInstance = Proxy.newProxyInstance(String.class.getClassLoader(), bean.getClass().getInterfaces(), invocationHandler);
return proxyInstance;
}
}
<!--1. 实现BeanPostProcessor 进行加工
2. 配置文件中对BeanPostProcessor进行配置
-->
<bean id="studentService" class="com.tcgroup.service.StudentServiceImpl"/>
<bean id="proxyBeanPostProcessor" class="com.tcgroup.beanPost.ProxyBeanPostProcessor"/>
4、基于注解的AOP编程
4.1 基于注解的AOP编程的开发步骤
- 原始对象
- 额外功能
- 切入点
- 组装切面
原来额外功能的实现:通过实现MethodInterceptor接口,在invoke方法里面实现
public class MyArround implements MethodInterceptor{
public Object invoke(MethodInvocation invocation){
Object ret = invocation.proceed();
return ret;
}
}
原来切入点是在aop:pointcut标签里面定义
<aop:config
<aop:pointcut id="" expression="execution(* login(..))"/>
通过注解编程:只要在类上面加上@Aspect注解 就可以告知Spring这是一个额外功能的实现类,在方法加上@Around注解,就可以在注解里面定义切入点了,不需要实现任何接口
@Aspect
public class MyAspect {
@Around("execution(* login(..))")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----- Aspect log -----");
Object ret =joinPoint.proceed();
return ret;
}
}
接下来在Spring配置文件里面告知Spring基于注解进行AOP编程
<bean id="studentService" class="com.tcgroup.service.StudentServiceImpl"/>
<!-- <bean id="proxyBeanPostProcessor" class="com.tcgroup.beanPost.ProxyBeanPostProcessor"/>-->
<!-- 创建额外功能的实现类的实例 -->
<bean id="arround" class="com.tcgroup.aspect.MyAspect"/>
<!--告知Spring基于注解进行AOP编程-->
<aop:aspectj-autoproxy />
实现结果和预期一致,跟之前不使用注解的结果是一样的
4.2 切入点复用 @Pointcut注解
切入点复用:在切面类中定义一个函数 上面@Pointcut注解 通过这种方式,定义切入点表达式,后续更加有利于切入点复用。
- 定义一个空参空方法体的方法
- 在方法上加上@Pointcut注解,在注解中定义切入点
- 可以在其他方法上通过 @Around注解的value属性引用这个切入点
@Aspect
public class MyAspect {
@Pointcut("execution(* login(..))")
public void loginPointCut(){}
@Around(value = "loginPointCut()")
public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----- Aspect log -----");
Object ret =joinPoint.proceed();
return ret;
}
@Around(value = "loginPointCut()")
public Object arround2(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("----- 其他功能 -----");
Object ret =joinPoint.proceed();
return ret;
}
}
5. 指定动态代理的创建方式
AOP底层实现有2种代理创建方式
- JDK 通过实现接口 做新的实现类方式 创建代理对象
- Cglib通过继承父类 做新的子类 创建代理对象
默认情况下 AOP编程底层都是应用JDK动态代理创建方式
如果需要切换Cglib通过继承父类的动态代理创建方式
- 基于注解AOP开发
在告知Spring基于注解进行AOP编程时, 通过proxy-target-class=“true” 设定成Cglib的创建方式
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 传统的AOP开发
在aop:config标签里面 同样通过proxy-target-class=“true” 设定成Cglib的创建方式
<aop:config proxy-target-class="true">
<!-- expression:切入点表达式
execution(*空格*(..)) 所有类的所有方法都作为切入点-->
<aop:pointcut id="pc" expression="execution(public boolean com.tcgroup.service.UserService.login(String,String))"/>
<!-- 组装:目的:把切入点与额外功能进行整合 -->
<aop:advisor advice-ref="arrount" pointcut-ref="pc"/>
</aop:config>
6.AOP开发中的一个坑
坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。
如果想让内层的方法也调用代理对象的方法,就要AppicationContextAware获得工厂,进而获得代理对象。
给StudentService的login和register方法都加了额外功能
如果从register方法中调用login方法, 应该两个方法都实现额外功能的, 但是实际只有register方法实现了额外功能,但是login方法没有实现, 这是因为当register方法调用login方法时,调用的时原始对象的login方法,而原始对象的login方法是没有实现额外功能的。
如果想让内层的方法也调用代理对象的方法,就要让原始类实现AppicationContextAware接口 获得工厂,进而获得代理对象。再通过工厂再次getBean拿到对象,这是拿到的还是原来的代理对象,然后在通过代理对象进行调用其他方法
public class StudentServiceImpl implements StudentService, ApplicationContextAware {
private ApplicationContext applicationContext ;
@Override
public void register(User user) {
System.out.println("=====StudentServiceImpl.register =====");
StudentService studentService=(StudentService) applicationContext.getBean("studentService");
studentService.login("zhangsan","999666");
}
@Override
public boolean login(String name, String password) {
System.out.println("=====StudentServiceImpl.login=====");
return false;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
7. AOP 总结