当前位置: 首页 > news >正文

Spring小细节

引言: emmm,最近就是想着把spring容器吃透一些,然后事务繁忙所以主要就看了看b站的ssm视频,没有很仔细的敲代码(因为学过一些),然后里边的AOP动态代理可以在b站看看视频一定要学会,这个由于时间问题没记多少东西,但其实AOP主要就用到了注解加切面这俩个类(如果对数据库操作切面还要包含对应的业务层和持久层等),AOP很有用一定要搞懂,希望以后大家都能找到一份好工作。

Spring

概念

1.狭义上spring框架指的是Spring Framework(Spring Framework 整合了 Spring MVC)而广义上spring体系才是全家桶

2.有功能的类就叫做组件(万物皆组件),比如service方法,而如果是单纯保存数据的类不叫做组件,比如pojo类。

3.容器管理组件就不用new了,IOC控制反转思想就是通过DI依赖注入(setter、构造器等方式)来实现,自动注入加生命周期。

4.注意容器打印的对象如果定义返回值就直接返回对象,否则就是返回地址(全限定类名(包名 + 类名)+@哈希码)因为没有重写toString,所以会继承 Object类的默认实现。

5.容器具有单实例特性,每个对象只会创建一次。

6.spring容器一般会用config包下的配置类来专门存放,类上必须加@Configuration,而bean配置文件没以前流行,这样比较好分类管理。并且如果类上没有@Configuration光方法有bean也不会生效,类必须注册进bean才可以。

7.配置类也是一个容器组件,比如personConfig(默认是类名小写),不过里边的方法我们都需要手动@Bean(只能加在方法上)注册。

8.像@Controller、@Service等MVC分层注解会自动扫描并创建实例,所以不需要手写@Bean了,而且写了的话理论可以,但不规范。

9.注意分层注解能起作用的前提是: 这些组件必须在主程序所在的包及其子包下,否则@ComponentScan(basePackages = "com.rjgc.spring") //组件批量扫描;只扫利用Spring相关注解注册到容器中的组件,类上不加@Component等注解就不扫。不过规范编程即可避免问题,而且即使这样写,包看着也难受。

10.要想注册容器,要么用mvc注解,要么就用@Configuration,然后只要是bean容器哪个类上都可以标@Import等注解,不过可以在config一个类下专门处理,这样让springApplication主程序更加清爽。

11.将第三方类注册bean,要么@Bean将其返回值写进去并且return,要么@Import(xxx.class),注意如果有的第三方类名重复,那么我们就@Import(全限定类名.class)指定注册。

12.容器的所有组件我们都可以称之为bean,将bean注册进去之后我们就可以注入不用new对象了。

Bean

注册


modules就是聚合,记得刷新,不过子项目不继承父项目的话,父项目没有控制权

