Spring框架(3)

JDK代理

代理模式
是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
在这里插入图片描述

1、静态代理

静态代理
静态代理中代理类与被代理类都需要实现同一个接口,这就说明我们的一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能是有非常多的类是需要被代理的,并且事先我们可能并不知道我们要代理哪个类。所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不好。

public class Test1 {
    public static void main(String[] args) {
        Person person =new Person("张三");
        Court court=new Lawyer(person);
        court.doCourt();
    }
}
// 接口
interface Court{
    void doCourt();
}
// 代理类
class Lawyer implements Court{
    private Person person;
    public Lawyer(Person person) {
        this.person = person;
    }
    @Override
    public void doCourt() {
        System.out.println("律师取证:视频证明张三当时正在旅游,不在案发现场");
        System.out.println("律师总结:张三不可能去杀人");
        person.doCourt();
    }
}
// 被代理的类
class Person implements Court{
    private String name;
    public Person(String name) {
        this.name = name;
    }
    @Override
    public void doCourt() {
        System.out.println(name+"说:我没有杀人");
    }
}

2、动态代理

1)Proxy动态代理

动态代理可以针对于一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在程序运行时才创建的代理模式成为动态代理。这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们的在Java代码中的“指示”动态生成的
Proxy 动态代理 JDK动态代理 面向接口
cglib 动态代理 第三方动态代理 面向父类
代码:

public class test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        final Dinner dinner = new Student("小明");
        // 通过Porxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强

        //第一个参数 ClassLoader loader,被代理的对象的类加载器
        ClassLoader classLoader = dinner.getClass().getClassLoader();
        //第二个参数 Class<?>[] interfaces,被代理对象所实现的所有接口
        Class[] interaces = dinner.getClass().getInterfaces();
        //第三个参数 InvocationHandler h,执行处理器对象,专门用于定义增强的规则
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("ivoke方法执行了");
                // Object proxy, 代理对象

                // Method method,被代理的方法
                //System.out.println(method.getName());
                // Object[] args,被代理方法运行时的实参
                //System.out.println(Arrays.toString(args));
                Object res = null;
                if(method.getName().equals("eat")){
                    System.out.println("饭前洗手");
                    // 让原有的eat的方法去运行
                    res =method.invoke(dinner, args);
                    System.out.println("饭后刷碗");
                }else{
                    // 如果是其他方法,那么正常执行就可以了
                    res =method.invoke(dinner, args);
                }
                return res;
            }
        };


    Dinner dinnerproxy = (Dinner) Proxy.newProxyInstance(classLoader, interaces, handler);

        dinnerproxy.eat("包子");
        dinnerproxy.drink();
    }

}
//实现InvocationHandler接口(可以不用自己实现InvocationHandler接口)
/*class MyInvocationHandler implements InvocationHandler{
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}*/


//接口
interface Dinner{
    void eat(String foodName);
    void drink();
}

//Person类
class Person implements Dinner{
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public void eat(String foodName) {
        System.out.println(name+"正在吃"+foodName);
    }

    @Override
    public void drink() {
        System.out.println(name+"正在喝茶");
    }
}

//Student类
class Student implements Dinner {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void eat(String foodName) {
        System.out.println(name + "正在食堂吃" + foodName);
    }

    @Override
    public void drink() {
        System.out.println(name+"正在喝可乐");
    }
}

使用代理技术 获得代理对象 代替张三 增强打官司的方法

总结
1、在不修改原有代码的 或者没有办法修改原有代码的情况下 增强对象功能 使用代理对象 代替原来的对象去完成功能
进而达到拓展功能的目的
2、JDK Proxy 动态代理面向接口的动态代理 一定要有接口和实现类的存在 代理对象增强的是实现类 在实现接口的方法重写的方法
生成的代理对象只能转换成 接口的不能转换成 被代理类
代理对象只能增强接口中定义的方法 实现类中其他和接口无关的方法是无法增强的
代理对象只能读取到接口中方法上的注解 不能读取到实现类方法上的注解

2)cglib 动态代理

