学习Spring的第4天

学习Spring的第4天

@Resource
@Autowired
区别:
1、@Autowired是Spring自己的注解,最强大
2、@Resource 是J2EE的。java的标准

因为@Resource是标准,所以它的扩展性很强。即使你的容器用的不是Spring。切换为另一个框架,@Resource照样适用。

@Autowired是Spring自己的注解,所以@Autowired离开了Spring就没办法使用了。

注意地方:
1、你想要使用@Autowired自动装配,那你就先要把一些组件带上那四大注解中的某一个才行。(先要成为人家的会员)
在这里插入图片描述
在这里插入图片描述
注意二:
在这里插入图片描述
在这里插入图片描述
原因:是因为:
在这里插入图片描述

这个类IOTest上面没有加上四大注解中的一个。
它都没有成为Spring的会员。
我们说:框架会自动去扫描基础包下面的,标了四大注解的那些类。

还有一种死循环的情况:
在这里插入图片描述
为什么会死循环呢?
执行步骤:
1、你run test()方法。就会去new 一个IOTest对象
2、只要去new IOTest(),就会执行:

    ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc4.xml");

这句话。
一旦执行这句话。就会扫描到IOTest这个类。因为,这个类的上面是@Component
3、扫描了之后,就会去new IOTest()。。。
这时候,就进入了循环。

如何使用Spring的单元测试呢?

1、导包:
spring-test-4.0.0.RELEASE在这里插入图片描述
为什么我们要使用Spring的单元测试呢?不是有junit单元测试吗?
在这里插入图片描述
使用Junit的单元测试,发现bookService不能赋值。因为IOTest类上没有加上四大注解的一个。
如果给IOTest类加上注解,又会出现死循环执行的情况。
所以,这种情况为了给bookService成功赋值上去,只能使用Spring的单元测试了,Junit的单元测试是不行的。
在这里插入图片描述
在这里插入图片描述

解释:
其中@ContextConfiguration(locations = "classpath:ioc4.xml")使用这个来指定Spring的配置文件的位置。
@RunWith(SpringJUnit4ClassRunner.class)指定用哪种驱动进行单元测试,默认就是junit,加上这个SpringJUnit4ClassRunner.class,就把Junit测试换成了Spring的单元测试。之前的@Test都是由Junit来执行。



@ContextConfiguration(locations = "classpath:ioc4.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class IOCTest {
    ApplicationContext ioc = null;
    @Autowired
    private BookService bookService;

    @Test
    public void test(){
        System.out.println("bookService"+bookService);
    }
}

使用Spring的单元测试的好处,其实就是,我们不用ioc.getBean()来获取组件了,直接Autowired获取,Spring为我们自动装配。

测试泛型的依赖注入。(难但是重要)

为什么会引入泛型依赖注入呢?
首先:看一下原先的代码架构。
在DAO层,有三个,分别是BaseDao,BookDao,UserDao
BaseDao:

package com.rtl.dao;

//这个类里面定义了一些基本的增删改查的方法。
public abstract class BaseDao<T> {
    public abstract void save();
}

在这里插入图片描述
2、BookDao:

package com.rtl.dao;

import com.rtl.bean.Book;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao extends BaseDao<Book> {


    @Override
    public void save() {
        System.out.println("BookDao   保存图书 .........");

    }
}

在这里插入图片描述
3、UserDao:

package com.rtl.dao;

import com.rtl.bean.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao extends BaseDao<User>{
    @Override
    public void save() {
        System.out.println("UserDao  保存用户.....");
    }
}

在这里插入图片描述
在Service层里面有两个:BookService和UserService

BookService:

package com.rtl.service;

import com.rtl.dao.BookDao;
import com.rtl.servlet.BookServlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class BookService {

    @Resource
    private BookDao bookDao;

    public void save(){
        bookDao.save();
    }

}

在这里插入图片描述
UserService:

package com.rtl.service;

import com.rtl.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    public void save(){
        userDao.save();
    }
}

在这里插入图片描述
测试类:
在这里插入图片描述
测试结果:
在这里插入图片描述

改进之后的代码:
添加了一个BaseService
然后,UserService和BookService都继承了BaseService。

BaseService:这是一个很关键的类。

注意:这个类上面并没有那四大注解。

public class BaseService<T>{
    @Autowired
    private BaseDao<T> baseDao;
    public void save(){
        baseDao.save();
    }
}