public static void main(String[] args) {
//1、跑起一个Spring的应用; ApplicationContext:Spring应用上下文对象; IoC容器
ConfigurableApplicationContext ioc = SpringApplication.run(主类.class, args);
System.out.println("====================ioc容器创建完成====================");
​
//4、获取容器中的组件对象;精确获取某个组件
// 组件的四大特性:(名字、类型)、对象、作用域   根据名字或类型就可以拿到对象,进而进行操作
// 组件名字必须全局唯一 (老版本好像一定只会放一个最先声明的bean(可能按首字母或者注册顺序),这样以后的bean就不能注册了)
// 新版本好像会报错 BeanDefinitionStoreException 所以方法重载bean名称不同理论可以,但是这样就没意义了
​
//小结:
//从容器中获取组件,
// 1)、组件不存在,抛异常:NoSuchBeanDefinitionException
// 2)、组件不唯一,
//      按类型只要一个(如果多个组件是同类型的):抛异常:NoUniqueBeanDefinitionException
//      按名字只要一个: 可以精确获取到指定对象
//      同类型有多个:返回所有组件的List集合
//      同名称不会有多个
// 3)、组件唯一存在,正确返回。
​
//4.1、按照组件的名字获取对象
Person zhangsan = (Person) ioc.getBean("zhangsan");
System.out.println("对象 = " + zhangsan);
​
//4.2、按照组件类型获取对象 (按类型只有一个bean才不会冲突)
//Person bean = ioc.getBean(Person.class);
//System.out.println("bean = " + bean);
​
//4.3、按照组件类型获取这种类型的所有对象
Map<String, Person> type = ioc.getBeansOfType(Person.class);
System.out.println("type = " + type);
​
//4.4、按照类型+名字 (这样对比直接获取名称好处就是不用强制类型转换)
Person bean = ioc.getBean("zhangsan", Person.class);
System.out.println("bean = " + bean);
}
​
@Bean("可以指定名称") // 不指定名称默认就是方法名
public Person zhangsan() {Person person = new Person();person.setName("张三");person.setAge(20);person.setGender("男");return person;
}
/**
*前提必须注册到容器中
*打印结果:
*对象 = Person(name=张三, age=20, gender=男) 按照名称拿取,需要强转,默认拿取的是Object
*bean = Person(name=张三, age=20, gender=男)  按照类型拿取容器中对象的好处是不用强转
*type = {zhangsan=Person(name=张三, age=20, gender=男), lisi=Person(name=李四, age=20, gender=男)} 直接获取多个
*/ 当然这个格式也是@Data注解重写toString后的效果

public class Spring01IocApplication{/*** Spring为我们提供了快速的MVC分层注解,有了这些注解就不用每个方法都@Bean了* @Configuration + @Bean:用于定义第三方组件或需要自定义创建逻辑的组件,必须手动编写@Bean方法。Spring特有注解* 1、@Controller 控制器* 2、@Service 服务层 (这些底层都封装了@Component)* 3、@Repository(传统的JDBC,标注在普通类上) @Mapper(MyBatis框架特有,并且必须是接口) 持久层* 4、@Component Spring核心组件(其实都标上这个就可以处理了,只不过上述三个给我们看会意思更明确一些)* @param args*/public static void main(String[] args) {ConfigurableApplicationContext ioc = SpringApplication.run(Spring01IocApplication.class, args);
​System.out.println("========");
​UserController bean = ioc.getBean(UserController.class);System.out.println("bean = " + bean);
​UserService bean1 = ioc.getBean(UserService.class);System.out.println("bean1 = " + bean1);}
}

优雅写法,虽然注解标注灵活,但是这样会更清爽

@Import({CoreConstants.class,Car.class}) 也可以是数组形式,而且不仅第三方照样也可以导入自己写得类
@Configuration
@ComponentScan(basePackages = "com.atguigu.spring")
public class AppConfig {
}

scope: 补充小知识,不过基本用不到

/*** 不过无论是否单实例,加上@Bean或其他mvc注解都是被立即注册进容器的,只不过对象创建时机会不同* @Scope 调整组件的作用域:* 1、@Scope("prototype"):非单实例:*      容器启动的时候不会创建非单实例组件的对象。*      什么时候获取,什么时候创建 (并且非单实例对象不会复用) (懒加载)* 2、@Scope("singleton"):单实例: 默认值*      容器启动的时候会创建单实例组件的对象。*      容器启动完成之前就会创建好  (饿汉式) (只有单实例可以加上@Lazy注解,这样可以变成懒加载,主要是缓解内存)* 3、@Scope("request"):同一个请求单实例* 4、@Scope("session"):同一次会话单实例*/

// FactoryBean在容器中放的组件的类型,是接口中泛型指定的类型,组件的名字是 工厂自己的名字

// 这个是工厂bean.如果创建对象很复杂时才用到。

@Component // 也要注册容器
public class BYDFactory implements FactoryBean<Car> {// 我们实现三个方法即可,分别是制作谁、制造的类型(这个主要用于多态)、是否单例。
}

//拿到环境变量
ConfigurableEnvironment environment = ioc.getEnvironment();
​
String property = environment.getProperty("OS");
System.out.println("property = " + property);