proxy 动态代理
面向接口
1必须有接口和实现类
2增强接口中定义的方法
3只能读取接口中方法的上注解

cglib动态代理模式 面向父类
在这里插入图片描述
导入依赖

<!--spring核心容器包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.5</version>
        </dependency>
 <!--Junit单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

代码:

public class Test01 {
    @Test
    public void testCglib(){
        Person person = new Person();
        // 获取一个Person的代理对象
        // 1 获得一个Enhancer对象
        Enhancer enhancer=new Enhancer();
        // 2 设置父类字节码
        enhancer.setSuperclass(person.getClass());
        // 3 获取MethodIntercepter对象 用于定义增强规则
        MethodInterceptor methodInterceptor=new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                /*Object o,  生成之后的代理对象 personProxy
                Method method,  父类中原本要执行的方法  Person>>> eat()
                Object[] objects, 方法在调用时传入的实参数组
                MethodProxy methodProxy  子类中重写父类的方法 personProxy >>> eat()
                */
                Object res =null;
                if(method.getName().equals("eat")){
                    // 如果是eat方法 则增强并运行
                    System.out.println("饭前洗手");
                    res=methodProxy.invokeSuper(o,objects);
                    System.out.println("饭后刷碗");
                }else{
                    // 如果是其他方法 不增强运行
                    res=methodProxy.invokeSuper(o,objects); // 子类对象方法在执行,默认会调用父类对应被重写的方法
                }
                return res;
            }
        };
        // 4 设置methodInterceptor
        enhancer.setCallback(methodInterceptor);
        // 5 获得代理对象
        Person personProxy = (Person)enhancer.create();
        // 6 使用代理对象完成功能
        personProxy.eat("包子");
    }
}
class Person  {
    public Person() {
    }
    public void eat(String foodName) {
        System.out.println("小明正在吃"+foodName);
    }
}
//静态的方法固定的
/*
class Son extends Person{
    public Son(String name){
        super(name);
    }

    @Override
    public void eat(String foodName) {
        System.out.println("洗手");
        super.eat(foodName);
        System.out.println("刷碗");
    }
}*/

AOP

1、AOP术语解析

AOP (面向切面编程)
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现 日志处理,权限控制,性能检测,事务控制等
AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理

AOP中的术语辨析

1 连接点 Joint point:
类里面那些可以被增强的方法,这些方法称之为连接点
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

2 切入点 Pointcut:
实际被增强的方法,称之为切入点
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方

3 通知 Advice:
实际增强的逻辑部分称为通知 (增加的功能)
Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
通知类型: 1 前置通知 2 后置通知 3 环绕通知 4 异常通知 5 最终通知

4 目标对象 Target:
被增强功能的对象(被代理的对象)
织入 Advice 的目标对象

5 切面Aspect:
表现为功能相关的一些advice方法放在一起声明成的一个Java类
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

6 织入 Weaving:
创建代理对象并实现功能增强的声明并运行过程
将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

2、AOP项目准备

AspectJ本身并不是spring框架中的组成部分, 是一个独立的AOP框架,一般把AspectJ和Spring框架的AOP依赖一起使用,所以要导入一个独立的依赖

实现的两种方式
1 基于注解方式实现 (熟练)
2 基于XML配置方式 (了解)

准备工作
导入依赖

   <packaging>jar</packaging>
    <!--pom.xml中导入spring依赖jar-->
    <dependencies>
        <!--spring核心容器包 context上下文,容器  只需导入他就可以beans、core、spel不需要导入-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.5</version>
        </dependency>
            <!--spring切面包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>5.3.5</version>
            </dependency>
            <!--织入包  spring-aspects 已经导入该包,这里可以不导入-->
            <!--<dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.6</version>
            </dependency>-->
            <!--aop联盟包-->
            <dependency>
                <groupId>aopalliance</groupId>
                <artifactId>aopalliance</artifactId>
                <version>1.0</version>
            </dependency>
            <!--Apache Commons日志包-->
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
            <!--德鲁伊连接池-->
            <!--<dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.10</version>
            </dependency>-->
            <!--mysql驱动-->
           <!-- <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.22</version>
            </dependency>-->
            <!--junit单元测试测试包-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.1</version>
                <scope>test</scope>
            </dependency>
            <!--lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
                <scope>provided</scope>
            </dependency>
    </dependencies>

