6、依赖注入
6.1、构造器注入
-
使用无参构造创建对象,这是Spring默认的!
-
假设我们要使用有参构造创建对象。
创建实体类:
package com.feng.pojo; public class User { private String name; public User(String name) { this.name = name; } public void show(){ System.out.println(this.name); } }
-
下标赋值(有参构造方法里面参数的下标,从0开始)
<!--通过下标赋值,有参构造方法中参数的下标,从零开始--> <bean id="user" class="com.feng.pojo.User"> <constructor-arg index="0" value="小明"/> </bean>
-
通过类型赋值(有参构造方法中参数的类型)
<!-- 第二种方式:通过类型创建,不建议使用!如果两个参数的类型相同怎么办 --> <bean id="user" class="com.feng.pojo.User"> <constructor-arg type="java.lang.String" value="小红"/> </bean>
-
直接通过参数名来进行设置(掌握这个就够用了)
<!--第三种:直接参数名设置--> <bean id="user" class="com.feng.pojo.User"> <constructor-arg name="name" value="小黄"/> </bean>
-
6.2、Set方式注入【重点 】
- 依赖注入:Set注入!
- 依赖:bean对象的创建依赖于容器!
- 注入:bean对象中的所有的属性,由容器来注入!
-
实体类
package com.feng.pojo; import java.util.*; public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address.toString() + ", books=" + Arrays.toString(books) + ", hobbys=" + hobbys + ", card=" + card + ", games=" + games + ", wife='" + wife + '\'' + ", info=" + info + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String[] getBooks() { return books; } public void setBooks(String[] books) { this.books = books; } public List<String> getHobbys() { return hobbys; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public Map<String, String> getCard() { return card; } public void setCard(Map<String, String> card) { this.card = card; } public Set<String> getGames() { return games; } public void setGames(Set<String> games) { this.games = games; } public String getWife() { return wife; } public void setWife(String wife) { this.wife = wife; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } }
package com.feng.pojo; public class Address { private String address; @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
-
beans.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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.feng.pojo.Address"> <property name="address" value="上海"></property> </bean> <bean id="student" class="com.feng.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="address"> </property> <property name="books"> <array > <value >三国演绎</value> <value >西游记</value> <value >西游记</value> </array> </property> <property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> </list> </property> <property name="card"> <map> <entry key="123" value="123456"></entry> <entry key="000" value="00000"></entry> </map> </property> <property name="games"> <set> <value>set1</value> <value>set2</value> </set> </property> <property name="wife" > <null/> </property> <property name="info" > <props > <prop key="name">小明</prop> <prop key="pwd">123456</prop> <prop key="admin">xiaoming</prop> </props> </property> </bean> </beans>
-
测试类
package com.feng.studenttest; import com.feng.pojo.Student; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Mytest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) context.getBean("student"); System.out.println(student.toString()); //Student{ // name='小明', // address=Address{address='上海'}, // books=[三国演绎, 西游记, 西游记], // hobbys=[听歌, 看电影], // card={123=123456, // 000=00000}, // games=[set1, set2], // wife='null', // info={admin=xiaoming, // name=小明, // pwd=123456}} } }
6.3、拓展方式注入
我们可以使用p命名空间和c命名空间进行注入
使用前需要引入关于它的头文件:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
使用:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--这里体现了依赖注入的两种方试:
方式一:set注入
方式二:构造注入
-->
<!--使用p命名空间注入属性,就是 property标签 就是给属性复制,p:属性名=“属性值” -->
<bean id="user" class="com.feng.pojo.User" p:name="小明" p:age="18"/>
<!--使用c命名空间注入,就是constructor-arg标签 前提是必须要有有参构造函数-->
<bean id="user1" class="com.feng.pojo.User" c:name="小红" c:age="16"/>
</beans>
注意点:p命名注入和c命名注入不能直接使用,需要导入xml约束!!
6.4、bean的作用域
-
singleton 单例模式(Spring默认机制 ,所有东西都是单例的)
-
不管你在测试的时候你getbean几次user2,得到的对象地址始终是相等的
<!--你也可以将这个对象显示的定义为单例模式(即全局唯一)--> <bean id="user2" class="com.feng.pojo.User" scope="singleton"/>
-
-
protoype
-
原型模式刚好和单例模式相反,它是你getBean几次user3,他每一次得到的对象是不同的
-
即每次从容器中get的时候,都会产生一个新的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVlJVdty-1629891802080)(C:\Users\江湖烽子\Pictures\Camera Roll\2021-08-25_100858.jpg)]
<!--你也可以将这个对象设置为原型模式--> <bean id="user3" class="com.feng.pojo.User" scope="prototype"/>
-
-
其余的request、session、application、这些个只能在web开发中使用到!
7、Bean的自动装配
- 自动装配是Spring满足bean依赖一种方式!
- Spring会在上下文中自动寻找,并自动给bean(对象)装配属性!
在Spring中有三种装配的方式
- 在XML中显示的配置
- 在Java中显示配置
- 隐式的自动装配bean【重要】
7.1、测试
环境搭建
- 一个人有两个宠物
非自动装配:
<bean id="cat" class="com.feng.pojo.Cat"/>
<bean id="dog" class="com.feng.pojo.Dog"/>
<bean id="people" class="com.feng.pojo.People">
<property name="name" value="小明"/>
<property name="cat" ref="cat"/> //这里现在要自己手动装配
<property name="dog" ref="dog"/>
</bean>
7.2、ByName自动装配
- byName: 会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean 的id
<!--现在我们让他自动去找 cat 和 dog-->
<!--
byName: 会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean 的id
-->
<bean id="people2" class="com.feng.pojo.People" autowire="byName">
<property name="name" value="小航"/>
</bean>
7.3、ByType自动装配
<bean class="com.feng.pojo.Cat"/>
<bean class="com.feng.pojo.Dog"/>
<!--
byType: 会自动在容器上下文中查找,和自己对象属性类型相同的bean,
这里的类型是:com.feng.pojo.Cat
com.feng.pojo.Dog
-->
<bean id="people3" class="com.feng.pojo.People" autowire="byType">
<property name="name" value="小红"/>
</bean>
小结:
- 在byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法后面的值一致!
- byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
7.4、使用注解实现自动装配
jdk1.5支持的注解,Spring2.5就支持注解了!
要使用注解须知:
-
导入约束 context约束
-
配置注解的支持 < context:annotation-config/>
<?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: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/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
@Autowired
- 直接在属性上用即可,也可以在属性的set方法上使用!
- 使用Autowired 我们可以不用编写set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在,且符合名字byName
知识拓展:
@Nullable 字段标记了这个注解,说明这个字段可以为null;
public @interface Autowired {
boolean required() default true;
}
public class People {
//如果显示定义了Autowired的required属性为false,说明这个对象可以为null
@Autowired(required = false)
private Dog dog;
@Autowired
private Cat cat;
private String name;
}
如果一个类要在xml中装配多个对象,那这时 @Autowired一个注解就搞不定了;
<bean id="cat" class="com.feng.pojo.Cat"/>
<bean id="dog" class="com.feng.pojo.Dog"/>
<bean id="cat11" class="com.feng.pojo.Cat"/>
<bean id="dog11" class="com.feng.pojo.Dog"/>
<bean id="people" class="com.feng.pojo.People"></bean>
这时我们可以使用@Qualifier(value=“XXX”)去配和@Autowired的使用,去指定一个唯一的bean对象注入
public class People {
@Autowired()
@Qualifier(value = "dog11")
private Dog dog;
@Autowired
@Qualifier(value = "cat11")
private Cat cat;
private String name;
}
这样就可以了:@Autowired装配上面两个, @Qualifier装配下面两个。
开发中最常用的是@Autowired注解!!!
@Resource注解
public class People {
@Resource(name = "dog2")
private Dog dog;
@Resource
private Cat cat;
private String name;
}
小结:
- @Resource 和 @Autowired 的区别:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired 通过byType的方式实现 ,如果找不到 名字,则通过byName实现
- @Resource 默认通过byName的方式实现,如果找不到 名字,则通过byType实现!如果两个都找不到的情况下就报错,@Resource还可以通过直接给标记的属性指定值来让他找 指定的bean的id
- 执行顺序不同:@Autowired 通过byType的方式实现,@Resource 先默认通过byName的方式实现
8、使用注解开发
在Spring4之后,要使用注解开发,必须要保证 aop 的包导入了
-
使用注解需要导入context约束,增加注解的支持!
<?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: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/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解支持--> <context:annotation-config/> </beans>
-
bean
//在类上面加一个 @Component 注解就相当于:<bean id="user" class="com.feng.pojo.User"/> //这里的 id 就是 这个类类名的小写 @Component //英文是组件的意思 public class User { }
-
属性如何注入
//@Value("小明") 相当于:<property name="name" value="小明"/> 即给属性复制 @Value("小明") public String name ; @Value("小明") //也可以放在set方法上 public void setName(String name) { this.name = name; }
-
衍生的注解
@Component有几个衍生注解,我们在开发中,会按照mvc三层架构分层!
- dao 【@Repository】 dao层
- service 【@service】 业务层
- controller 【@controller】 servlet层
这四个是等价的,功能是一样的,都是代表将某个类注册到Spring中,装配bean只是开发中层数的不同我们习惯上每层用不一样的注解。
-
自动装配:(即装配属性为自定义的引用类型 和 基本类型的属性是复制(用@Value(“小明”) )是不一样的)
- @Autowired: 通过byType的方式实现 ,如果找不到 名字,则通过byName实现
- @Qualifier:如果 @Autowired 不能唯一自动装配上属性,则需要通过@Qualifier(value=“XXX”)
- @Nullable:字段标记了这个注解,说明这个字段可以为null;
- @Resource:默认通过byName的方式实现,如果找不到 名字,则通过byType实现!
-
作用域
@Scope("prototype")//作用域设置为原型模式 public class User { }
-
小结
xml与注解:
- xml 更加万能,适用于任何场合!维护简单方便
- 注解不是自己类使用不了,维护相对复杂
xml与注解最佳实现:
-
xml 用来管理bean;
-
注解只负责完成属性的注入;
-
我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持。
<!--开启注解支持--> <context:annotation-config/> <!--指定要扫描的包,在这个包下面的注解就会生效--> <context:component-scan base-package="com.feng.pojo"/>
9、使用Java的方式配置Spring
- 我们现在要完全不使用Spring的xml配置了。全权交给java来做!
- JavaConfig 是 Spring 的一个子项目,在Spring 4 之后,他成为了一个核心功能!
实体类:
package com.feng.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//这个注解的意思还是和原来一样,就是说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
//给name属性复制为小明
@Value("小明")//属性注入值
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
配置类:
package com.feng.config;
import com.feng.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//@Configuration注解代表这是一个配置类,就和之前的beans.xml文件是一样的
@Configuration //这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component
@ComponentScan("com.feng.pojo") //扫描包
@Import(UserConfig2.class) //将其他类的配置导入进来
public class UserConfig {
//这里注册一个bean,就相当于我们之前写的一个bean标签
//id 就是方法名 返回值就相当于bean标签中的class属性
//<bean id="getUser" class="User"/>
@Bean
public User getUser(){
return new User();//就是返回要注入到bean的对象
}
}
测试类:
import com.feng.config.UserConfig;
import com.feng.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void Test01(){
//如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user.getName());
System.out.println(user.toString());
}
}
这种纯Java的配置方式,在SpringBoot中随处可见!
10、代理模式
代理:代理就是帮一些人做一些事情。
为什么要学习代理模式? 因为这就是SpringAOP的底层!
面试:【SpringAOP 和 SpringMVC】必问!
代理模式的分类:
- 静态代理
- 动态代理
10.1、静态代理
角色分析:
-
抽象角色:一般会使用接口或者抽象类来解决
//住房这件事,作为一个接口 public interface Rent { //租房的方法 public void rent(); }
-
真实角色:被代理的角色
//房东,要租房子 就要继承租房接口,实现租房方法 public class Host implements Rent{ @Override public void rent() { System.out.println("房东要出租房子!!"); } }
-
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
//中介,它要出租房东的房子,即他自己也要会出租房子 //就要实现租房的接口,帮房东出租房子 public class Proxy implements Rent{ private Host host; //这里用set方法和构造方法都是一样的,都可以注入 public void setHost(Host host) { this.host = host; } @Override public void rent() {//中介出租房子 seeHose(); host.rent();//本质还是房东出租房子 hetong(); fare(); } //但中介还会做其它事情; //带你看房子 public void seeHose(){ System.out.println("中介带你看房子!"); } //带你签合同 public void hetong(){ System.out.println("中介带你签合同!"); } //收中介费 public void fare(){ System.out.println("收取中介费!"); } }
-
客户:访问代理对象的人!
//需要租房的人,真实的人,,他要找房东租房子 //但现在它找不到房东,他只能找到中介。 public class Client { public static void main(String[] args) { //找中介 Proxy proxy = new Proxy(); //中介背地里联系房东 Host host = new Host(); proxy.setHost(host); //中介出租房子(执行出租房子的方法) proxy.rent(); } }
代理模式的好处:
- 可以使真实的角色的操作更加纯粹!不用去关注一些公共的它的业务,如:房东就只需要会出租房子,像签合同,看人房子,这些事情可以交给代理角色(中介)来做。
- 这样就实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍开发效率会变低
10.2、加深理解
-
假如在公司中你需要在写好的几层架构中间添加一个日志功能,还不要改变原来的代码,这是代理模式就是很好的实现机制,AOP的实现机制就是代理模式!
-
接口UserService
public interface UserService { void add(); void update(); void delete(); void query(); }
-
真实的实现类UserServiceImpl
package com.feng.demo02; public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void query() { System.out.println("查询了一个用户"); } }
-
代理实现类UserServiceProxy,想实现增加日志的功能
package com.feng.demo02; //代理角色,代理真实角色 public class UserServiceProxy implements UserService { private UserServiceImpl userServiceimpl; public void setUserServiceimpl(UserServiceImpl userServiceimpl) { this.userServiceimpl = userServiceimpl; } @Override public void add() { logj("add"); System.out.println("增加了一个用户"); } @Override public void update() { logj("update"); System.out.println("修改了一个用户"); } @Override public void delete() { logj("delete"); System.out.println("删除了一个用户"); } @Override public void query() { logj("query"); System.out.println("查询了一个用户"); } //业务中需要给每个方法上加上一个日志 public void logj(String msg){ System.out.println("这是一个"+msg+"方法"); } }
-
客户类Client
package com.feng.demo02; public class Client { public static void main(String[] args) { //先找代理角色 UserServiceProxy userServiceProxy = new UserServiceProxy(); //代理角色找真实角色 UserServiceImpl userService = new UserServiceImpl(); userServiceProxy.setUserServiceimpl(userService); userServiceProxy.add(); } }
AOP的实现机制就是代理模式!
10.3、动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理【我们使用这个】
- 基于类:cglig类
- Java字节码实现:javasist
需要了解两个类:Proxy:代理,InvocationHandler:调用处理程序
核心类生成代理对象的类:
package com.feng.demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//这个类不在是静态的一个代理类,而是会动态生成一个代理角色去代理真实角色
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的真实角色实现的接口
//这里是租房
private Rent rent;
//使用set方法传入被代理的接口
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类(代理角色)
public Object getProxy(){
//这里要用到Proxy这个类来生成代理类
//第一个参数为这个类的类加载器,
//第二个参数为被代理对象实现的所有接口
//第三个参数为这个类
return Proxy.newProxyInstance(ProxyInvocationHandler.class.getClassLoader(),rent.getClass().getInterfaces(),this);
}
/*
* 所有动态代理类的方法调用,都会交由invoke()方法去处理
* proxy 被代理后的对象,即上面getProxy()方法生成的Object的对象
*
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHose();
hetong();
Object result = method.invoke(rent, args);
fare();
return result;
}
//但中介还会做其它事情;
//带你看房子
public void seeHose(){
System.out.println("中介带你看房子!");
}
//带你签合同
public void hetong(){
System.out.println("中介带你签合同!");
}
//收中介费
public void fare(){
System.out.println("收取中介费!");
}
}
测试:
package com.feng.demo03;
//需要租房的人,真实的人,,他要找房东租房子
//但现在它找不到房东,他只能找到中介。
public class Client {
public static void main(String[] args) {
//找中介 这时没有这个代理类,我们需要通过动态代理类来创建出来一个代理角色
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//要去找被代理的真实角色 这里是房东
Host host = new Host();
//将真实角色注入到动态代理类中
handler.setRent(host);
Rent proxy = (Rent) handler.getProxy();//生成代理类
proxy.rent();
}
}
动态代理的好处:
-
可以使真实的角色的操作更加纯粹!不用去关注一些公共的它的业务,如:房东就只需要会出租房子,像签合同,看人房子,这些事情可以交给代理角色(中介)来做。
-
这样就实现了业务的分工!
-
公共业务发生扩展的时候,方便集中管理!
-
一个动态代理类代理的是一个接口,一般就是对应一类业务
-
一个动态代理类可以代理多个类,只要是实现了同一个接口