1. AOP
1.1 什么是AOP面向切面编程
1.1.1 什么是AOP?
Aspect Oriented Program
面向切面编程
在不改变原有逻辑上增加额外的功能,比如解决系统层面的问题,或者增加新的功能
AOP思想把功能分两个部分,分离系统中的各种关注点
核心关注点
:业务的主要功能横切关注点
:非核心、额外增加的功能
好处
○ 减少代码侵入,解耦
○ 可以统一处理横切逻辑
○ 方便添加和删除横切逻辑
使用场景:
权限控制
缓存
日志处理
事务控制
1.2 核心概念
横切、通知、连接点、切入点、切面
横切关注点
● 对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点
● 比如 权限认证、日志、事物
通知 Advice
● 在特定的切入点上执行的增强处理,有5种通知,后面讲解
● 做啥? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用
连接点 JointPoint
● 要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,
● 一般是方法的调用前后,全部方法都可以是连接点
● 只是概念,没啥特殊
切入点 Pointcut
● 不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那几个你想要的方法
● 在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint连接点
● 过滤出相应的 Advice 将要发生的joinpoint地方
切面 Aspect
● 通常是一个类,里面定义 切入点+通知 , 定义在什么地方; 什么时间点、做什么事情
● 通知 advice指明了时间和做的事情(前置、后置等)
● 切入点 pointcut 指定在什么地方干这个事情
● web接口设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
● 目标 target,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
● 织入 Weaving,把切面(某个类)应用到目标函数的过程称为织入
● AOP代理,AOP框架创建的对象,代理就是目标对象的加强,Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理
1.3 通知Advice类型讲解
@Before
前置通知:
在执行目标方法之前运行
@After
后置通知:
在目标方法运行结束之后
@AfterReturning
返回通知:
在目标方法正常返回值后运行
@AfterThrowing
异常通知:
在目标方法出现异常后运行
@Around
环绕通知:
在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
,需要手动执行 joinPoint.procced()
1.4 举个通俗的栗子-AOP面向切面编程
常见例子
用户下单
- 核心关注点:创建订单
- 横切关注点:记录日志、控制事务
用户观看付费视频
- 核心关注点:获取播放地址
- 横切关注点:记录日志、权限认证
//目标类 VideoOrderService; 里面每个方法都是连接点,;切入点是CUD类型的方法,R读取的不作为切入点
//CRDU全称:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
class VideoOrderService{
//新增订单
addOrder(){ }
//查询订单
findOrderById(){}
//删除订单
delOrder(){}
//更新订单
updateOrder(){}
}
//权限切面类 = 切入点+通知
class PermissionAspects{
//切入点 定义了什么地方
@Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
public void pointCut(){}
//before 通知 表示在目标方法执行前切入, 并指定在哪个方法前切入
//什么时候,做什么事情
@Before("pointCut()")
public void permissionCheck(){
System.out.println("在 xxx 之前执行权限校验");
}
....
}
//日志切面类 = 切入点+通知
class LogAspect{
//切入点 定义了什么地方
@Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
public void pointCut(){}
//after 通知 表示在目标方法执行后切入, 并指定在哪个方法前切入
//什么时候,做什么事情
@After("pointCut()")
public void logStart(){
System.out.println("在 xxx 之后记录日志");
}
....
}
1.5 AOP切入点表达式
访问修饰符 返回值类型(必填) 包和类 方法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除了返回类型、方法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)
@Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
1.5.1 常见匹配语法
*
匹配任何数量字符 单个;
..
匹配任何数量字符,可以多个,在类型模式中匹配任何数量子包;在方法参数模式中匹配任何数量参数
() 匹配一个不接受任何参数的方法
(..) 匹配一个接受任意数量参数的方法
(*) 匹配了一个接受一个任何类型的参数的方法
(*,Integer) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是Integer类型
2. AOP动态代理
2.1 AOP的实现策略之JDK动态代理
JDK动态代理的原理是对指定的业务类实现的接口再生成一个实现类,在其中的业务方法中来实现代理
定义一个java.lang.reflect.InvocationHandler接口的实现类,重写invoke方法
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
public class JdkProxy implements InvocationHandler {
//目标类
private Object targetObject;
//获取代理对象
public Object newProxyInstance(Object targetObject){
this. targetObject = targetObject;
//绑定关系,也就是和具体的哪个实现类关联
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) {
Object result = null;
try{
System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin");
result = method.invoke(targetObject,args);
System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
2.2 AOP的实现策略之CGLib动态代理
CgLib动态代理的原理是对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理
public class CglibProxy implements MethodInterceptor {
//目标类
private Object targetObject;
//绑定关系
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
//设置代理类的父类(目标类)
enhancer.setSuperclass(this.targetObject.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建子类(代理对象)
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
result = methodProxy.invokeSuper(o,args);
System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
2.3 CGLib动态代理和JDK动态代理总结
● 两种动态代理的区别:
○ JDK动态代理:要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLib动态代理
○ CGLib动态代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展
○ JDK动态代理是自带的,CGlib需要引入第三方包
○ CGLib动态代理基于继承来实现代理,所以无法对final类、private方法和static方法实现代理
● Spring AOP中的代理使用的默认策略:
○ 如果目标对象实现了接口,则默认采用JDK动态代理
○ 如果目标对象没有实现接口,则采用CgLib进行动态代理
○ 如果目标对象实现了接扣,程序里面依旧可以指定使用CGlib动态代理
3. AOP示例
● 需求分析:针对Videoservice接口实现日志打印
● 三个核心包
○ spring-aop:AOP核心功能,例如代理工厂
○ aspectjweaver:简单理解,支持切入点表达式
○ aspectjrt:简单理解,支持aop相关注解
● 定义service接口和实现类
● 定义横切关注点
● 引入相关包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
*
● maven仓库改为阿里云
<repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
● 添加schema
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
</beans>
● 配置bean和aop
<bean id="timeHandler" class="net.xdclass.sp.aop.TimeHandler"/>
<bean id="videoService" class="net.xdclass.sp.service.VideoServiceImpl"/>
<!--aop配置-->
<aop:config>
<!--横切关注点-->
<!-- 切面 -->
<aop:aspect id="timeAspect" ref="timeHandler">
<!--定义切入点表达式-->
<!--<aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.sav*(..))"/>-->
<aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.*(..))"/>
<!--配置前置通知和后置通知-->
<aop:before method="printBefore" pointcut-ref="allMethodLogPointCut"/>
<aop:after method="printAfter" pointcut-ref="allMethodLogPointCut"/>
</aop:aspect>
</aop:config>
4. XML配置到注解配置
4.1 开启注解配置和包扫描
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//扫描指定的包,包括子包
context.scan("net.xdclass");
//里面完成初始化操作,核心方法
context.refresh();
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
Video video = (Video) context.getBean("video");
video.init();
}
4.2 常用注解和xml对比
-
bean定义
○ xml方式:
○ 注解方式:@Component
通用组件 细分:@Controller
(用于web层)@Service
(用于service层)@Repository
(用于dao仓库层) -
bean取名
○ xml方式:通过id或者name
○ 注解方式:@Component("XXXX")
-
bean注入
○ xml方式:通过<property …>
○ 注解方式:类型注入@Autowired
名称注入@Autowired
+@Qualifier
以及@Resource
-
bean生命周期
○ xml方式:init-method
、destroy-method
○ 注解方式:@PostConstruct
初始化、@PreDestroy
销毁 -
bean作用范围
○ xml方式:scope
属性
○ 注解方式:@scope
注解
5. @Configuration
和@Bean
注解定义第三方bean
@Configuration
标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)
@bean
注解:用于告诉方法产生一个Bean对象,然后这个Bean对象交给Spring管理,Spring将会将这个Bean对象放在自己的IOC容器中
注意点: SpringIOC容器管理一个或者多个bean,这些bean都需要在
@Configuration
注解下进行创建
@Configuration
public class AppConfig {
//使用@bean注解,表明这个bean交个spring 进行管理
// 如果没有指定名称,默认采用 方法名 + 第一个字母小写 作为bean的名称
@Bean(name = "videoOrderName",initMethod = "init",destroyMethod = "destroy")
@Scope
public VideoOrder videoOrder(){
return new VideoOrder();
}
}
6. 自动映射配置文件@PropertySource
和@Value
注解
● @PropertySource
,指定加载配置文件(即配置文件映射到实体类)
● 使用@Value
映射到具体的java属性
@Configuration
@PropertySource(value = {"classpath:config.properties"})
public class CustomConfig {
@Value("${server.host}")
private String host;
@Value("${server.port}")
private int port;
//setter getter
}
7. 注解配置AOP面向切面编程
7.1 Spring AOP注解的基础准备
- 声明切面类
@Aspect
(切面): 通常是一个类,里面可以定义切入点和通知 - 配置切入点和通知
@Pointcut
@Around ...
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
public class LogAdvice {
//切入点表达式
@Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
@Before("aspect()")
public void beforeLog(JoinPoint joinPoint){
System.out.println("LogAdvice beforeLog");
}
//后置通知
@After("aspect()")
public void afterLog(JoinPoint joinPoint){
System.out.println("LogAdvice afterLog");
}
/**
* 环绕通知
* @param joinPoint
*/
@Around("aspect()")
public void around(JoinPoint joinPoint){
Object target = joinPoint.getTarget().getClass().getName();
System.out.println("调用者="+target);
//目标方法签名
System.out.println("调用方法="+joinPoint.getSignature());
//通过joinPoint获取参数
Object [] args = joinPoint.getArgs();
System.out.println("参数="+args[0]);
long start = System.currentTimeMillis();
System.out.println("环绕通知 环绕前=========");
//执行连接点的方法
try {
((ProceedingJoinPoint)joinPoint).proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("环绕通知 环绕后=========");
System.out.println("调用方法总耗时 time = " + (end - start) +" ms");
}
}
7.2 开启Spring AOP注解和扫描
@Configuration
@ComponentScan("net.xdclass")
@EnableAspectJAutoProxy //开启了spring对aspect的支持
public class AnnotationAppConfig {
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
}