准备工作
切入点表达式: 通过一个表达式来确定AOP要增强的是哪个或者那些方法
语法结构:execution([权限修饰符][返回值类型][类的全路径名][方法名](参数 列表) )
例子1
execution(* com.msb.dao.UserDaoImpl.add(…)) //指定切点为UserDaoImpl.add方法
execution(* com.msb.dao.UserDaoImpl.(…)) //指定切点为UserDaoImpl.所有的方法
execution(
com.msb.dao..(…)) //指定切点为dao包下所有的类中的所有的方法
execution(* com.msb.dao..add(…)) // 指定切点为dao包下所有的类中的add的方法
execution(
com.msb.dao..add(…)) // 指定切点为dao包下所有的类中的add开头的方法
在这里插入图片描述
基于注解方式实现
项目结构
在这里插入图片描述

开启注解扫描和AOP切面编程自动生成代理对象配置

<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <!--spring 包扫描  -->
    <context:component-scan base-package="com.wml"/>
    <!--aop autoProxy 自动生成代理对象 -->
    <aop:aspectj-autoproxy/>
</beans>

准备接口
UserDao和EmpDao

public interface UserDao {
    int addUser(Integer userid,String username);
}
public interface EmpDao {
    int addEmp(Integer empno,String ename,String job);
}

接口实现类UserDaoImpl和EmpDaoImpl

@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public int addUser(Integer userid, String username) {
        System.out.println("UserDaoImpl add... ...");
        //制造异常
        //int i = 1/0;
        return 1;
    }
}
@Repository
public class EmpDaoImpl implements EmpDao {
    @Override
    public int addEmp(Integer empno, String ename, String job) {
        System.out.println("EmpDaoImpl add... ...");
        return 1;
    }
}

准备接口
UserService和EmpService

public interface UserService {
    int addUser(int userid,String username);
}
public interface EmpService {
        int addEmp(Integer empno,String ename,String job);
}

接口实现类UserServiceImpl和EmpServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public int addUser(int userid, String username) {
        System.out.println("UserServiceImpl add... ...");
        int rows = userDao.addUser(userid,username);
        return rows;
    }
}
@Service
public class EmpServiceImpl implements EmpService {
   private EmpDao empDao;
    @Override
    public int addEmp(Integer empno, String ename, String job) {
        System.out.println("EmpServiceImpl add... ...");
        int rows = empDao.addEmp(empno,ename,job);
        return rows;
    }
}

准备切面

@Aspect//切面声明,告诉spring这是一个切面
@Component//spring初始化
@Order(2)
public class DaoAspect01 {

    //定义一个公共切点,切点表达式execution(* com.wml.Dao.Impl.UserDaoImpl.addUser(..)
    //固定的写法(只增强了UserDaoImpl中的addUser方法)>>>>实现类的路径
    /*@Pointcut("execution(* com.wml.Dao.Impl.UserDaoImpl.addUser(..))")
    public void addPointCut(){};*/
    //固定的写法(只增强了UserDao中的addUser方法)>>>>切点表达式直接指向接口
    /*@Pointcut("execution(* com.wml.Dao.UserDao.addUser(..))")
    public void addPointCut(){};*/
    //增强Dao层中的所有add方法
    @Pointcut("execution(* com.wml.Dao.*.add*(..))")
    public void addPointCut(){};
    /*
     * 前置通知: 切点方法执行之前先执行的功能
     * 参数列表可以用JoinPoint接收切点对象
     * 可以获取方法执行的参数
     * */

    @Before("addPointCut()")
    public void methodBefore(JoinPoint joinPoint){
        System.out.println("methodBefore1 invoked... ...");
        //获取目标切点方法运行的时候传入的参数
        Object[] args = joinPoint.getArgs();
        System.out.println("args"+ Arrays.toString(args));
    }

