作为一个稀有的Java妹子,所写的所有博客都只是当作自己的笔记,留下证据自己之前是有用心学习的~哈哈哈哈(如果有不对的地方,也请大家指出,不要悄悄咪咪的不告诉我)
一、AOP
1.什么是AOP
1.AOP是面向切面编程,与OOP的纵向继承特点相比,AOP关注的是横向功能的扩展,OOP的最小单位是类,AOP的最小单位是切面。
2.AOP是为了提取出重复的代码,减少代码的冗余,切面就是这些重复代码提取后存放的位置。一般是解决系统级别的问题,如事务、日志监控、身份鉴别等,使得业务代码与这些系统级别的代码区分开。
3.所以AOP其实就是为了简化代码,是程序员更加的轻松,专注业务代码的逻辑。
2.AOP的实现
AOP的底层就是代理模式,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
1.静态代理和动态代理
代理分为两种,它们都有增强方法的作用,但是静态代理是在编译期就为目标类添加了相应的功能,而动态代理是在运行期在内存里生成代理对象,不会改变目标类的字节码。
静态代理
需要手动的再写一个类去实现目标接口,然后对目标方法进行增强。
//目标接口
public interface MovieService {
void paly();
}
//实现类
public class MovieServiceImpl implements MovieService {
@Override
public void paly() {
System.out.println("电影开始拉");
}
}
以上是原始的业务代码,比如这时候新的需求是在电影放映前插播一些广告,那么在不修改原来的paly方法的前提下,就采用静态代理的方式,创建另外一个类去实现接口。
public class MovieServiceProxy implements MovieService {
private MovieServiceImpl movieService;
public MovieServiceProxy(MovieServiceImpl movieService){
this.movieService = movieService;
}
@Override
public void paly() {
playAd();
movieService.paly();
}
public void playAd(){
System.out.println("广告开始。。");
}
}
public class Test {
public static void main(String[] args) {
MovieService movieService = new MovieServiceProxy(new MovieServiceImpl());
movieService.paly();
}
}
结果:
广告开始。。
电影开始拉
静态代理在不改变原有的方法前提下,需要新增一个类实现接口,然后在新的静态代理类中去实现方法的增强。
动态代理
动态代理的目的跟静态代理是一样的,就是想在不修改原有实现类的基础上增强方法,与静态代理的区别就是动态代理不需要手动的创建一个类,而是在程序运行时自动在内存中创建目标接口的代理类。
Spring AOP的动态代理有两种,JDK动态代理和CGLIB动态代理,前者是基于接口的代理,后者是基于类的代理。
1.jdk动态代理
主要是通过反射机制,为目标类生成代理类,核心类是:
·InvocationHandler 接口
·Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1、 loader:类加载器
2、interfaces:被代理的接口
3、InvocationHandler:InvocationHandler对象
代理类实现了目标接口,调用InvocationHandler执行任务。
还是以刚才的Movie接口举例
//接口
public interface MovieService {
void paly();
}
//实现类
public class MovieServiceImpl implements MovieService {
@Override
public void paly() {
System.out.println("电影开始拉");
}
}
public class DynamicService implements InvocationHandler {
private Object object;
public DynamicService(Object o){
this.object = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理拉");
method.invoke(object,args);
return null;
}
}
public class Test {
public static void main(String[] args) {
InvocationHandler invocationHandler1 = new DynamicService(new MovieServiceImpl());
MovieService movieService1 = (MovieService)Proxy.newProxyInstance(MovieServiceImpl.class.getClassLoader(), MovieServiceImpl.class.getInterfaces(), invocationHandler1);
movieService1.paly();
}
}
结果:
动态代理拉
电影开始拉
再定义一个接口
public interface TVService {
void paly();
}
public class TVServiceImpl implements TVService {
@Override
public void paly() {
System.out.println("电视剧开始拉");
}
}
public class Test {
public static void main(String[] args) {
InvocationHandler invocationHandler1 = new DynamicService(new MovieServiceImpl());
MovieService movieService1 = (MovieService)Proxy.newProxyInstance(MovieServiceImpl.class.getClassLoader(), MovieServiceImpl.class.getInterfaces(), invocationHandler1);
movieService1.paly();
//定义新的InvocationHandler
InvocationHandler invocationHandler2 = new DynamicService(new TVServiceImpl());
TVService tVService = (MovieService)Proxy.newProxyInstance(TVServiceImpl.class.getClassLoader(), TVServiceImpl.class.getInterfaces(), invocationHandler2);
tVService.paly();
}
}
结果:
动态代理拉
电影开始拉
动态代理拉
电视剧开始拉
那么这里并没有手动的创建MovieService和TVService的静态代理类,而是通过动态代理获取对应的代理类,并且jdk动态代理,代理类和目标类都实现同一个接口,方法名跟目标类是一样的,只是方法做了增强。
2.CGLIB动态代理
当目标类没有实现任何接口的时候,Spring AOP会选择使用 CGLIB 来动态代理目标类,主要就是通过代理类继承目标类,重写目标类的方法。
核心类是:
·MethodInterceptor 接口
·Enhancer 类
MethodInterceptor这个接口是一个拦截器,每个cglib的代理类都会有一个拦截器,当调用目标类的时候,就会被拦截器拦截,执行代理类的方法。
3.AOP的相关术语
1.通知(Advice)
就是公用的功能
2.连接点(JoinPoint)
允许通知的地方
3.切入点(Pointcut)
具体的某个方法
4.切面(Aspect)
通知和切入点的结合
5.目标(target)
目标类,就是被代理的类,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
6.代理(Proxy)
aop的实现是依靠代理机制
4.实际的例子
在开发中也遇到过很多aop的例子,其实只要理解了aop是为了做什么的,就能发现代码里很多地方都使用了aop,比如Spring的事务,只要在类或者某个方法上加上@Transactional注解,就会开启事务,其实这就是aop为没一个使用该注解的类生成了代理类,代理类的每个方法在执行前会开启事务,执行完会提交事务。
接下来举一个我在实际开发中用到的一个很简单aop例子,就是为某些方法打印入参。
//这是切面
@Component
@Aspect
@Slf4j
public class MethodLogAspect {
//定义切点,我自定义了一个注解,凡是使用了该注解的方法都会被当作切点
@Pointcut("@annotation(com.jiebai.qqsk.member.aspect.MethodLog)")
public void poinCut(){
}
//定义了前置通知和后置通知
@Before("poinCut()")
public void Before(JoinPoint jp){
String methodName = jp.getSignature().getName();
Map<String,String> map = new HashMap<>();
String[] parameterNames = ((MethodSignature) jp.getSignature()).getParameterNames();
Object[] args = jp.getArgs();
if(parameterNames!=null && parameterNames.length>0){
for(int i=0;i<parameterNames.length;i++){
map.put(parameterNames[i],args[i].toString());
}
}
log.info("进入方法"+methodName+",参数:"+ JSONObject.toJSONString(map));
}
@After("poinCut()")
public void after(JoinPoint jp){
String methodName = jp.getSignature().getName();
log.info("退出方法"+methodName);
}
}
在使用的时候只需要在方法上加上自定义注解即可
@Override
@MethodLog
public Map<String, Object> userWithdrawGTC(BigDecimal amount, String type, Integer userId) throws Exception {
.....省略业务代码
}