Spring 框架的概述以及Spring中基于XML的IOC配置
/**
* 获取spring的IoC核心容器,并根据id获取对象 * ApplicationContext的三个常用实现类:
* ClassPathXmlApplicationContext: 它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话加载不了。
* FileSystemXmlApplicationContext: 它可以加载磁盘任意路径下的配置文件(必须有访问权限)
* AnnotationConfigApplicationContext: 它是用于读取注解创建容器的
*
* 核心容器的两个接口引发出的问题:
* ApplicationContext: 单例对象适用
* 它在构建核心容器时,创建对象采取的策略是立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象
* BeanFactory: 多例对象适用
* 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象,什么时候才真正的创建对象。
* @param args
*/
<!-- Spring对bean的管理细节
1,创建bean的三种方式
2,bean对象的作用范围
3,bean对象的生命周期
-->
<!-- 第一种方式:使用默认构造函数。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其它属性和标签时,
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.jsp.service.impl.AccountServiceImpl"></bean>
-->
<!-- 第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入Spring容器)
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其它属性和标签时,
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="instanceFactory" class="com.jsp.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
-->
<!-- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)-->
<bean id="accountService" class="com.jsp.factory.StaticFactory" factory-method="getAccountService"></bean>
<!-- bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值:
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
gloabl-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
-->
<bean id="accountService" class="com.jsp.service.impl.AccountServiceImpl" scope="prototype"></bean>
Spring中基于注解的IOC和IOC的案例
* 用于创建对象
* 就和xml配置文件中编写bean标签实现的功能是一样的
* @Component:
* 作用:用于把当前类对象存入spring容器
* 属性:
* value:用于指定bean的id。当我们不写时,它默认值是当前类名,且首字母小写
* @Controller:一般用在表现层
* @Service:一般用在业务层
* @Repository:一般用在持久层
* 以上三个注解他们的作用和属性与Component是一样的,
* 他们三个是Spring框架为我们提供明确的三层使用的注解,
* 使我们的三层对象更加清晰。
*
* 用于注入数据
* 就和xml配置文件中的bean标签中写一个property标签作用一样
* @Autowried:
* 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
* 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
* 如果ioc容器中由多个类型匹配时,
* 出现位置:可以是成员变量上,也可以是方法上。
* 细节:在使用注解注入时,set方法就不是必须的了
* @Qualifier:
* 作用:在按照类型注入的基础之上,再按照名称注入。它在给类成员注入时不能单独使用。
* 但是在给方法参数注入时可以
* 属性:value:用于注入指定bean的id
* @Resource:
* 作用:直接按照bean的id注入。它可以独立使用
* 属性:name:用于指定bean的id
* 以上三个注入都只能注入其它bean类型的数据,而基本类型和String类型无法使用上述注解实现
* 另外,集合类型的注入只能通过xml来实现
*
* @Value:
* 作用:用于注入基本类型和String类型的数据
* 属性:value:用于指定数据的值。它可以使用spring中的SpEL(也就是spring中的el表达式)
* SpEL的写法:${表达式}
*
* 用于改变作用范围
* 就和集合在bean标签中使用scope属性实现的功能一样
* @Scope:
* 作用:用于指定bean的作用范围
* 属性:value:指定范围的取值。常用取值:singleton prototype
* 和生命周期相关
* 在bean标签中使用init-method和destroy-method的作用一样
* @PreDestroy:用于指定销毁方法
* @PostConstruct:用于指定初始化方法
* spring中的新注解:
* Configuration:
* 作用:指定当前类是一个配置类
* 细节:当配置作为AnnotationConfigApplicationContext对象创建参数时,该注解可以不写。
* ComponentScan
* 作用:用于通过注解指定Spring在创建容器时要扫描的包。
* 属性:
* value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
* 我们使用此注解就等同于在xml中配置了:
* <context:component-scan base-package="com.jsp"></context:component-scan>
* Bean
* 作用:用于把当前方法的返回值作为bean对象存入spring容器中
* 属性:
* name:用于指定的bean的id。当不写时默认值是当前方法的名称
* 细节:
* 当我们使用注解配置方法时,如果方法有参数,Spring框架会去容器中查找有没有可以用的Bean对象。
* 查找的方式和Autowired注解的作用是一样的
*
* Import
* 作用:用于导入其它配置类
* 属性:
* value:用于指定其他配置类的字节码。当我们使用Import注解之后,
* 有Import注解的类就是父配置类,而导入的都是子配置类
* PropertySource
* 作用:用于指定properties文件的位置
* 属性:
* value:指定文件的名称和路径
* 关键字:classpath,表示类路径下
@Configuration
@ComponentScan(basePackages = "com.jsp")
public class SpringConfiguration {
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "runner")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUser("root");
ds.setPassword("maple");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Configuration,ComponentScan,@Bean三个注解配置相当于之前xml配置文件。
Test测试类:
public class AccountServiceTest {
@Test
public void testFindAll() {
//获取容器
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//1.获取容易
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test");
account.setMoney(12345f);
//1.获取容易
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//1.获取容易
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//1.获取容器
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.得到业务层对象
IAccountService as = ac.getBean("accountService",IAccountService.class);
//3.执行方法
as.deleteAccount(4);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pYfHZakG-1575855156098)(/Users/mac/Desktop/注解配置–方法所使用的对象.png)]
通过注解配置,test类中使用AnnotationConfigApplicationContext类通过SpringConfiguration反射得到容器
junit单元测试中,没有main方法也能执行
junit集成了一个main方法
该方法就会判断当前测试类中哪些方法有@Test注解
junit就让有Test注解的方法执行
junit不会管我们是否采用spring框架,在执行测试方法时,junit根本不知道是不是使用了spring框架,所有也就不会为我们读取配置文件/配置类创建spring核心容器。
由以上可知:当测试方法执行时,没有IOC容器,就算写了Autowired注解,也无法实现注入。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2gxNYknO-1575855156099)(/Users/mac/Desktop/junit整合spring框架坐标配置.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOThk5mq-1575855156102)(/Users/mac/Desktop/junit版本.png)]
package com.jsp.test;
import com.jsp.dao.IAccountDao;
import com.jsp.domain.Account;
import com.jsp.service.IAccountService;
import config.SpringConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* 使用junit单元测试,测试配置
* Spring整合junit的配置
* 1,导入spring整合junit的jar(坐标)
* 2,使用junit提供的一个注解把原有的main方法替换了,替换成spring提供的。
* @RunWith
* 3,告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
* @ContextConfiguration:
* location:指定xml文件的位置,加上classpath关键字,表示在类路径下
* classes:指定注解类所在的位置
* 当使用spring 5.x版本的时候,要求junit的jar包必须是4.12及以上
* @author mac
* @create 2019/11/2
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}
Spring中的aop和基于XML以及注解的AOP配置
动态代理:
第一种基于接口:
package com.jsp.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者
* @author mac
* @create 2019/12/8
*
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码基础上对方法增强
* 分类:
* 1,基于接口的动态代理
* 2,基于子类的动态代理
*
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
* Class[]:字节码数组
* 用于让代理对象和被代理对象有相同的方法,固定写法
* InvocationHandler:用于提供增强的代理
* 他是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口的实现类都是谁用谁写
*/
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 表示当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1,获取方法执行的参数
Float money = (Float) args[0];
//2,判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
第二种基于子类:需要借助于第三方cglib库
pom.xml
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RH3AkvvS-1575855156104)(/Users/mac/Desktop/动态代理cglib库.png)]
package com.jsp.cglib;
import com.jsp.proxy.IProducer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模拟一个消费者
* @author mac
* @create 2019/12/8
*
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码基础上对方法增强
* 分类:
* 1,基于接口的动态代理
* 2,基于子类的动态代理
*
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 他是用于指定被代理对象的字节码
*
* Callback:用于提供增强的代理
* 他是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口的实现类都是谁用谁写
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数一样
* @param methodProxy : 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1,获取方法执行的参数
Float money = (Float)args[0];
//2,判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}