    /*
     * 后置通知:方法执行之后要增强的功能
     * 无论切点方法是否出现异常都会执行的方法  最终通知
     * 参数列表可以用JoinPoint接收切点对象
     * */

    @After("addPointCut()")
    public void methodAfter(JoinPoint joinPoint){
        System.out.println("methodAfter1 invoked... ...");
    }

    /*
     * 返回通知:切点方法正常运行结束后增强的功能
     * 如果方法运行过程中出现异常,则该功能不运行
     * 参数列表可以用 JoinPoint joinPoint接收切点对象
     * 可以用Object res接收切点方法返回值,需要用returning指定返回值名称
     * */

    @AfterReturning( value = "addPointCut()",returning = "res")
    public void methodAfterReturning(JoinPoint joinPoint,Object res){
        System.out.println("methodAfterReturning1 invoked... ...");
        System.out.println(res);
    }

    /*
     * 异常通知:切点方法出现异常时运行的增强功能,不出现不运行
     * 可以接受切点方法抛出的异常对象
     * 如果方法运行没有出现异常,则该功能不运行
     * 参数列表可以用Exception ex接
     * 收异常对象 需要通过throwing指定异常名称
     * */

    @AfterThrowing( value = "addPointCut()",throwing = "ex")
    public void methodAfterThrowing(Exception ex){
        System.out.println("methodAfterThrowing1 invoked... ...");
        System.out.println(ex.getMessage());
    }

    /*环绕通知:在切点方法之前和之后都进行功能的增强
     * (参数列表需要带上一个特殊的形参ProceedingJoinPoint 代表我们的切点
     * 通过proceedingJoinPoint手动控制切点方法执行的位置
     * 环绕通知的返回值必须是object 在环绕通知中必须要将切点方法继续向上返回)
     * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能
     * 方法列表可以通过ProceedingJoinPoint获取执行的切点
     * 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置
     * proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理
     * 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值
     * */

    @Around("addPointCut()")
    public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("methodAroundA1 invoked... ...");
        Object proceed = proceedingJoinPoint.proceed();//控制切点方法在这里执行
        System.out.println("methodAroundB1 invoked... ...");
        return proceed;
    }
}

测试代码

public class Test01 {
    @Test
    public void getBeanBefore01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserService userservice = context.getBean(UserService.class);//通过类型
        userservice.addUser(1,"小明");
    }

    @Test
    public void getBeanBefore02(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserDao userdao = context.getBean(UserDao.class);//通过类型
        userdao.addUser(1,"小明");
        //通过反射获取类名
        System.out.println(userdao.getClass().getSimpleName());
    }
    @Test
    public void getBeanAfter03(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserDao userdao = context.getBean(UserDao.class);//通过类型
        userdao.addUser(1,"小红");
    }
    @Test
    public void getBeanAfterReturning04(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserDao userdao = context.getBean(UserDao.class);//通过类型
        userdao.addUser(1,"小新");
    }
    @Test
    public void getBeanAfterThrowing05(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserDao userdao = context.getBean(UserDao.class);//通过类型
        userdao.addUser(1,"小刚");
    }
    @Test
    public void getBeanAround06(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserDao userdao = context.getBean(UserDao.class);//通过类型
        int rows = userdao.addUser(1, "小北");
        System.out.println(rows);
    }
    @Test
    public void getBean(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        EmpDao empdao = context.getBean(EmpDao.class);//通过类型
        int rows = empdao.addEmp(10, "TOM","SALESMAN");
        System.out.println(rows);
    }
}

在这里插入图片描述
在这里插入图片描述
有多个增强类对同一个方法进行增强,通过@Order注解设置增强类优先级=
数字越小,优先级越高
数字越小,其代理位置越靠近注入位置
在这里插入图片描述
在这里插入图片描述
完全使用注解开发
创建配置类

Configuration//配置类
@ComponentScan("com.wml")//包扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//使用自动切面代理对象的的生成
public class SpringConfig {
}

测试代码

public class Test01 {
    @Test
    public void getBeanSpringConfig(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userservice = context.getBean(UserService.class);//通过类型
        userservice.addUser(1,"小明");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值