day53_spring

今日内容

零、 复习昨日

零、 复习昨日

一、代理设计模式

代理的设计理念是限制对象的直接访问,即不能通过 new 的方式得到想要的对象,而是访问该对象的代理类。

这样的话,我们就保护了内部对象,如果有一天内部对象因为 某个原因换了个名或者换了个方法字段等等,那对访问者来说 一点不影响,因为他拿到的只是代理类而已,从而使该访问对 象具有高扩展性。

代理类可以实现拦截方法,修改原方法的参数和返回值,满足 了代理自身需求和目的,也就是代理的方法增强性。

按照代理的创建时期,代理可分为:静态代理动态代理

静态代理由开发者手动创建,在程序运行前,已经存在;

动态代理不需要手动创建,它是在程序运行时动态的创建代理类。


总结:代理模式–给某个目标对象提供一个代理,以改变对该对象的访问方式,以便于对目标方法的增强

图…

1.1 静态代理

静态代理,在运行之前,提前先把代理创建好.

需求: 目标类(Fangdong),目标方法(chuzu()),来一个代理FangdongProxy(中介),中介会在目标方法执行前后,实现一些增强的功能.

演示:

目标接口和实现类

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc 房东接口
 */
public interface Fangdong {
    void chuzu();
}

public class FangdongImpl implements Fangdong{
    public void chuzu() {
        System.out.println("房东出租房!" );
    }
}

代理

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc 房东代理
 */
public class FangdongProxy {

    private Fangdong fangdong;

    public FangdongProxy(){
        this.fangdong = new FangdongImpl();
    }

    // 代理执行方法
    public void chuzu(){

        // 前: 功能增强
        System.out.println("租房前: 中介发布消息");
        // 目标方法:出租房
        fangdong.chuzu();
        // 后: 功能增强
        System.out.println("中介签合同/后期水电气的维修" );
    }
}

测试

public static void main(String[] args) {

    // 找代理
    FangdongProxy proxy = new FangdongProxy();
    proxy.chuzu();
}

租房前: 中介发布消息
房东出租房!
中介签合同/后期水电气的维修

以上就是静态代理,FangdongProxy在程序运行前,是提前创建好的.

现在需要有一人需要买车,有汽车厂商,就需要一个汽车代理商,代理商在真正执行卖车前后可以做一些增强.即需要再创建汽车厂的代理

但是.如果需要有一人需要买酒,有酒厂,就需要酒厂的代理,代理商在真正执行卖酒前,后可以做一些增强,即需要再创建酒厂的代理

…依次类推

这样下去,每做一件事情,就需要一个代理,代理类越来越多,多个代理类的增强的功能代码冗余


既然是这样,那能不能根据目标类,动态产生代理呢?

答案是可以的,那就是动态的为程序生成代理!

1.2 动态代理

动态代理: 在程序运行过程中,动态的为目标类产生代理类

实现动态代理有两种方案:

  • jdk动态代理(JDK自带)
    • jdk动态代理,只能代理接口,即目标类必须有接口
  • cglib动态代理(第三方技术,需要导入第三方jar包)
    • cglib动态代理,目标类可以是接口也可以是实现类

1.2.1 JDK动态代理

在java的java.lang.reflect.InvocationHandler包下有一个InvocationHandler接口,可以实现动态产生代理对象.

package com.qf.proxy.dynamic.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class MyInvocationHandler implements InvocationHandler {

    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    /**
     * @param proxy   代理对象
     * @param method  目标方法
     * @param args    目标方法执行需要的参数
     * @return 目标方法执行后返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 目标方法前:
        System.out.println("前期宣传" );

        // 目标方法执行
        Object ret = method.invoke(target,args);

        // 目标方法后:
        System.out.println("后期维护" );
        return ret;
    }
}

测试

public class TestJDKDynamic {

    public static void main(String[] args) {

        // 动态代理,只需要给定目标类,就会产生目标类的代理对象

        // Class clazz = FangdongImpl.class;
        // ClassLoader loader = clazz.getClassLoader( );
        // Class[] interfaces = clazz.getInterfaces( );
        // MyInvocationHandler handler = new MyInvocationHandler(new FangdongImpl( ));
        // /**
        //  * @param   loader 被代理的类(目标类)的类加载器
        //  * @param   interfaces 被代理的类(目标类)的实现的接口数组
        //  * @param   handler 刚才自己创建的MyInvocationHandler
        //  * @return 返回代理对象
        //  */
        // Fangdong proxy = (Fangdong) Proxy.newProxyInstance(loader, interfaces, handler);
        //
        // proxy.chuzu();


        Class clazz = CarFactoryImpl.class;
        ClassLoader loader = clazz.getClassLoader( );
        Class[] interfaces = clazz.getInterfaces( );
        MyInvocationHandler handler = new MyInvocationHandler(new CarFactoryImpl( ));
        /**
         * @param   loader 被代理的类(目标类)的类加载器
         * @param   interfaces 被代理的类(目标类)的实现的接口数组
         * @param   handler 刚才自己创建的MyInvocationHandler
         * @return 返回代理对象
         */
        CarFactory proxy = (CarFactory) Proxy.newProxyInstance(loader, interfaces, handler);

        proxy.sellCar();

        //  JDK的动态代理,目标类必须实现接口,否则代理失败
    }
}

