应用场景
AOP-面向切面编程。主要功能:日志记录,性能统计,安全控制,事务处理,异常处理。我的理解是:将一些非业务逻辑代码,从业务代码中剥离出来,从而符合将不变的东西,从变化的部分抽离出来。
AOP与OOP:AOP是一种看待问题的一种方式,OOP也是。若把应用比作蛋糕,OOP将每一层看做是(用户模块,xxx模块),而AOP是竖着看。二者无孰优孰劣,AOP是OOP的一种补充。
动态代理与静态代理
代理,在现实生活很常见,比如租客,房东,二房东。三者之间的关系。
静态代理:只在程序运行之前,代理类与委托类的关系就已经确定了。
jdk代理(接口代理)
特点:1-代理对象不需要实现接口,2-委托者需要实现接口。
public class ProxyFactory {
// 维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象生成一个代理对象
/**
* ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h) {
* Objects.requireNonNull(h);
*
* ClassLoader loader: 指定目标对象使用的类加载器,获取加载器的路径固定
* Class<?>[] interfaces,:目标对象的接口类型,使用泛型指定
* h:事件处理,执行目标对象方法的时候,会生成目标对象
* 实际上是两级代理
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk反射");
return method.invoke(target, args);
}
}
);
}
}
调用更简单
// 创建目标对象
ITeacherDao target = new TeacherDao();
// 给目标对象,创建代理对象
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
proxyInstance.teach();
缺点:委托类必须实现代理行为接口。
Cglib代理
原理:对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理
public class ProxyFactory implements MethodInterceptor {
// 1. 维护一个目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 2.返回一个代理对象,是target对象的代理对象
public Object getProxyInstance(){
// 1.创建一个工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(target.getClass());
// 3.设置回调的对象,此句会导致调用动态代理类对象的方法会被指派到CglibProxy对象的intercept()方法
enhancer.setCallback(this);
// 4.通过字节码技术动态创建动态代理类实例
return enhancer.create();
}
// 3.重写intercept 方法,可调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib 代理模式");
return method.invoke(target, args);
}
}
Cglib特点
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
AOP编程
引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
第一步:写个配置
主要注解
@Configuration
@EnableAspectJAutoProxy
/**
* aop:指在程序运行期间动态的将某段代码切入到指定的位置进行运行的编程方式
* 1,导入aop 模块,spring-aspects
* 2,编写业务逻辑类
* 3, 定义一个增强类,切面类.添加注解@Aspect
* 4,将切面类和业务类加入到容器中
* 6,告诉spring 哪个类是一个切面类
* //@EnableAspectJAutoProxy 最重要的
*
* 总结:
* 三步:
* 1,将业务逻辑类和切面了都加入到容器中,告诉Spring哪个是切面类,@Aspect
* 2,在切面类上每一个通知方法上标注通知注解,告诉Spring何时何地运行,切入表达式
* 3,开启基于注解的aop 模式,@EnableAspectJAutoProxy
* */
@Configuration
@EnableAspectJAutoProxy
public class MainConfigOfAOP {
// 业务类
@Bean
public MathCalculator calculator(){
return new MathCalculator();
}
// 增强类
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
第二步:业务类
public class MathCalculator {
public int div(int i,int j){
return i-j;
}
}
第三步:切面类
/**
* 切面类,增强类
*/
@EnableAspectJAutoProxy
@Aspect
@Slf4j
@Component
public class LogAspects {
private static final ThreadLocal<Long> startTime = new NamedThreadLocal<Long>(" StratTime ");
@Resource
private HttpServletRequest httpClient;
// 抽取公共的切入点表达式。比如各种切入方法。下面用的时候,直接就可以写方法名就可以了。
/**
* 表达式:访问修饰符 返回值 包名,包名,… 类名 方法名(参数列表)
* .. :表示当前包以及子包
* . :表示当前包下(不包括子包)
* execution( * com.example.demo.controller..*.*(..)) :表示 任何访问修饰符和返回值 com.example.demo.controller..* controller包下以及子包下的所有类的所有所有方法
* */
@Pointcut("execution( * com.example.demo.controller..*.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void logStart() {
long beginTime = System.currentTimeMillis();
startTime.set(beginTime);
System.out.println("正常运行开始------");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
Long beginTime = startTime.get();
long endTime = System.currentTimeMillis();
log.info(" time:{}, url:{}, method:{}, class:{}",
endTime-beginTime+" ms",
httpClient.getRequestURL(),
httpClient.getMethod(),
joinPoint.getTarget().getClass().getName());
System.out.println("正常运行结束------");
}
// @Around("pointCut()")
// public void aroundMenthord(){
// System.out.println("环绕通知------》");
// }
@AfterReturning("pointCut()")
public void logReturn() {
System.out.println("正常运行返回------");
}
@AfterThrowing("pointCut()")
public void logExeception() {
System.out.println("正常运行异常------");
}
}