spring的初始化
1、spring创建对象时通过反射来创建对象的,底层第通过无参构造器,如果没有无参构造器会报以下错误:
-- NoSuchMethodException: cn.wolfcode._01_hello.Person.<init>()
2、spring在获取ClassPathXmlApplicationContext("_01_hello.xml")对象时就扫描xml中的所有的<bean>标签,并为其创建了对象存放在spring容器中
3、获取指定<bean>对象的步骤
- 获取ApplicationContext act = ClassPathXmlApplicationContext("Xxx.xml");
- 获取对应的bean对象 act.getBean("id");
- 可以通过字节码Xxx.class获取(当bean只有一个时): act.getBean(Xxx.class)
springtest测试
@RunWith(SpringJUnit4ClassRunner.class)
//@RunWith注解是JUnit框架的一部分,它用于指定测试类或测试方法运行时使用的特殊测试运行器。它通常与@org.junit.Test注解一起使用。
//例如,在使用Spring框架时,我们可以使用@RunWith(SpringJUnit4ClassRunner.class)注解来使用Spring提供的JUnit运行器来运行我们的测试类
@ContextConfiguration("classpath:_02_ioc.xml")//要测试的bean的xml文件
public class MySpringTest {
@Autowired
private Cat cat;
@Test
public void test(){
cat.say();
}
}
--- 如果需要同时测试多个配置文件可以在@ContextConfiguration里面配置多个
@ContextConfiguration({"classpath:A.xml","classpath:B.xml"})//要测试的bean的xml文件
使用ApplicationContext获取bean的三种方式
getBean("id或name")
getBean(类名.class) //推荐使用,因为一般bean中只有一个类型的类
getBean("id或name",类名.class)
使用@Autowired时,它会先使用byType自动注入进行找对应类型的bean,如果有两个相同类型的bean时,那么它会按byName去找对应的bean,如果没有会报错;在对于有多个类型的bean时,可以使用@Qualifier(“id”)来指定自动注入哪个bean
<bean id="dog1" class="cn.wolfcode2._06_outoWire.Dog"></bean>
<bean id="dog2" class="cn.wolfcode2._06_outoWire.Dog"></bean>
@Autowired
@Qualifier("dog1")
private Dog dog;
//因此使用了 @Qualifier 注解来指定要注入的 bean 的 ID 为 "dog1"。这样就可以明确指定要注入的 bean,避免了出现 NoUniqueBeanDefinitionException 异常的情况。
一、spring配置别名和设置非单例对象
- 别名 : userDAO 和 dao 都可以作为id使用
- 作用范围: prototype(非单例,默认),singleton(单例)
<bean id="userDAO" name="dao" class="全限定名" scope="prototype"/>
-- 适合交给容器管理的bean
-表现层对象 -业务层对象 -数据层对象 -工具对象
-- 不适合交给容器管理的bean
-domain下的封装实体类对象
spring是使用无参的构造方建对象的,底层使用的是反射
-证明使用无参构造方法
创建一个带参的构造器,会发现创建不了对象
-证明底层使用反射
可以私有化构造器,发现还是能创建出对象
二、实例化bean的三种方式
-
构造方法(常用)
-配置<bean> <bean id="userDAO" class="UserDAOImpl类的全限定名" />
-
静态工厂
-需要三个成员(接口、实现类、工厂) -工厂类的写法 public class OrderDAOFactory{ public static OrderDAO getOrderDAO(){ return new OrderDAOImpl(); } }
-
FactoryBean(实用)
public class UserDaoFactoryBean implements FactoryBean<UserDAO>{
//userDAO是指定生产对象的类型,指定的泛型T
//实现getObject()方法,返回一个实例对象 return new UserDAOImpl();
//实现getObjectType()方法,返回一个字节码对象 return UserDAO.class;
//方法isSingleton()不写就是单例 返回true就是单例,返回false就是非单例
}
- 配置<bean>
<bean id="userDAO" class="UserDaoFactoryBean类的全限定名" />
它会根据工厂的getObject()方法创建出对应的对象,类型是getObjectType()返回的类型
--- 在bean中使用接口时需要使用这种方式,因为接口不能直接创建对象
三、依赖注入
注入方式:
-setter注入
-简单类型 -引用类型
比如配置引用类型:
public class BookServiceImpl implements BookService{
private BookDAO bookDAO;
//写一个BookDAO的 setter方法
}
-配置<bean>
<bean id="bookDAO" class="BookDAOImpl实现类全限定名"/>
<bean id="bookService" class="BookServiceImpl实现类全限定名">
//name是字段名,ref是<bean>的id
<property name="bookDAO" ref="bookDAO"/>
</bean>
配置普通类型(value直接给值就行了)
<property name="实现类中的字段名" value="10"/>
=================================================================
-构造方法注入(就是调用有参构造器创建对象了)
-简单类型 -引用类型
引用类型写法
<bean id="bookDAO" class="BookDAOImpl实现类全限定名"/>
<bean id="bookService" class="BookServiceImpl实现类全限定名">
//name是构造器的形参名,ref是<bean>的id
<constructor-arg name="bookDAO" ref="bookDAO"/> //多个参数可以使用多行
</bean>
配置普通类型(value直接给值就行了)
<constructor-arg name="构造器的形参名" value="10"/>
四、自动注入(自动装配)
- byType按类型注入(对应的类的bean只能有一个,而且必须要setter方法),中可以去掉name
//service实现类有BookDAO字段
public class BookServiceImpl implements BookService{
private BookDAO bookDAO;
//写一个BookDAO的 setter方法
public void save(){
System.out.println("service ...");
bookDAO.save(); //使用到BookDAO类型对象,会自动去<bean>配置中找该类型的bean
}
}
-配置<bean>
<bean id="bookDAO" class="BookDAOImpl实现类全限定名"/>
<bean id="bookService" class="BookServiceImpl实现类全限定名" autowire="byType">
- byName注入(哪个的id名和setter方法名对应的属性一样,就注入哪个,没有就报空指针)
依赖自动注入的特征
- 自动注入用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型注入时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称注入时(byName)必须保障容器中具有指定名称的bean,因属性名与配置耦合,不推荐使用
- 自动注入优先级低于setter注入与构造器注入,同时出现时自动注入配置失效
注解之@Autowired和@Value(掌握)
1、@Autowired注解是先进行getBean(class clz)byType进行注入,如果有多个相同的类型的时候,会通过类型和名称一起匹配查找getBean(“id或name”,class clz),如果没有则报错
@Value可以直接给普通对象属性进行赋值
Ioc注解(掌握)
- @Repository : 用于标注在DAO实现类上
- @Service : 用于标注在业务层实现类上
- @Controller : 用于标注在控制层类上(如springMVC的Controller)
- @Component : 除了以上这些的话,其他可以使用这个注解进行标注
Scope和PostConstruct以及PreDestroy注解
- @Scope : 帖在类上,标明bean的作用域
- @PostContruct : 贴在方法上,比小明创建完后调用此方法。
- @PreDestroy : 贴在方法上,标明销毁时调用此方法
注意:当作用域scope的属性值为prototype(原型,非单例)时,它的销毁操作方法是不会被调用的,需要手动来调用;当一个类不是单例的时候,spring容器是不会管理该类完整的生命周期的
五、集合注入
数组、List、Set、Map、properties
--- 数组、List、Set都是一样的,只不过set会自动去重
<bean id="bookDAO" class="BookDAO实现类全限定名">
<property name="array">
<array>
<value>10</value>
<value>20</value>
</array>
</property>
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="city" value="zhangjiang"/>
</map>
</property>
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="city">zhangjiang</prop>
</props>
</property>
</bean>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82IxQq2w-1685604744318)(C:\Users\24128\AppData\Roaming\Typora\typora-user-images\image-20230412231650903.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8oOn0esx-1685604744319)(C:\Users\24128\AppData\Roaming\Typora\typora-user-images\image-20230412232102682.png)]
六、动态代理
步骤:
InvocationHandler接口的底层步骤和逻辑
InvocationHandler 是一个接口,它包含了一个 invoke() 方法,用于在代理对象上调用方法时进行处理。其底层步骤和逻辑如下:
1、创建一个实现了 InvocationHandler 接口的代理对象,并将目标对象传入代理构造函数(给target真实对象赋值)。
2、当代理对象的方法被调用时,将被重定向到 invoke() 方法。
3、invoke() 方法接收到方法名、参数和代理对象等信息。
4、根据方法名和参数等信息,可以自定义处理逻辑,例如在调用目标方法前后添加一些额外的逻辑。
5、invoke() 方法将处理结果返回给代理对象。
6、代理对象将结果返回给调用者。
总体来说,InvocationHandler 接口允许在方法调用前后添加自定义逻辑,从而扩展目标对象的功能。它提供了一种比继承更灵活的方式,因为它可以在运行时动态地添加和删除方法,并在需要时修改代理的行为。通常情况下,我们可以使用 InvocationHandler 接口实现代理模式、AOP 等功能。
jdk动态代理
1、创建被代理类对象
public class PersonDAOImpl implements IPersonDAO {
public void sayHello(String name) {
System.out.println("你好:"+name);
}
public void add() {
System.out.println("执行添加操作");
}
}
2、创建一个动态代理类来实现java.lang.reflect.InvocationHandler;接口并实现其中的**invoke()**方法
— 方法中使用该实现类的构造器来给被代理类进行初始化赋值
public class PersonInvocationHandler implements InvocationHandler {
private Object obj; //被代理类对象
public PersonInvocationHandler(Object obj) {
//利用代理类的构造器给被代理类对象进行初始化
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事物");
//被代理类实行的方法
Object result = method.invoke(obj, args);
System.out.println("关闭事物,资源");
return result;
}
}
3、测试类实行的代码
@Test
public void test2(){
IPersonDAO person = new PersonDAOImpl();//创建一个被代理类对象
IPersonDAO result = (IPersonDAO) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new PersonInvocationHandler(person));
result.add();
}
使用newProxyInstance()方法时,就会创建一个被代理对象,当被代理对象执行对应的方法时,就会重定向去调用InvacationHandler.invoke,invoke方法中执行时就是执行被代理类对象(真实类型对象)执行的方法,因为在创建代理对象的时候,其中的第三个参数就已经存储了被代理类的真实类的类型了,所以里面执行的就是真实类的方法
使用cglib动态代理
1、创建一个真实类
2、创建一个代理类实现org.springframework.cglib.proxy.InvocationHandler接口
3、书写xml文件
<bean id="tx" class="cn.wolfcode2.cglib.MyTransactionManager"></bean>
<bean id="translationHandler" class="cn.wolfcode2.cglib.proxy.MyTranslationHandler">
<property name="tx" ref="tx"/>
<property name="target">
<bean id="userService" class="cn.wolfcode2.cglib.service.impl.UserServiceImpl"></bean>
//里层的bean只对外层的bean可用
</property>
</bean>
4、书写测试类测试代码
Enhancer是CGLIB库中的一个类,用于生成代理类。它通过创建目标类的子类来实现代理,从而避免了代理类必须实现接口的限制,因此它也被称为“子类代理”。Enhancer通过设置一系列的回调函数(Callback)来拦截对目标类方法的调用,并在拦截时执行自己的逻辑。
enhancer.setSuperclass() : 用于设置代理类的父类,即被代理类。CGLIB可以生成一个继承于被代理类的代理类,从而实现代理功能。在设置代理类父类时,需要使用setSuperclass方法,传入被代理类的Class对象。
setcallback() : 当使用 CGLIB 实现动态代理时,需要设置回调方法才能在代理对象方法被调用时执行相应的逻辑,否则会出现空指针异常。原因是代理对象在被调用时会去执行回调方法,如果回调方法没有被设置,则会为空,从而导致空指针异常。
create() : 方法返回的对象是一个代理类的实例对象,它继承了被代理类的所有非私有方法,并且可以覆盖这些方法。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(handler.getTarget().getClass());
enhancer.setCallback(handler);
UserServiceImpl userService = (UserServiceImpl) enhancer.create();//创建一个真实类的子类对象,包含真实类的字段和方法
userService.check();
使用javassist动态代理
public class JavassistProxyFactory { //代理工厂类
public static Object createProxy(Class targetClass, MethodHandler handler) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
//传入被代理的字节码,CGLIB可以生成一个继承于被代理类的代理类
enhancer.setCallback(handler);
Class<?> proxyClass = enhancer.createClass();
//创建代理类的字节码,在调用该方法前必须设置好回调方法
Object proxyInstance = proxyClass.newInstance();
return proxyInstance;
}
}
public class JavassistHandler implements MethodHandler { //代理处理器
private Object target;
public JavassistHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Method proceed, Object[] args) throws Throwable {
System.out.println("执行前操作===");
Object result = method.invoke(target, args);
System.out.println("执行方法后的操作===");
return result;
}
}
public class UserServiceImpl {
public void save() {
System.out.println("save user");
}
}
public class JavassistProxyTest {
@Test
public void testJavassistProxy() throws Exception {
UserServiceImpl userService = new UserServiceImpl();
JavassistHandler handler = new JavassistHandler(userService);
UserServiceImpl proxy = (UserServiceImpl) JavassistProxyFactory.createProxy(UserServiceImpl.class, handler);
proxy.save();
}
}
七、使用JavaConfig来获取bean,无需配置xml
1、先创建实体类
@Component
public class User {
@Value("战三")
private String name;
@Value("123456")
private String password;
}
@Component
public class Dog {
@Value("二哈")
private String name;
}
2、创建一个生产实体类的类(javaConfig类)
@Configulation : 用于标注一个类为配置类,相当于.xml
作用:可以让spring容器在启动时自动扫描配置类,并将其中声明的bean加入到容器中
@Bean : 用于声明bean对象,相当于bean标签
@ComponentScan():属性默认扫描的是当前包及其子包,使用了这个后配置类中可以不用使用@Bean,spring会默认把对应的包下的bean加入容器中,其他的类要加@Component注解
@Inport:用于导入一个或多个配置类或组件类(多个可以用{}括起来),将其导入到当前类中,以实现一些额外的功能
@Configuration //指定它是生产bean的类
@ComponentScan("cn.wolfcode._01_springConfig") //扫描器
@Import(ConfigDemo1.class) //导入其他的生产bean的类,管理起来可以统一通过这个主类去获取
public class ConfigDemo {
@Bean //相当于xml中的bean标签,表明这个bean要交给spring容器管理
public User getUser() {
return new User();
}
}
--- 被关联的生产bean的类
@Configuration
public class ConfigDemo1 {
@Bean
public Dog getDog() {
return new Dog();
}
}
3、使用测试类去获取指定的bean
@Test
public void test1(){
//不使用xml配置文件来获取bean
ApplicationContext context = new AnnotationConfigApplicationContext(ConfigDemo.class);
//ConfigDemo.class关联了生产Dog类的类
//User user = (User) context.getBean("user"); //通过名字来获取
User user = (User) context.getBean("getUser"); //通过方法来获取
Dog dog = (Dog) context.getBean("getDog");
System.out.println(user);
System.out.println(dog);
}
4、使用spring测试类来测试配置类
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
// 配置并返回数据源
return new DruidDataSource();
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})//可以对多个配置类进行测试
public class MyTest {
// 测试方法
}
八、spring的ioc原理
1、spring中ioc的原理是使用xml解析、反射和工厂模式来实现的
xml解析: 通过解析来获得其中的class的值
<bean class="cn.wolfcode.mapper.UserMapper"></bean>
**反射:**通过xml解析获得的class的值,再进行Class.forName(“xxx.xxx.xxx”)来获得对应的字节码对象clz,再通过字节码对象的clz.newInstance()来创建对象
Class clz = Class.forName("xxx.xxx.xxx.xx");
UserMapper userMapper = clz.newInstance();
**工厂模式:**通过工厂模式来返回一个该对象,定义一个静态方法用过返回反射创建好的对象
public class UserMapperFactory(){
public static Object getBean(){
return clz.newInstance();
}
}
2、spring中有两个常用的接口(BeanFactory、ApplicationContext),ApplicationContext是开发人员常用的接口
**BeanFactory接口 😗*在获取spring容器的时候并没有创建出bean对象,而是在使用bean的时候才开始创建bean对象的
**ApplicationContext接口:**在获取spring容器的时候就创建好了bean对象了
**常用的两个实现类:**ClassPathXmlApplicationContext、FileSystemXmlApplicationContext
ClassPathXmlApplicationContext:从字节码的根路径下开始查找xml文件
FileSystemXmlApplicationContext:从电脑磁盘的的路径下找对应的xml文件
public class UserMapperFactory(){
public static Object getBean(){
return clz.newInstance();
}
}
2、spring中有两个常用的接口(BeanFactory、ApplicationContext),ApplicationContext是开发人员常用的接口
**BeanFactory接口 😗*在获取spring容器的时候并没有创建出bean对象,而是在使用bean的时候才开始创建bean对象的
**ApplicationContext接口:**在获取spring容器的时候就创建好了bean对象了
**常用的两个实现类:**ClassPathXmlApplicationContext、FileSystemXmlApplicationContext
ClassPathXmlApplicationContext:从字节码的根路径下开始查找xml文件
FileSystemXmlApplicationContext:从电脑磁盘的的路径下找对应的xml文件