条件注解(可以满足条件才注册到容器)

@Conditional(MacCondition.class) // 标注在普通类或方法上没有效果,因为本身不负责注册bean
// 可以标在容器组件类或Bean方法上,必须是容器才可以 这个注解可以点进去看看,内容可以是数组
-------------
public class MacCondition implements Condition { // 实现的这个接口也是规范@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 只要是上下文就可以拿到,可能是不同的实现方式Environment environment = context.getEnvironment();String property = environment.getProperty("OS");return property.contains("mac");}
}
而且该类只是一个判断条件的工具,所以不需要注册到bean

注入

前提每个类都要在bean中注册了,设计模式:而且我们一般注入接口,这样可以实现多态,依赖倒置,因为接口不咋变,而实现类可能经常改变。

@Autowired userController = UserController(abc(方法名)=com.atguigu.spring.ioc.service.UserService@68e62ca4)

personList=[Person(name=比尔盖茨, age=20, gender=男), Person(name=张三2, age=20, gender=男), Person(name=李四, age=20, gender=男)] 这个是集合,区分上边的map是每个都加上前边的方法名了,而这个只需加一个参数名
注意当一个类中的参数依赖注入时,获取到的ioc里面默认参数是map格式
/*** 自动装配流程(先按照类型,再按照名称)* 1、按照类型,找到这个组件;*      1.0、只有且找到一个,直接注入,名字无所谓*      1.1、如果找到多个,再按照名称去找;变量名就是名字(新版)。*          1.1.1、如果找到: 直接注入。*          1.1.2、如果找不到,报错* 这个也是bean(类是一个bean组件,比如@Controller)中依赖注入bean(找我们所有配置好的bean)*/@Autowired //自动装配; 原理:Spring 调用 容器.getBean
UserService abc; // 这个类型下只有一个所以随便写@Autowired // 精确名称
Person bill;@Autowired //把这个类型的所有组件都拿来 
List<Person> personList; // 只能带类型@Autowired
Map<String,Person> personMap; // 这样显示的结果会带上注入的bean的方法名称@Autowired
ApplicationContext applicationContext; // 也可以找到
@Qualifier("bill") //精确指定:如果容器中这样的组件存在多个,且有默认组件。我们可以使用 @Qualifier 切换别的组件。
@Autowired
Person atom;
// 其实这个精确指定名称通过自定义方法名也可以实现,不过如果@Primary标注之后就需要通过这个注解来切换bean了
//面试题:@Resource 和 @Autowired 区别?
//1、@Autowired 和 @Resource 都是做bean的注入用的,都可以放在属性上,只有Spring家族可以使用
//2、@Resource 具有更强的通用性,javaEE也可以用@Resource // 没找到就报错
UserDao userDao;@Autowired(required = false) // 这个是特有的,即使没找到为空即可这样不报错,不过还是建议用这个就足以了
// 注意这个是没注册到bean不会报错,如果注入后找不到仍然会报错,找不到的原因同上
Dog dog;

@Autowired // setter注入也是另一种方式
public void setDog(@Qualifier("dog02") Dog dog) {System.out.println("setDog..." + dog);this.haha = dog;
}

感知接口

@Service // 前提也是注册到bean然后就会自动调用setter方法
public class HahaService implements EnvironmentAware {private Environment environment;@Override // 内部已经写好,我们实现这个即可,所以不需要@Autowired,自己写需要public void setEnvironment(Environment environment) {this.environment = environment;}public String getOsType(){return environment.getProperty("OS");}
}

非常强大的@Value注解,所以pojo类如果别的类不用的话,也可以加上@Component来注册到bean并赋值

