@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()。。。
这时候,就进入了循环。
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();
}
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 保存图书 .........");
}
}
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();
}
}
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。
注意:这个类上面并没有那四大注解。
public class BaseService<T>{
@Autowired
private BaseDao<T> baseDao;
public void save(){
baseDao.save();
}
}
之前的BaseDao也是没有注解的。
UserService:
@Service
public class UserService extends BaseService<User>{
}
@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是一个容器,它帮我们管理组件。
DI:依赖注入。哪些组件需要另外一些组件,只需要一个注解@Autowired,进行自动赋值。
要想使用Spring,你先成为Spring的会员。(也就是你这个组件要先加入到容器里面去)
1、容器在启动的时候,都会自动创建所有的单实例的对象
2、在Autowired自动装配的时候,它是从容器里面去找那些符合要求的bean
3、使用代码:
ioc.getBean();
的形式获取bean的时候,其实也是从容器里面去找这个bean
4、容器里面包含了所有的bean
调试Spring的源码。查看容器的本质到底是什么?
本质就是Map。
这个Map对象,保存了所有创建好的bean,并给外界提供获取的功能。
探索:
单实例的bean保存到哪个map里面?
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);
}
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;
}
}
public class AOPTest {
@Test
public void test(){
Calculator calculator = new MyMathCalculator();
int result = calculator.add(1, 2);
System.out.println(result);
}
}
开始加日志记录了:
之前的方法:
现在的方法:
每个方法里面都加了这个日志的打印。
发现,这种方式(每个方法内部加上日志打印。)添加日志是很不好的,维护起来非常的麻烦。
我们的加减乘除才是核心功能。
日志功能只是系统的辅助性的功能。
我们上一种方式,就是将核心功能和辅助功能耦合了。
我们希望:
核心功能不变,但是辅助功能是在核心功能运行期间,动态的加上。
回到这里例子就是:
执行加减乘除的时候,才会加上日志的功能。
怎么实现呢?
思想:
我们的加减乘除的方法,不要自己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)
之前我们就说过,希望在执行核心方法的前后,来执行辅助功能。
现在我们就在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;
}
};
现在真正方法的定义,就没有写日志了。
之前:
现在:
测试动态代理:
测试结果:
这个时候就发现,核心功能(加减乘除和日志打印已经解耦了)。
可以使用动态代理来将日志代码动态的写在核心方法执行的前后。
但是,我们发现,虽然动态代理很强大,但是写起来好难。
当然,这个动态代理技术最大的缺陷就是:
如果目标对象没有实现任何接口