1.2.2 CGLIB动态代理

CGLIB是第三方技术,需要导入第三方jar包

但是Spring框架已经整合了CGLIB技术,即在导入spring依赖时,已经导入了cglib包了

day53_spring_第1张图片
package com.qf.proxy.dynamic.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class MyProxyInterceptor implements MethodInterceptor {

    // cglib增强器
    private Enhancer enhancer = new Enhancer();

    public MyProxyInterceptor(Class targetClass){
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(this);
    }

    // 获得代理对象
    public Object getProxyBean() {
        return enhancer.create();
    }


    /**
     * @param target  目标对象(被代理对象)
     * @param method  目标方法
     * @param args  目标方法的参数
     * @param methodProxy 代理目标方法
     * @return 目标方法执行后,返回数据
     * @throws Throwable
     */
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        // 前:
        System.out.println("前期宣传" );

        // 目标方法
        Object ret = methodProxy.invokeSuper(target, args);

        // 后:
        System.out.println("后期维护" );

        return ret;
    }
}
package com.qf.proxy.dynamic.cglib;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class TestCGLIB {

    public static void main(String[] args) {

        // 动态代理,只需要给定目标类,就会产生目标类的代理对象

        // MyProxyInterceptor myProxyInterceptor = new MyProxyInterceptor(FangdongImpl.class);
        // Fangdong proxy = (Fangdong) myProxyInterceptor.getProxyBean( );
        // proxy.chuzu();

        MyProxyInterceptor myProxyInterceptor = new MyProxyInterceptor(CarFactoryImpl.class);
        CarFactoryImpl proxy = (CarFactoryImpl) myProxyInterceptor.getProxyBean( );
        proxy.sellCar();

        // CGLIB代理的好处,就在于目标类即可以实现接口,也可以不用实现接口,都可以代理

    }
}

1.3 代理总结

需要理解: 什么是代理? 为什么需要代理? 代理的类型?

动态代理的代码,不需要去记,知道两种不同动态代理方案的特点.


理解几个词汇

  • 目标类
  • 目标方法
  • 代理类
  • 增强

二、AOP

2.1 概念

Spring中另外一个核心功能,AOP


AOP(Aspect Oriented Programming),即面向切面编程.

OOP(Object Oriented Programming ),即面向对象编程.

AOP面向切面编程,利用 一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了 多个类的公共行为抽取出封装到一个可重用模块,并将其命名 为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

day53_spring_第2张图片

面向切面编程的作用:就是将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果.

原理: AOP底层使用的就是动态代理,给AOP指定哪些类型(目标类)需要增强,就会产生对应的代理对象,代理对象执行方法前后会先执行增 强的方法.

好处:减少系统的重复代码,降低模块之间的耦合度,便于维护,可以只关注核心业务

2.2 AOP术语

连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。即每个方法在切入之前,都是连接点

切入点(Pointcut):被Spring切入连接点。即真正会增强的目标方法

通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。

目标对象(Target):被代理的目标对象

织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。

代理(Proxy):被AOP织入通知后,产生的结代理类。

切面(Aspect):由切点和通知组成

2.3 应用场景

  • 事务管理
    • 后续spring管理事务用的AOP原理
  • 权限校验
    • 后期使用Spring Security注解开发时,其实利用了AOP思想
  • 日志记录
  • 性能检测
  • 等等

2.4 AOP入门编程【重点】

需求: UserService以及UserServiceImpl,在其中方法执行前,后执行增强的方法