// 当然前提也要注册到bean @Component会扫描到
/*** 1、@Value("字面值"): 直接赋值* 2、@Value("${key}"): 动态从配置文件中取出某一项的值。 $* 3、@Value("#{SpEL}"): Spring Expression Language: Spring 表达式语言* SpEL就代表可以写表达式 #*/@Value("旺财") 
private String name;
@Value("${dog.age : 默认值}") // 读取配置文件yml的信息,如果没有设置为默认值就不会报错了
private Integer age;@Value("#{10*20}")
private String color;@Value("#{T(java.util.UUID).randomUUID().toString()}") // 如果是静态方法必须加T()
private String id;@Value("#{'Hello World!'.substring(0, 5)}") // 非静态方法
private String msg;@Value("#{new String('haha').toUpperCase()}")
private String flag;@Value("#{new int[] {1, 2, 3}}")  // 注意如果我们new自己的类则该语法需要带上全类名s
private int[] hahaha;

属性来源

//说明属性来源: 把指定的文件导入容器中,供我们取值使用
@PropertySource("classpath:xxx/cat.properties") // 注意classpath表示的是从类路径开始即当前项目的resources目录
// 虽然注解可以随便标,但还是建议遵循单一职责,不要防御性编程
@Data
@Component
public class Cat {@Value("${cat.name:Tom}") // : 后面是取不到的时候的默认值;private String name;@Value("${cat.age:20}")private int age;
}

注意

1.classpath:cat.properties;从自己的项目类路径下找

2.classpath*:Log4j-charsets.properties;从所有包的类路径下找(比如第三方依赖),如果jar包下一点开刚好看到就这样写,否则

classpath*:xxx/xxx/xxx.properties即可

3.ResourceUtils也可以获得资源文件,比如classpath: jar: file:等,也可以拿取到网上的uri图片地址

当然我们依赖注入时不仅可以注入类,拿到对象调用方法,还可以直接注入方法

//1、定义环境标识:自定义【dev、test、prod】; 默认【default】
//2、激活环境标识:
//      明确告诉Spring当前处于什么环境。
//      你要不说是啥环境,就是 default 环境//利用条件注解,只在某种环境下激活一个组件。
@Profile({"dev","default"}) // @Profile("环境标识")。当这个环境被激活的时候,才会加入如下组件。
@Bean // 写成数组格式,这样满足一个即可,不会报错
public MyDataSource dev(){MyDataSource myDataSource = new MyDataSource();myDataSource.setUrl("jdbc:mysql://localhost:3306/dev");myDataSource.setUsername("dev_user");myDataSource.setPassword("dev_pwd");return myDataSource;
}

@Profile

spring.profiles.active=test

这个就是在yml中写出来即可激活环境了,刚好对应我们的注解@Profile

而且这种也不想@Value那样写$,直接写即可


post后置,pre之前,最复杂并且功能最全的是BeanPostProcessor,这个能改对象,真正具有拦截功能,而其他只能感知不能修改(其他感知的是void类型,而这个可以有返回值)

AOP

自定义日志

/**

  • 日志:

  • 1、硬编码: 不推荐; 耦合:(通用逻辑 + 专用逻辑)希望不要耦合; 耦合太多就是维护地狱,比如方法中sout

  • 2、静态代理

  • 定义:定义一个代理对象,包装这个组件。以后业务的执行,从代理开始,不直接调用组件;

  • 特点:定义期间(编码期间就确定好了)就指定好了互相代理关系

  • 实际上就是一个代理类也实现了接口,然后构造对象时传递参数为实际的类,实现方法调用实际类的方法,从而达到代理效果

  • 缺点:需要些很多类来代理不同对象

  • 3、动态代理:

  • 运行期间才确定好了代理关系(目标对象在执行期间被拦截,插入指定逻辑)

  • 注意目标对象必须有接口才可以

  • 4、AOP:

  • 更简洁,优化了对象必须有接口才可以动态代理

    */

数组不能直接打印,所以Arrays.asList(数组)即可,当然Arrays.toString更牛,即使参数为null也可以,而前面那个会报空指针异常

mapper.xml小细节

非常标准的写法: 主要是防止有单独的where或and出现,不过其实<where>标签和<set>已经解决了,所以下方主要是手动处理时的原理。

