Spring的简述
-
Spring 是一个轻量级的框架(依赖少,消耗资源少)
-
Spring是分层的架构,也就是y一个分层的JavaEE Full Stack(一站式)的轻量级开源框架
也就是在经典三层(web , service , dao 层都有相应的技术解决方案)
-
web : springMVC
service:spring (可以做事务管理,切面编程AOP)
dao:hibernate,mybatis,jdbcTemplate , spring-data
Spring的优点
-
方便解耦,简化开发
(高内聚,低耦合)
比如分层的时候,dao层的所有技术都放在一起,就是高内聚
低耦合:不同的技术之间,能不调用就不调用 (比如Spring的IOC)
让不同的层之间形成低耦合
Spring的模块
Spring的Maven依赖导入
注意:beans里面依赖包含了core,core包含了commons-logging
IOC
Inverse Of Control 控制反转
简单来说:以前是自己New一个对象实例出来,现在是由Spring容器提供一个对象实例给自己用。
配置文件:
Spring配置文件是有xml约束模板的,去创建或者复制即可
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置Service-->
<bean id="userService" class="com.jzp.a_ioc.UserServiceImpl"></bean>
</beans>
测试IOC
@Test
public void demo01(){
//这是之前的开发
UserServiceImpl userService = new UserServiceImpl();
userService.addUser();
}
@Test
public void demo02(){
//spring IOC提供实例
//1. 加载Spring配置的xml文件,获取Spring容器
String xml = "applicationContext.xml";
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(xml);
UserService userService = (UserService) ac.getBean("userService");
userService.addUser();
}
问题:
Spring内部是通过什么原理实现的IOC。在xml文件中配置了全限定类名,然后在工厂里根据类名,通过反射生产出实例。
DI
Dependency Inject 依赖注入
什么是依赖,什么是注入 就是如
public class B{
private A a; // 称B依赖于A
public void setA(A a){
B.a = a; //通过setter方法进行注入,称为注入
}
}
之前的开发:
public class BookServiceImpl{
//以前的开发 是Service与Dao耦合, 只要Dao的实现类一旦发生改变,那么这里也要改变,重新修改new的 实例名称
//解决办法:自定义工厂,通过工厂提供
private BookDao bookDao = new BookDaoImpl();
//spring的DI解决之后(Service实现类,使用Dao接口,不知道具体的方法)
}
模拟Spring DI的执行过程
BookService bookService = new BookServiceImpl(); --->IoC
BookDao bookDao = new BookDaoImpl(); --->IoC
bookService.setBookDao(bookDao); --->DI
BeanFactory和ApplicationContext的区别
后者是前者的子接口
后者功能更完善,且不会延迟加载(也就是第一次调用getBean()时才会实例化这个Bean),
而后者是当Context(配置文件)加载完成的时候,就会立即实例化所有的Bean
测试结果证明BeanFactory的延迟加载
装配Bean基于XML
Bean实例化方式
-
默认构造
-
静态工厂 :
Spring本身就有工厂,为什么还要静态工厂呢?
答:静态工厂实例化Bean是用来整合其他框架常用的技术
这就说到了单例和多例,就说到了Servlet是单例的,Structs2是多例的
Servlet为什么是单例的: https://blog.csdn.net/wgyscsf/article/details/50129367?ref=myread
注意:在例子中,我们只是模拟了一下静态工厂是如何把实例交给Spring去管理的。
但是实际开发中,我们的工厂注入实例给Spring的应用场景是:
我们通过调用别人的工厂(在class中写需要的工厂的全限定类名+生产的实例方法) ,去注入给Spring
普通工厂
就是不用静态方法,通过生产工厂Bean,再通过工厂Bean里的工厂方法实例化对象
public class MyBeanFactory {
//通过简单工厂(非静态工厂)创建实例
//也就是可以创建多个工厂实例
public UserService createService(){
return new UserServiceImpl();
}
}
<!--创建工厂Bean-->
<bean id="MyBeanFactory" class="com.jzp.c_inject.c_factory.MyBeanFactory"></bean>
<!--通过上面注册的工厂Bean,获取实例-->
<bean id="userService" factory-bean="MyBeanFactory" factory-method="createService"></bean>
Bean的种类
-
普通Bean
-
FactoryBean : 这是一个接口,是一个特殊的Bean,具有工厂生产对象的能力,只能生产特定的对象
有很多实现类,此接口提供getObject()方法返回对象实例
API文档
比如ProxyFactoryBean: 用于生产代理对象的Bean
FactoryBean和BeanFactory的区别?
BeanFactory: 是一个工厂,用来生产任意Bean
而FactoryBean:是一个特殊的Bean, 可以生产出特定的Bean出来
方法为:
Bean的作用域
UserService userService = ac.getBean("userService", UserService.class);
UserService userService2 = ac.getBean("userService", UserService.class);
<!--创建实例-->
<bean id="userService" class="com.jzp.d_scope.UserServiceImpl" scope="prototype"></bean>
Bean生命周期:
Bean的初始化与销毁
public void demo01() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String xmlPath = "applicationContext_f_lifecycle.xml";
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = ac.getBean("userService", UserService.class);
//测试销毁方法
//通过反射获取到其子类或者父类的close()方法,并使用
ac.getClass().getMethod("close").invoke(ac);
}
<bean id="userService" class="com.jzp.e_lifecycle.UserServiceImpl" init-method="myInit" destroy-method="myDestroy"></bean>
后处理Bean
也就是BeanPostProcessor接口,只要实现了这个接口,并注册这个Bean给Spring容器
<!--注册后处理Bean-->
<bean class="com.jzp.e_lifecycle.MyBeanPostProcessor"></bean>
后处理Bean的编写
public class MyBeanPostProcessor implements BeanPostProcessor {
//导入源码后,才会人性化一点
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("前方法");
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后方法");
return bean;
}
}
结果:
后处理Bean的原理:
A a = new A();
a = B.before(a); //用钩子B取出a ,做初始化之前的处理方法,并返回一个新的Bean对象(也可以是原来的)
a.init();
a = B.after(a); //用钩子取出a,做初始化之后的处理方法,并返回一个新的或原来的Bean
在这里返回a的JDK代理对象
为什么在后处理Bean的after()方法里返回动态代理对象呢,而不是在init()里呢?
答案:因为JDK代理对象是根据接口进行反射的,如果在init()里返回JDK代理对象,因为JDK代理对象
内部是只有业务接口方法的(而没有在UserServiceImpl实现类里的init()这些方法),所以a.init() [此处a为JDK代理对象] 永远无法执行
正确做法:在after()方法里再返回JDK代理对象
//在这里开启事务
a.addUser(); //一般我们会在这个业务方法执行之前,可以加事务控制,所以可以在此用JDK代理对象开启事务,
关闭事务
目的: 能够对这个Bean的方法进行动态代理,在前后做相应的如事务处理,或者性能监控
//在这里关闭事务
a.destroy()
代理类源代码:
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
System.out.println("后方法"+beanName);
//返回JDK代理对象
//目的: 能够对这个Bean的方法进行动态代理,在前后做相应的如事务处理,或者性能监控
return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("addUser")){
System.out.println("开启事务-------");
//生成代理对象
Object object = method.invoke(bean, args);
System.out.println("关闭事务-------");
//返回代理对象
return object;
}
return null;
}
});
}
问题:
getClassLoader(),顺便复习JMM内存管理, 和动态代理的具体参数的含义和内部原理
后处理Bean如何只作用于一个:
在后处理Bean实现类里的方法参数中的beanName,然后判断beanName.equals("我们想要处理的Bean名字"),即可
否则后处理Bean里的方法对所有Bean有效
动态代理的只作用于指定的method,也是同理的实现,method.getName().equals("addUser")
记住:后处理Bean必须是单例的
属性依赖注入
分为 手动装配 和 自动装配
手动装配:一般是根据XML配置文件,或者注解
- 基于XML装配,构造方法,setter方法
- 基于注解装配
自动装配: 就是根据一定的约束自动装配
- byType: 按类型装配
- byName:按名称装配
- constructor 构造装配
- autodetect: 不确定装配
构造函数注入
public class User {
private Integer uid;
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public User(Integer uid, String name) {
this.uid = uid;
this.name = name;
}
<!--编译器报错:没有默认的构造函数可以使用,因为我在User里写了带参构造函数-->
<!--所以要实验:构造函数的注入-->
<bean id="user" class="com.jzp.f_xml.a.constructor.User">
<constructor-arg name="name" value="zhanp"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
</bean>
第二种:
<bean id="user" class="com.jzp.f_xml.a.constructor.User">
<!--index为索引,type为当多个构造方法匹配时,如何区分注入的属性类型-->
<constructor-arg index="0" type="java.lang.String" value="zhanp"></constructor-arg>
<constructor-arg index="1" type="java.lang.Integer" value="20"></constructor-arg>
</bean>
装配Bean 基于注解
注解:就是一个类,使用@注解名称
开发中:使用注解 取代 xml配置文件。
-
@Component取代
@Component("id") 取代
2.web开发,提供3个@Component注解衍生注解(功能一样)取代
@Repository :dao层
@Service:service层
@Controller:web层
3.依赖注入 ,给私有字段设置,也可以给setter方法设置
普通值:@Value("")
引用值:
方式1:按照【类型】注入
@Autowired
方式2:按照【名称】注入1
@Autowired
@Qualifier("名称")
方式3:按照【名称】注入2
@Resource("名称")
4.生命周期
初始化:@PostConstruct
销毁:@PreDestroy
5.作用域
@Scope("prototype") 多例注解使用前提,添加命名空间,让spring扫描含有注解类
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!-- 组件扫描,扫描含有注解的类 -->
<context:component-scan base-package="com.itheima.g_annotation.a_ioc"> </context:component-scan>
</beans>