在这里插入图片描述
之前的BaseDao也是没有注解的。
在这里插入图片描述
UserService:

@Service
public class UserService extends BaseService<User>{
}

在这里插入图片描述
BookService:

@Service
public class BookService extends BaseService<Book>{
}

在这里插入图片描述
测试:

在这里插入图片描述
在这里插入图片描述

问:在BaseService类里面,它的类上面并没有添加注解,为什么baseDaoa对象不是空的呢?

在这里插入图片描述
答:
因为在BookService这个类继承了BaseService。
而BookService加了@Service注解。
在这里插入图片描述
继承了之后,相当于这块代码就在BookService里面了。
在这里插入图片描述
在这里插入图片描述
所以,baseDao就不是null。

奇怪的事情:
这个baseDao不是null,但是你写代码的时候,idea会说你是null。但是你又可以运行。很诡异。!!!
在这里插入图片描述

泛型依赖注入的原理:

在这里插入图片描述
结论就是:
之前,我们在进行注入匹配的时候:
1、按照数据类型去匹配
2、相同的就按照属性名作为id去匹配。
现在又多了一种:就是看泛型。

比如:
按照数据类型是BaseDao去匹配。那么有两个:
一个是BaseDao
另一个是BaseDao
这个时候的参考依据就是看你的泛型类型是什么来决定。

IOC总结:

IOC是一个容器,它帮我们管理组件。
DI:依赖注入。哪些组件需要另外一些组件,只需要一个注解@Autowired,进行自动赋值。
要想使用Spring,你先成为Spring的会员。(也就是你这个组件要先加入到容器里面去)

1、容器在启动的时候,都会自动创建所有的单实例的对象
2、在Autowired自动装配的时候,它是从容器里面去找那些符合要求的bean
3、使用代码:

ioc.getBean();

的形式获取bean的时候,其实也是从容器里面去找这个bean
4、容器里面包含了所有的bean

调试Spring的源码。查看容器的本质到底是什么?
本质就是Map。
这个Map对象,保存了所有创建好的bean,并给外界提供获取的功能。
探索:
单实例的bean保存到哪个map里面?

AOP:

Aspect Oriented Programming
面向切面编程

OOP:
Object Oriented Programming
面向对象编程

面向切面编程,是基于OOP基础之上的编程思想。

面向切面编程的意思:

在程序运行期间,将某段代码,动态的切入到 指定方法 的 指定位置
进行运行
举例子:
1、建一个接口,里面有加减乘除的方法。

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

在这里插入图片描述
2、实现类:

package com.rtl.impl;

import com.rtl.inter.Calculator;

public class MyMathCalculator implements Calculator {

    @Override
    public int add(int i, int j) {
        int result = i + j ; 
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j ;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j ;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j ;
        return result;
    }
}

在这里插入图片描述
3、写测试类(没有日志记录的)

public class AOPTest {
    @Test
    public void test(){
       Calculator calculator = new MyMathCalculator();
        int result = calculator.add(1, 2);
        System.out.println(result);
    }
}

在这里插入图片描述

改进:

开始加日志记录了:

第一种方式:每个方法内部加上日志打印。

之前的方法:
在这里插入图片描述
现在的方法:
在这里插入图片描述
每个方法里面都加了这个日志的打印。
在这里插入图片描述
发现,这种方式(每个方法内部加上日志打印。)添加日志是很不好的,维护起来非常的麻烦。

第2种方式:动态代理。

我们的加减乘除才是核心功能。
日志功能只是系统的辅助性的功能。
我们上一种方式,就是将核心功能和辅助功能耦合了。
我们希望:
核心功能不变,但是辅助功能是在核心功能运行期间,动态的加上。

回到这里例子就是:
执行加减乘除的时候,才会加上日志的功能。
怎么实现呢?

动态代理

思想:
我们的加减乘除的方法,不要自己new 对象(MyMathCalculator)然后用对象.的形式调用add()。
在这里插入图片描述
而是想办法使用代理对象去执行加减乘除。
这时候,代理对象会帮我们调用加减乘除。而且在代理对象调用加减乘除的前后帮我们做一些事情。

例如:
Caculator proxy = CaculatorProxy.getProxy(Caculator);
proxy.add(1,2);

那么问题来了:
如何制作CaculatorProxy呢?
这个CaculatorProxy是一个帮Caculator生成代理对象的类。
JDK里面支持动态代理
java.lang.reflect.Proxy
在这里插入图片描述
在这里插入图片描述