编程的过程:

  • 创建项目
  • 导入依赖
    • 核心依赖
    • 切面依赖
  • 目标类
  • 切面类
  • 织入(配置文件设置)
  • 测试

2.4.1导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.1.6.RELEASEversion>
        dependency>

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aspectsartifactId>
            <version>5.1.6.RELEASEversion>
        dependency>

        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.13.1version>
            <scope>testscope>
        dependency>
    dependencies>

2.4.2 目标类

public interface UserService {

    void findUserById();

    void deleteUserById();

}
public class UserServiceImpl implements UserService {

    @Override
    public void findUserById() {

        System.out.println("核心业务---> 查询用户" );

    }
    @Override
    public void deleteUserById() {

        System.out.println("核心业务---> 删除用户" );

    }
}

2.4.3 切面类

演示的环绕通知

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
public class MyAspect {

    /**
     * 定义增强的方法:如果前后都要增强的方法
     * 那么就还使用环绕通知/增强
     */
    /**
     * 自定义环绕通知
     * @param joinPoint 连接点/切入点,即目标方法
     * @return 目标方法的返回值
     */
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 目标方法前:
        System.out.println("开启事务/权限校验" );

        // 目标方法执行
        Object ret = joinPoint.proceed( );
        System.out.println("目标方法返回值---> " + ret );
        // 目标方法后:
        System.out.println("提交事务/日志记录" );
        return ret;
    }

}

2.4.4 配置(织入)


<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="userService" class="com.qf.service.impl.UserServiceImpl"/>

    
    <bean id="myAspect" class="com.qf.aspect.MyAspect"/>

    
    <aop:config>
        
        <aop:aspect ref="myAspect">
            
            
            
            <aop:around method="myAround"
                        pointcut="execution(* com.qf.service.impl.*.*(..))"/>
        aop:aspect>
    aop:config>
beans>

2.4.5 测试

@Test
public void testAspect() {
    String path = "applicationContext.xml";
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path);

    UserService userService = context.getBean("userService", UserService.class);
    userService.findUserById();

    System.out.println("-----------------" );

    userService.deleteUserById();
}
开启事务/权限校验
核心业务---> 查询用户
目标方法返回值---> null
提交事务/日志记录
-----------------
开启事务/权限校验
核心业务---> 删除用户
目标方法返回值---> null
提交事务/日志记录

练习: 重复

2.5 其他增强【重点】

  • 环绕通知: 目标方法执行前,后都有增强的方法(已经演示过)
    • 常用于事务管理,性能检测
  • 前置通知
    • 常用于权限检验
  • 后置通知
    • 常用于日志记录,或者释放资源
  • 后置返回通知
    • 常用于获得目标方法的返回值,处理返回值
  • 异常通知
    • 常用于代码有异常后去执行一些功能,处理异常

2.5.1 各种增强

package com.qf.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class MyAspect {

    /**
     * 定义增强的方法:如果前后都要增强的方法
     * 那么就还使用环绕通知/增强
     */

    /**
     * 自定义环绕通知
     * @param joinPoint 连接点/切入点,即目标方法
     * @return 目标方法的返回值
     */
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 目标方法前:
        System.out.println("开启事务/权限校验" );

        // 目标方法执行
        Object ret = joinPoint.proceed( );
        System.out.println("目标方法返回值---> " + ret );
        // 目标方法后:
        System.out.println("提交事务/日志记录" );
        return ret;
    }


    /**
     * 自定义前置通知
     * @param joinPoint,目标对象
     */
    public void myBefore(JoinPoint joinPoint) {
        // 目标对象
        Object target = joinPoint.getTarget( );
        System.out.println("target = " + target);
        // 获得目标方法签名(方法名)
        Signature signature = joinPoint.getSignature( );
        System.out.println("signature = " + signature);

        System.out.println("前置通知--->权限校验--->OK" );

        // 假设权限校验没有通过,通过抛出异常让代码停下,不再执行目标方法
        // System.out.println("前置通知--->权限校验--->ERROR" );
        // throw new RuntimeException("权限校验--->ERROR");
    }


    /**
     * 自定义后置通知
     */
    public void myAfter() {
        System.out.println("后置通知--->记录日志,释放资源" );
    }

    /**
     * 自定义后置返回通知
     * @param ret 用于接收目标方法返回的数据
     * @return
     */
    public Object myAfterRet(Object ret) {
        System.out.println("后置返回通知,接收到目标方法返回值--->" + ret);
        return ret;
    }

    public void myException(Exception e) {
        System.out.println("目标方法报的错---> " + e.getMessage());
    }
}

