【学习视频来自b站江南一点雨,推荐所有新手去看,讲得超棒】
将Bean注册到Spring容器中有三种方法:
1.xml注入
2.Java配置
3.自动化扫描
获取bean步骤
1.引入依赖
2.添加配置文件xml
3.创建实体类,在xml中添加bean标签
4.用ClassPathXmlApplicationContext(configLocation)导入配置文件,加载容器,获取对象
4.1用getBean获取对象,按名字获取对象,强转
4.2用getBean获取对象,按名字和class
4.3 用getBean获取对象,用class获取(当xml中有两个Bean时就会报错)
User user1 =(User) ctx.getBean("user");
User user2=ctx.getBean("user",User.class);
User user3 = ctx.getBean(User.class);
注意:用这3种方法获取的实例都是同一个
或者用FileSystemXmlApplicationContext(”文件路径“) 使用场景较少
属性注入
1.构造方法注入
1.1通过name注入,在bean标签中添加
<constructor-arg name="id" value="1"/>
2.通过Set方法注入
2.1添加 最常用
<property name="address" value="address1"/>
这种本质上是调用了set方法
2.2 p名称空间 ,本质上是调用了set方法
xmlns:p="http://www.springframework.org/schema/p"
<bean class="com.text.ioc.model.User" id="user3" p:userName="name3" p:address="address3" p:id="3">
</bean>
快捷键alt+回车
注意:所有框架中涉及到反射注入值的,属性名都是java中的内省机制分析出来的属性名,即:根据get\set方法分析出的属性名
3.外部方法注入
适用于当使用外部的、没有构造方法的Bean时(如通过builder构造的Request、OkHttpClient等实例)
3.1使用静态工厂注入
1.提供一个静态工厂
private static OkHttpClient OkHttpClient;
private static get(){
写静态的get方法:如果没有工厂,就new OkHttpClient.Bulider().build();
}
2.在xml中配置工厂
<bean class="org.text.OkHttpUtils" factory-method="get" id="okHttpClient"/>
3.获取实例并使用
3.2 实例工厂注入
工厂方法是一个实例方法,所以工厂类必须实例化之后才可以调用
<bean class="org.text.OkHttpUtils" id="okHttpUtils"/>
<bean class="okHttp3.OkHttpClient" factory-bean="okHttpUtils" factory-method="get" id="okHttpClient"/>
4.对象注入
用name和ref
5.数组注入
property中添加标签 或者
value 字符串;对象:ref引用bean 套一层bean或引用外面的bean
Map:
<property name="details">
<map>
<entry key="gender" value="男"/>
<map/>
<property/>
properties:
<property name="info">
<props>
<prop key=phone>12345678</prop>
<props/>
<property/>
java配置
有Spring Boot出现之后更常用
1.编写实例
2.创建Java配置类(即在类上面加@Configuration),new对象
@Configuration
public class JavaConfig{
@Bean
SayHello sayHello(){
return new SayHello;
}
}
3.使用:new AnnotationConfigApplicationContext(JavaConfig.class)获得容器对象,用对象getBean
自动化配置
主要是利用自动化扫描(包扫描),用注解;自动扫描可以用Java配置,也可以用xml配置
@Service 用在service层
@Repository 用在DAO层
@Controller 用在controller层
@Component 用在其它层
用Java配置:
1.写各层的代码,加对应的注解
2.在javaConfig配置文件里加注解 @ComponentScan(basePackages=“要扫描的包名”)
不指定包名会扫描配置文件所在包及 所在包的子类下所有文件
3.获得容器对象,getBean通过类型或名字获取(默认是首字母小写的类名,也可以在注解中自定义名字)
注意:
在注解 @ComponentScan(basePackages="要扫描的包名",useDefaultFilter=true)
useDefaultFilter默认的扫描方式,默认为true,四类注解全部扫描
当useDefaultFilter设置为false,用includeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION,class=Service.class)}设置只扫描service注解
excludeFilters 除了写的都扫
用xml配置
1.在前加
<context:component-scan base-package="要扫描的包" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"
</context:component-scan>
2.加载xml文件,getBean
对象注入
自动扫描时对象注入有三种方式:
@Autowerid 根据类型查找,要求一个类型只有一个对象;配合@Qualifier 才可以指定变量名
@Resources 根据名称查找,默认定义的变量名就是查找的名称,也可在注解中自定义名称;适合多个实例
@Injected 较少使用
条件注解
@condition 指 在满足某一个条件的情况下生效的配置
1.编写接口和不同条件下运行的实体类
2.定义两个condition类,impl 实现spring中condition的方法(matches)
2.1;获取操作系的名字
String osName=conditionContext.getEnviroment().getProperty("os.name");
2.2如果操作系统名称正确则返回true
return osName.toLowerCase().contains("win");
3.配置实体类
@Configuration
public class JavaConfig{
@Bean("cmd")
@Condition(WindowCondition.class)
ShowCmd winCmd{
return new WindowShowCmd();
}
@Bean("cmd")
@Condition(LinuxCondition.class)
ShowCmd linuxCmd{
return new LinuxShowCmd();
}
}
4.使用时 获取context ,getBean获取cmd
多环境切换
@Profile 底层基于条件注解,用于多环境(生产、测试)切换
Java中配置
1.准备dataSource实体类(包含url、username、password)
2.配置文件中写
@Configuration
public class JavaConfig{
@Bean
@Profile("dev")
DataSource devDS(){
DataSource ds=new DataSource();
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
return ds
}
@Bean
@Profile("prod")
DataSource proDS(){
DataSource ds=new DataSource();
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
return ds
}
}
3.创建容器ctx(不要根据配置,直接new一个新的)
3.1 设置activeProfiles ,导入配置文件,刷新容器
ctx.getEnviroment.setActiveProfiles(“prod);
ctx.register(JavaConfig.class);
ctx.refresh();
3.2使用时改setActiveProfiles的内容即可
xml配置
1.准备dataSource实体类
2.配置文件中写
<beans profile="dev">
<bean class="DataSource" id="devDs">
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</bean>
</beans>
<beans profile="prod">
<bean class="DataSource" id="devDs">
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</bean>
</beans>
3.main中加载,先new空的 ClassPathXmlApplicationContext();,再设置环境、引入配置文件、刷新容器、获取Bean
ctx.getEnviroment().setActiveProfiles("prod");
ctx.setConfigLocation("applicationContext.xml");
ctx.refresh();
DataSource ds=ctx.getBean(DataSource.class);
Bean的作用域
默认时,一个容器中有一个实例(scope=“singleton”)
xml中
bean标签中加 scope=“prototype” ,这个bean再容器中就不是单例,多次获取将拿到多个不同的实例
scope=“request” 在web环境下有效
scope=“session” 在web环境下有效
Java中
单例默认不加,代码中加@Scope(“prototype”) ,多次获取将拿到多个不同的实例
id和name的区别
大部分情况下一一昂
name支持多个,多个name之间用 , 隔开
id是唯一的
混合配置
就是把Java配置和xml配置混到一起,
在Java配置的那个类上额外加注解@ImportResource(“classpath:applicationContext.xml”)
容器加载安装Java加载(AnnotationConfigApplicationContext)
Aware接口
用来获取容器的各种信息(比如配置文件的信息)
1.依赖就是spring的依赖
2.实现BeanNameAware(获取名称),BeanFactoryAware(获取工厂),ResourceLoadAware(获取资源加载器),EnvironmentAware(获取环境),注解@PropertySource(“要加载的文件名字”)
3.私有变量beanName,resourceLoader,enviroment
注意:用了aware接口后,代码与容器的耦合会增大;比较少用
AOP
面向切面编程,是对面向对象思想的补充;可以做到在程序运行时,不改变源代码,动态增强方法的功能
如:日志、事务、数据库操作等等
常见概念
切点:要添加代码的地方
通知(增强advance):向切点动态添加的代码
切面:切点+通知
连接点:切点的定义(告诉spring怎么去定位到切点)
AOP的实现
本质基于Java动态代理实现
有两种方式:jdk,cglib
动态代理
方法不改变,在方法上加东西
1.定义接口(MyCalculator)、实现(MyCalculatorImpl)、代理类(MyCalculatorproxy),实现中写方法
2.代理类中定义方法
public class CalculatorProxy{
public static Object(MyCalculator myCalculator){
return Proxy.newProxyInstance(MyCalculator.class.getClassLoder(),myCalculator.getClass().getInterfaces(),new InvocationHandler){
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
System.out.println(method.getName()+"方法开始执行了");
Objetc invooke = method.invoke(myCalculator,args);
System.out.println(method.getName()+"方法执行结束了");
return invoke;
}
}
}
}
其中:proxy:代理对象;method:代理的方法;args:方法的参数;return:方法的返回值
3.调用时先new实现,再用proxy的方法传入实现获得实体类,再使用方法
MyCalculatorImpl myCalculator =new MyCalculatorImpl();
MyCalculator calculator =(MyCalculator)CalculatorProxy.getInstance(myCalculator);
int add=calculator.add(3,4);
System.out.println("add="+add);
AOP中的五种通知
前置通知、后置、异常、返回、环绕
JAVA配置使用时:
1.项目中加依赖:spring-cintext、aspectjrt、aspectjweaver
2.写接口、实现类(@Component 注解,扫描成实体类)
3.定义切点
3.1使用自定义注解(会入侵代码,少用)
①自定义注解 @Action ,注解上加
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
②在要拦截方法上加@Action
③定义增强(通知、Advice):
@Component
@Aspect //表示这是一个切面
public class LogAspect{
//前置通知 在目标方法执行之前执行
@Before(value="@annotation(xxx.xxx.xxx.Action)")
public viod before(JoinPoint joinPoint){
//前置通知的方法,可以打印日志
//获取方法名
String name =jiontPoint.getSignature().getName();
//打印
System.out.pringln(name+"方法开始执行了");
}
//后置通知,在目标方法执行之后执行
@After("@annotation(Action)")
public viod after(JoinPoint joinPoint){
//后置通知的方法,可以打印日志
}
//返回通知,在该方法中获取目标方法的返回值
@AfterReturning(value="@annotation(Action)",returning="r")
public viod returning(JoinPoint joinPoint,Object r){
//返回通知的方法,可以打印日志
// returning是方法的返回值,没有返回类型不匹配则不显示;Objcet可以匹配所有的返回值类型
//如果无返回值(viod),返回参数类型是Object,会有通知,返回值是null
//异常通知,目标方法抛出异常时,此方法会触发
@AfterThrowing(value="@annotation(Action)",throwing="e")
public viod returning(JoinPoint joinPoint,Exception e){
//返回通知的方法,可以打印日志
// throwing e 异常参数,和方法的参数名一一对应,注意异常的类型,匹配不上则不显示
}
//环绕通知(集大成者,可以实现上面的四个通知,注意参数和别的不一样;核心类似于通过反射执行方法)
@Aorund("@annotation(Action)")
public Object around(ProceedingJoinPoint pjp){
Object proceed=null;
try{
//类似于method.invoke,可以在这个方法的前、后分别添加日志,前置/后置通知;不写的话目标方法就不会执行
//pjp.proceed(new Object[]{5,5})可以改参数
proceed=pjp.proceed();
}catch(Throwable throwable){
//捕获异常,会有异常通知
throwable.printStackTrace();
}
return proceed;
}
}
④在JavaConfig(配置类)中开启包扫描和自动代理
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class JavaConfig{
}
⑤调用
public class Main{
public static void main(String[] args){
//获取容器
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(JavaConfig.class);
//获取Bean
MyCalculator mycalculator = ctx.getBean(MyCalculator.class)//这里写实现类可能会找不到,写接口就行
mycalculator.add(3,4);
}
}
③定义增强(通知、Advice)的优化:
一个一个配置不方便统一修改
解决:1.用一个方法定义切点:
@Component
@Aspect //表示这是一个切面
public class LogAspect{
@Pointcut("@annotation(Action)")
public void pointcut(){
//一个空的方法,后续要改的时候只改这个方法即可
}
@Before(value="pointcut()")//这里写上面的方法名
public viod before(JoinPoint joinPoint){
//略
}
}
2.非侵入式优化:
@Component
@Aspect //表示这是一个切面
public class LogAspect{
@Pointcut("execution(返回值类型 包名.某个类.某个方法(参数类型))")
public void pointcut(){
//一个空的方法,后续要改的时候只改这个方法即可
}
@Before(value="pointcut()")//这里写上面的方法名
public viod before(JoinPoint joinPoint){
//略
}
}
注意:
返回值类型、某个类、某个方法可以用通配符 * ,代表任意/所有
参数类型可以用 … (两个点)表示任意个数、任意类型的参数
xml配置使用时:
基本与Java配置相同,区别在于
①LogAspect文件中不必写注解,如@Before(value=“@annotation(xxx.xxx.xxx.Action)”)…,而是在xml文件中配置
<bean class="xxx.LogAspect" name="logAspect"/>
<bean class="xxx.MyxxxImpl" name="myxxx"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(返回值类型 包名.某个类.某个方法(参数类型))"/>
<aop:aspect ref="logAspect">
<aop:before method="before" pointcut="这里可以自定义,用-ref就是引用上面的"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-returning method="returning" pointcut-ref="pointcut" returning="r"/>
<aop:afte-throwing method="throwing" pointcut-ref="pointcut" throwing="e"/>
<aop:around method="around" pointcut-ref="pointcut"/>
<aop:aspect/>
<aop:config>
②调用时加载ClassPathXmlApplicationContext->getBean->用方法
JDBCTemplate
利用aop思想封装的JDBC操作工具
使用步骤
1.新建项目,添加依赖spring-jdbc、spring-context、mysql-connector-java
2.准备数据库及对应的实体类
3.准备一个Java配置类,在其中配置JDBCTemplate:提供DataSource和JDBCTemplate的Bean
@Configuration
public class JDBCConfig{
@Bean
DataSource dataSource(){
//通过DriverMangerDataSource获取dataSource
DriverMangerDataSource dataSource=new DriverMangerDataSource();
//设置dataSource的内容
dataSource.setDriverClassName("com.mysql.cj.jdbc".Driver);
dataSource.setUrl("jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF8");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource())
}
}
4.调用
AnnotationConfigApplicationContext获取容器ctx、getBean获得jdbcTemplate
增删改使用jdbcTemplate.update(“sql语句”,“参数1”,"“参数2”…),查用query,如query(“sql语句”,new RowMapper())、query(“sql语句”,new BeanPropertyRowMapper(User.class))、quertForList
定制需求可以用jdbcTemplate.update(预状态通道)
如果用xml配置,则上述3改为在xml文件中写
<bean class="xxx.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="xxx"/>
<!--设置url、用户名、密码 略-->
<bean/>
<bean class="xxx.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
<bean/>
在4中获取容器用ClassPathXmlApplicationContext