这个类里面有一个方法是:
在这里插入图片描述

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

在这里插入图片描述
我们现在就把日志写在invoke()里面。

之前我们就说过,希望在执行核心方法的前后,来执行辅助功能。
现在我们就在method.invoke()这个前后写上日志的打印。

实现动态代理:

1、写好CalculatorProxy这个类。里面有个getProxy()用来获取它的代理类对象。(重要!!!)

package com.rtl.proxy;

import com.rtl.inter.Calculator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

//这个类就是Calculator的代理对象,Calculator是被代理对象。
public class CalculatorProxy {

    //这个方法传入的参数是被代理对象,返回代理对象。
    public static Calculator getProxy(final Calculator calculator){
        ClassLoader loader = calculator.getClass().getClassLoader();
        Class<?>[] interfaces = calculator.getClass().getInterfaces() ;
        InvocationHandler h = new InvocationHandler() {
            //在我们new InvocationHandler的时候,会自动实现方法invoke(),里面有三个参数:
                //参数1:Object proxy。这个是代理对象,这个是给JDK使用的,我们任何时候不要动这个对象
                //参数2:Method method:当前将要执行的目标方法。利用反射执行目标方法(自己定义的加减乘除)。
                //参数3:Object[] args:外界调用加减乘除的时候传入的参数值。
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("【"+method.getName()+"】方法开始执行,使用的参数列表是:【"+ Arrays.asList(args)+"】");
                // method.invoke()里面传入了两个参数值
                //1、calculator:我们就是为了它创建代理,所以,就是执行这个对象的加减乘除方法。注意要在前面声明参数的时候加上final关键字
                Object result = method.invoke(calculator, args);
                System.out.println("【"+method.getName()+"】方法执行完成,它的计算结果是:"+result);
                return result;
            }
        };
        //参数一:ClassLoader loader:它是被代理对象Calculator的类加载器 calculator.getClass().getClassLoader()
        //参数2:Class<?>[] interfaces:他是被代理对象Calculator实现了哪些接口。calculator.getClass().getInterfaces()
        //参数3:这个参数是最重要的。方法执行器,帮我们的目标对象(代理对象)执行目标方法(自己定义的加减乘除)。直接new出来。

        Object proxy = Proxy.newProxyInstance(loader, interfaces, h);

        return (Calculator) proxy;
    }

}

注意:
1、这个类(CalculatorProxy )就是Calculator的代理对象,Calculator是被代理对象。
2、写好getProxy()
在这里插入图片描述
在这里插入图片描述
其中再写getProxy方法的时候,里面有个参数new的时候,会出现匿名内部类形式。
这里面要自定义invoke方法。
invoke()方法的里面就执行了核心功能,也可以在核心功能执行前后执行一些辅助功能。

InvocationHandler h = new InvocationHandler() {
            //在我们new InvocationHandler的时候,会自动实现方法invoke(),里面有三个参数:
                //参数1:Object proxy。这个是代理对象,这个是给JDK使用的,我们任何时候不要动这个对象
                //参数2:Method method:当前将要执行的目标方法。利用反射执行目标方法(自己定义的加减乘除)。
                //参数3:Object[] args:外界调用加减乘除的时候传入的参数值。
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("【"+method.getName()+"】方法开始执行,使用的参数列表是:【"+ Arrays.asList(args)+"】");
                // method.invoke()里面传入了两个参数值
                //1、calculator:我们就是为了它创建代理,所以,就是执行这个对象的加减乘除方法。注意要在前面声明参数的时候加上final关键字
                Object result = method.invoke(calculator, args);
                System.out.println("【"+method.getName()+"】方法执行完成,它的计算结果是:"+result);
                return result;
            }
        };

现在真正方法的定义,就没有写日志了。
之前:在这里插入图片描述
现在:
在这里插入图片描述
测试动态代理:
在这里插入图片描述
测试结果:
在这里插入图片描述
在这里插入图片描述
这个时候就发现,核心功能(加减乘除和日志打印已经解耦了)。
在这里插入图片描述
可以使用动态代理来将日志代码动态的写在核心方法执行的前后。

但是,我们发现,虽然动态代理很强大,但是写起来好难。
当然,这个动态代理技术最大的缺陷就是:
如果目标对象没有实现任何接口
在这里插入图片描述

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值