2.5.2 配置


<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="userService" class="com.qf.service.impl.UserServiceImpl"/>
    <bean id="houseService" class="com.qf.service.impl.HouseServiceImpl"/>


    
    <bean id="myAspect" class="com.qf.aspect.MyAspect"/>

    
    <aop:config>
        
        <aop:aspect ref="myAspect">
            
            
            
            


            
            
            

            
            

            
            <aop:after-throwing throwing="e" method="myException" pointcut="execution(* com.qf.service.impl.*.*(..))"/>

        aop:aspect>
    aop:config>

beans>

2.6 总结

  • 通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理。
  • 进而彻底解决了辅助功能(事务管理,日志记录,权限校验)冗余的问题;
  • 业务类中职责单一性得到更好保障;
  • 辅助功能也有很好的复用性。

2.7 AOP底层动态代理【面试】

AOP的底层使用 的就是动态代理,使用的技术

jdk+cglib都用了

  • 当目标类有接口的时候使用JDK动态代理
  • 当目标类没有实现接口时,使用CGLIB动态代理

三、注解开发AOP【重点】

3.1 注解介绍

IOC,DI,AOP全部使用注解开发

注解 解释
@Aspect 声明类为切面类
@Around 环绕通知
@Before 前置通知
@After 后置通知
@AfterReturning 后置返回通知
@AfterThrowing 异常通知
@Pointcut 抽取切入点表达式

业务层类加注解@Service,@Autowired

3.2 代码添加注解

重点是给切面类加注解

package com.qf.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc 切面
 */
@Component // 该注解,spring创建该类对象
@Aspect    // 该注解,该类是切面类
public class MyAspectAnno {

    /**
     * 定义一个切入点表达式公共方法
     */
    @Pointcut("execution(* com.qf.service.impl.*.*(..))")
    public void myPointcut(){}


    @Around("myPointcut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 目标方法前:
        System.out.println("开启事务/权限校验" );

        // 目标方法执行
        Object ret = joinPoint.proceed( );
        System.out.println("目标方法返回值---> " + ret );
        // 目标方法后:
        System.out.println("提交事务/日志记录" );
        return ret;
    }

    @Before("myPointcut()")
    public void myBefore(JoinPoint joinPoint) {
        // 目标对象
        Object target = joinPoint.getTarget( );
        System.out.println("target = " + target);
        // 获得目标方法签名(方法名)
        Signature signature = joinPoint.getSignature( );
        System.out.println("signature = " + signature);

        System.out.println("前置通知--->权限校验--->OK" );

        // 假设权限校验没有通过,通过抛出异常让代码停下,不再执行目标方法
        // System.out.println("前置通知--->权限校验--->ERROR" );
        // throw new RuntimeException("权限校验--->ERROR");
    }

    @After("myPointcut()")
    public void myAfter() {
        System.out.println("后置通知--->记录日志,释放资源" );
    }

    @AfterReturning(value = "myPointcut()",returning = "ret")
    public Object myAfterRet(Object ret) {
        System.out.println("后置返回通知,接收到目标方法返回值--->" + ret);
        return ret;
    }

    @AfterThrowing(value = "myPointcut()",throwing = "e")
    public void myException(Exception e) {
        System.out.println("目标方法报的错---> " + e.getMessage());
    }
}

3.3 配置文件扫描注解,让注解生效


<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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    
    <context:component-scan base-package="com.qf"/>

    
    <aop:aspectj-autoproxy/>

beans>

测试即可

    @Test
    public void testAspectByAnno() {
        String path = "applicationContextAnno.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path);
        // 使用注解,对象名,默认是类名首字母小写
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.findUserById();

        System.out.println("-----------------" );

        userService.deleteUserById();

        System.out.println("-----------------" );
        HouseService houseService = context.getBean("houseServiceImpl", HouseService.class);
        int i = houseService.deleteHouse( );
        System.out.println("i = " + i);
    }

任务

配置文件编写aop
注解开发aop
关于代理的几个问题,要介绍出来

------
复习mybatis的核心配置文件,测试代码
阿里巴巴数据库连接池druid
----
spring整合mybatis,实现crud

利用aop实现事务管理
-----------------------
明天早上
1 读单词
2 读昨日学习的总结
3 jvm

你可能感兴趣的:(#,Java2307,spring,java,缓存)