<!--
prefix:前缀 ;如果标签体中有东西,就给它们拼一个前缀
suffix:后缀
prefixOverrides:前缀覆盖; 标签体中最终生成的字符串,如果以指定前缀开始,就覆盖成空串
suffixOverrides:后缀覆盖
-->
<select id="queryEmpByNameAndSalary" resultType="com.atguigu.mybatis.bean.Emp">select * from t_emp<trim prefix="where" prefixOverrides="and || or"><if test="name != null">emp_name= #{name}</if><if test="salary != null">and emp_salary = #{salary}</if></trim>
</select>
<update id="updateEmp">update t_emp<trim prefix="set" suffixOverrides=","><if test="empName != null">emp_name = #{empName},</if><if test="empSalary != null">emp_salary = #{empSalary},</if><if test="age != null">age = #{age}</if></trim>where id = #{id}
</update>

上述forEach在加个不为空的判断,避免传递的ids为null发生报错,元素之间分割符肯定不会出现末尾单个逗号情况

<!批量新增-->
<insert id="addEmps">insert into t_emp(emp_name,age,emp_salary)values<foreach collection="emps" item="emp" separator=",">(#{emp.empName},#{emp.age},#{emp.empSalary})</foreach>
</insert>
<!批量修改 这个比单纯for循环然后每次都更新一条效率高,缺点就是不能用事务管理,这个是一个长sql管理很多短sql-->
<update id="updateBatchEmp"><foreach collection="emps" item="e" separator=";">update t_emp<set><if test="e.empName != null">emp_name = #{e.empName},</if><if test="e.empSalary != null">emp_salary = #{e.empSalary},</if><if test="e.age != null">age = #{e.age}</if></set>where id=#{e.id}</foreach>
</update>
# allowMultiQueries 允许一次发送多个sql,并用分号隔开
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-example?allowMultiQueries=true
# xml中必须加上这个才允许用分号隔开

@SpringBootTest // 只是影响速度,但是不会启动我们的主程序
public class HttpClientTest {/*** 测试通过HttpClient发送GET请求* client是客户端的意思*/@Testpublic void testGet() throws Exception{// 1.创建httpclient对象(CloseableHttpClient刚好实现了HttpClient这个接口)CloseableHttpClient httpClient = HttpClients.createDefault();// 2.创建请求对象(当然必须启动才能访问到) 客户端肯定是向我们服务端发送请求HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");// 3.发送请求并接受响应结果CloseableHttpResponse response = httpClient.execute(httpGet);// 验证: 获取响应状态码以及响应数据System.out.println("服务端返回的状态码为: " + response.getStatusLine().getStatusCode());HttpEntity entity = response.getEntity(); // 也是他自带的封装,需要解析String result = EntityUtils.toString(entity);System.out.println("服务端返回的数据为: " + result);// 关闭资源response.close();httpClient.close();}/*** 测试通过HttpClient发送POST请求*/@Testpublic void testPost() throws Exception{// 获取资源CloseableHttpClient httpClient = HttpClients.createDefault();// 创建请求HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");/*** 最重要的一步是封装传递的参数*/// 因为阿里的fatsJson简单一些就用了JSONObject jsonObject = new JSONObject();jsonObject.put("username", "admin");jsonObject.put("password", "123456");StringEntity stringEntity = new StringEntity(jsonObject.toString()); // HttpEntity的实现类// 还要指定一下编码方式和传递的数据格式stringEntity.setContentEncoding("utf-8");stringEntity.setContentType("application/json");httpPost.setEntity(stringEntity);// 发送请求CloseableHttpResponse response = httpClient.execute(httpPost);System.out.println("相应状态码为: " + response.getStatusLine().getStatusCode());HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity); // 好看System.out.println("响应数据为: " + result);response.close();httpClient.close();}
}

日志使用

日志可以代替System.out,因为其可以更方便而且不用全局修改,可以灵活选择记录到文件或打印控制台

级别

我们可以进行日志级别的调整,比如:

if("1".equals(log)){log.debug("不重要的日志......");// 开始执行业务流程try {//关键点log.info("关键日志........"); // 精细一些//容易出问题点aa.bb();log.warn("调用 aa.bb() 方法后注意可能后续版本废除........"); // 这个在里边写即可}catch (Exception e){log.error("错误日志......"+e.getMessage()); // 发生错误记录下原因// 如果是fatal类型的致命错误程序可能直接崩溃}
}
# 如果哪个包、哪个类不说日志级别,就用默认root
logging.level.root=debug // 可以调整整个项目级别,这样信息更详细  
logging.level.com.rjgc.boot=error // 精细某个包下的级别,只打印error即以上  如果不想打印写成off即可
# 设置日志组
logging.group.base=com.rjgc.service,com.rjgc.dao# 按组批量设置日志级别
logging.level.biz=debug # 可以打印更详细的信息
logging.level.web=debug # 这个是系统自动分好组了,可以去了解下
输出到文件

# 当两个都配置时优先以文件名为准
# 表示当前项目所在的根文件夹下生成一个指定名字的日志文件
logging.file.name=boot.log
# 表示在生成一个文件夹,里边的名称其实为spring.log
#logging.file.path=D://aaa.log

当然我们也可以写logback.xml即可,而且这时以我们写得为准,不在以yml为准了,当然如果我们换框架的话(通过依赖切换日志),xml就没用了。

其实我们xml中主要配置这些即可:

1、配置(日志输出到文件、打印日志级别(可以分组))

2、记录日志(合适的时候选择合适的级别进行日志记录。比如log.error/info/debug)

3、文件归档和滚动切割其实默认都写好了,满足条件自动触发,我们不写也是可以的

http://www.dtcms.com/a/316410.html

相关文章:

  • oelove奥壹新版v11.7旗舰版婚恋系统微信原生小程序源码上架容易遇到的几个坑,避免遗漏参数白屏显示等问题
  • Electron-updater + Electron-builder + IIS + NSIS + Blockmap 完整增量更新方案
  • 物联网后端系统架构:从基础到AI驱动的未来 - 第十章:AI促进IOT领域发生革命式发展
  • WebRTC采集模块技术详解
  • 阿里云百炼平台创建智能体-上传文档
  • Mysql使用Canal服务同步数据->ElasticSearch
  • Linux-环境变量
  • Transformer的并行计算与长序列处理瓶颈
  • 视频转二维码在教育场景中的深度应用
  • QT跨线程阻塞调用方法总结
  • SpringMVC 6+源码分析(四)DispatcherServlet实例化流程 3--(HandlerAdapter初始化)
  • 【机器学习深度学习】 知识蒸馏
  • 2.4.9-2.5.1监控项目工作-控制质量-确认范围-结束项目或阶段
  • 三极管三种基本放大电路:共射、共集、共基放大电路
  • 后量子时代已至?中国量子加密技术突破与网络安全新基建
  • 无监督学习聚类方法——K-means 聚类及应用
  • CMAQ空气质量模式实践技术及案例分析应用;CMAQ空气质量模式配置、运行
  • Go语言实战案例:使用sync.Mutex实现资源加锁
  • 一次完整的 Docker 启动失败排错之旅:从 `start-limit` 到 `network not found
  • 三坐标测量机全自研扫描测头+标配高端性能,铸就坚实技术根基
  • 如何实现一个简单的基于Spring Boot的用户权限管理系统?
  • layernorm backward CUDA优化分析
  • Spring Boot 集成 ShardingSphere 实现读写分离实践
  • MySQL数据类型介绍
  • langchain入门笔记01
  • 【nvidia-B200】Ubuntu 22.04 中安装指定版本的 NVIDIA 驱动时出现依赖冲突
  • 亚马逊否定投放全攻略:精准过滤无效流量的底层逻辑与实战体系
  • 【教育教学】人才培养方案制定
  • Erlang notes[1]
  • 贝叶斯统计从理论到实践