精华内容
下载资源
问答
  • JAVA框架

    千次阅读 2019-10-15 10:42:43
    AOP(Aspect Oriented Program):面向切面编程(实现周边功能,比如性能统计,日志,事务管理等) 方法 : 新建对象的set和get方法 新建bean configuration … - applicationContext.xml、并在这个xml配置文件...

    Spring

    Spring是一个基于IOC和AOP的结构J2EE系统的框架,对象的生命周期由Spring来管理
    IOC(Inversion Of Control):控制反转
    DI(Dependency Inject): 依赖注入
    AOP(Aspect Oriented Program):面向切面编程(实现周边功能,比如性能统计,日志,事务管理等)
    方法
    新建对象的set和get方法
    新建bean configuration … - applicationContext.xml、并在这个xml配置文件(SpringIOC容器)中使用Bean创建对象并且给对象及其属性赋值

        <bean id="id(唯一)" class="包名.类名">
            <property name="属性名" value="属性值" />
            <property name="属性名" ref="属性映射(对应对象的id值)" />
        </bean>
    

    然后通过主函数中 ApplicationContext取出对象

     public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            类 实例名 = (类名) context.getBean("id");
    

    三种依赖注入方式:set方法注入(同上)、构造方法注入( < constructor-arg value(ref)=“value” type=“type” index=“0” name=“属性名”>< /constructor-arg>)、p命名空间注入(引入p命名空间、简单类型:p:属性名=“属性值”、引用类型(除了String外):p:属性名-ref=“引用的id”)
    IOC容器赋值:如果是简单类型(8个基本+String),value; 如果是对象类型,ref=“需要引用的id值”,因此实现了对象与对象之间的依赖关系、赋值null< property name=“name” > < null/>< /property>、赋值空值< property name=“name” > < value>< /value> < /property>、集合类型赋值proprety中使用< set> < list> < array>等,可以混着用。注意< map> 中使用< entry> < key>< value>
    value与< value>注入方式的区别:value必须使用双引号,不能使用type属性,只能采用实体引用处理特殊符号,后者可以使用<![CDATA[ ]]>标记处理特殊符号
    自动装配:bean中的autowire只适用于ref类型,autowire=“byName|byType|constructor|no”、实现全局自动装配default-autowire=“byName|by…”、注解形式的自动装配@Autowired、默认是byType、 @Qualifier(“id”)、改为byName
    使用注解
    ApplicationContext中增加扫描包

    < context:component-scan base-package="包名">
    </ context:component-scan>
    

    对应包中加入注解:@Component(“id”)细化:(dao层注解:@Repository)(service层注解:@Service)(控制器层注解:@Controller)
    使用注解实现声明式事务:实现数据库的事务
    配置:配置数据库相关、配置事务管理器txManager、增加对事务的支持;使用:方法明前增加注解@Transactional并增加其属性和方法
    AOP:前置通知、后置通知、环绕通知、异常通知
    实现方式:实现类、实现接口、注解、配置
    接口形式AOP:实现接口并重写方法(前置MethodBeforeAdvice)(后置AfterReturningAdvice)(环绕MethodInterceptor)(异常ThrowsAdvice)、 ApplicationContext中加入业务类方法的对象和通知类方法的对象、增加配置:

    < aop:config>
    		<!-- 切入点(连接线的一端:业务类的具体方法) -->
    		< aop:pointcut expression="execution(public * 业务类名.方法名(参数类型))"   id="poioncut"/>
    		<!-- (连接线的另一端:通知类) -->
            < aop:advisor advice-ref="通知类名"  pointcut-ref="poioncut" />
    </aop:config>
    

    环绕通知:可以获取目标方法的全部控制权,目标方法的一切信息可以通过invocation参数获取到,底层是通过拦截器实现的、环绕通知类实现MethodInterceptor接口和invoke方法, result = invocation.proceed() ;中result获得返回值,invocation.proceed()实现业务类方法执行,try中实现前置通知和后置通知、catch中捕获异常并实现异常通知
    注解形式AOP:@Component引入注解识别@Aspect修饰类形成通知类 @Before(pointcut=“execution(public * 业务类名.方法名(参数类型))”)修饰方法形成通知前置方法、后置@AfterReturning(returning=“returningValue”)、异常@AfterThrowing(throwing=“e”)、环绕@Around、方法中的参数可以使用JoinPoint jp、返回值参数Object returningValue、返回异常参数NullPointerException e、环绕通知中参数使用ProceedingJoinPoint jp
    配置:增加扫描包、开启AOP注解代理< aop:aspectj-autoproxy>< /aop:aspectj-autoproxy>
    配置形式AOP:写通知类logSchema及其通知方法,并将通知类和业务类加入到IOC容器中,applicationContext配置:

    <aop:config>
    		<!-- 切入点(连接线的一端:业务类的具体方法) -->
    		<aop:pointcut expression=""   id="pcShema"/>
    		<!-- 原方法(连接线的另一端:通知类)<aop:advisor advice-ref="logSchea"  pointcut-ref="pcShema" />-->
    		 <!-- schema方式 -->
    		 <aop:aspect ref="logSchema">
    		 <!-- 连接线:连接业务和通知before -->
    		 <aop:before method="before" pointcut-ref="pcShema"/>
    		 <!-- 连接线:连接业务和通知afterReturning -->
    		 <aop:after-returning method="afterReturning" returning="returnValue" pointcut-ref="pcShema"/>
    		 <!-- 连接线:连接业务和通知Exception -->
    		 <aop:after-throwing method="Exception" pointcut-ref="pcShema" throwing="e"/>
    		 <!-- 环绕通知 -->
    		 <aop:around method="around" pointcut-ref="pcShema" />
             </aop:aspect>
    </aop:config>
    

    WEB项目整合Spring
    IOC初始化:当服务启动时(tomcat),通过监听器将SpringIOC容器初始化一次(该监听器 spring-web.jar已经提供)web.xml配置、推荐方法:

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
      			classpath:applicationContext.xml,
      			<!--.默认约定的位置:WEB-INF/applicationContext.xml-->
      			classpath:applicationContext-*.xml
      	</param-value>
    </context-param>
    

    将applicationContext分层:cotroller、service、dao并分别实例化和依赖注入
    实现Web容器和IOC容器的整合(手动方式):从IOC容器中求出实例对象、一般在servlet初始化中实现

    public void init() throws ServletException  {
    		//ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext-Service");
    		ApplicationContext context=WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
    		studentService=(IStudentService)context.getBean("studentService");
    		<!--类 实例名 = (类名) context.getBean("id");-->
    	}
    

    最后完善web项目

    Spring进阶

    注解形式存放bean
    (配置类)必须有@Configuration注解

    //存bean
    @bean
    //取bean
    ApplicationContext context  = new AnnotationConfigApplicationContext(配置类.class) ;
    

    三层组件:(@Controller、@Service、@Repository -> @Component)+配置文件或者注解扫描器@component-scan(只对三层组件负责)、扫描器指定规则(FilterType(ANNOTATION,ASSIGNABLE_TYPE,CUSTOM))
    非三层组件(转换器): @Bean+方法的返回值、@import(直接编写到@Import中且id值是全类名、——自定义ImportSelector接口的实现类,通过selectimports方法实现(方法的返回值就是要纳入IoC容器的Bean),并且告知程序自己编写的实现类@Import({Orange.class,MyImportSelector.class})、——编写ImportBeanDefinitionRegistrar接口的实现类,重写方法
    @Import({Orange.class,MyImportSelector.class,ImportBeanDefinitionRegistrar.class})并且告知程序自己编写的实现类)、FactoryBean(工厂Bean、准备bean实现类和重写方法,注册bean注册到@Bean中)
    bean的作用域:scope(singleton)单例默认值、@scope(prototype)多实例(singleton:容器在初始化时,就会创建对象(唯一的一个),支持延迟加载@Lazy、prototype:容器在初始化时,不创建对象;只是在每次使用时创建对象)
    条件注解:可以让某一个Bean在某些条件下加入Ioc容器,其他情况下不加入容器,通过实现condition接口
    Bean的生命周期:创建(new …)、初始化(赋初值)、 …、销毁
    @Bean+返回值方式

    @Bean(value="stu",initMethod = "myInit",destroyMethod = "myDestroy") 
    

    三层组件功能性注解方式:将响应组件加入@Component(value="")注解、 给初始化方法加@PostConstruct、给销毁方法加@PreDestroy
    三次组件接口方法:InitializingBean初始化、DisposableBean 销毁并实现其中的方法
    接口BeanPostProcessor:拦截了所有中容器的Bean,并且可以进行bean的初始化 、销毁
    BeanFactoryPostProcessor:bean被实例化之前
    BeanDefinitionRegistryPostProcessor:bean被加载之前
    具体顺序:BeanDefinitionRegistryPostProcessor(a) ->加载bean->BeanFactoryPostProcessor(b)->实例化bean->BeanPostProcessor
    自动装配
    @Autowired根据类型自动注入、结合@Qualifier("")根据名字注入、默认值@primary、@Autowired(required=false)修改未匹配为null
    三层组件:如果@Autowired在属性前标注则不调用set方式;如果标注在set前面则调用set方式;不能放在方法的参数前
    Bean+返回值:@Autowired可以在方法的参数前(也可以省略)、方法前(如果只有一个有参构造方法则@Autowired也可以省略)
    @Resource根据名字自动注入,没有名字然后根据类型
    @Inject默认根据类型匹配(引入jar包)
    底层组件:实现Aware的子接口,从而获取到开发的组件对象,对对象进行操作
    环境切换:bean前@Profile说明条件注入
    激活
    1.运行环境中设置参数-Dspring.profiles.active=@Profile环境名
    2.硬编码:IoC容器在使用时必须refresh(),需要设置保存点|配置类的编写处

    //注解方式
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext() ;
    ConfigurableEnvironment environment = (ConfigurableEnvironment)context.getEnvironment();
    environment.setActiveProfiles("@Profile环境名");
    //保存点
    context.register(配置类名.class);
    context.refresh();
    

    监听器:可以监听事件,监听的对象必须是ApplicationEvent自身或其子类/子接口
    1.监听类实现ApplicationEvent接口
    2.监听类注解形式申明是监听方法

    @Component
    public class MyListener {
    //本方法是一个监听方法
    @EventListener(classes = {ApplicationEvent.class})
    public void myListenerMethod(ApplicationEvent event){
    System.out.println("--0000000--------"+event);
        }
    }
    

    SpringMVC

    SpringMVC构建Web应用程序的全功能MVC模块
    配置与使用
    JSP发出请求
    将Servlet的访问通过web.xml配置文件拦截请求,交给SpringMVC处理、快速创建:alt+/ dispatcherServlet

    	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
    	<servlet>
    		<servlet-name>springDispatcherServlet</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<!--默认路径/WEB-INF/springDispatcherServlet-servlet.xml-->
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>classpath:springmvc.xml</param-value>
    		</init-param>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    
    	<!-- Map all requests to the DispatcherServlet for handling -->
    	<servlet-mapping>
    		<servlet-name>springDispatcherServlet</servlet-name>
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>
    

    springmvc.xml中配置扫描包和视图解析器(用于解析返回值地址并通过请求转发实现页面跳转)

    	<!--配置视图解析器(InternalResourceViewResolver)  -->
    	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="prefix" value="/views/"></property>
    		<property name="suffix" value=".jsp"></property>
    	</bean>
    	<!--view-name会被视图解析器加上前缀和后缀  -->
    

    控制器中添加注解@controller(实现扫描包扫描)@RequestMapping(映射匹配类、类之前)、@RequestMapping(value,method,params)(映射匹配方法、方法之前)(可以增加个属性和约束条件对请求参数进行约束)、@PathVariable获取动态参数
    REST风格:GET :查、POST :增、DELETE :删、PUT :改
    springmvc实现给普通浏览器增加 put|delete请求方式 :put|post请求方式的步骤:
    1.web.xml增加HiddenHttpMethodFilte过滤器

    <filter>
    			<filter-name>HiddenHttpMethodFilte</filter-name>
    			<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
    			<filter-name>HiddenHttpMethodFilte</filter-name>
    			<url-pattern>/*</url-pattern>
    </filter-mapping>
    

    2.表单提交必须是post方法且通过隐藏域的value值设置实际的请求方式DELETE|PUT (< input type=“hidden” name="_method" value=“DELETE”/>)
    3.控制器实现提交方式约束匹配、参数获取和功能执行
    处理数据模型:跳转时需要带数据和页面(ModelAndView、ModelMap、Map、Model -数据放在了request作用域)
    ModelAndView:

    public ModelAndView testModelAndView() {
    ModelAndView mv = new ModelAndView("jsp");
    mv.addObject("属性名", 属性值或对象);
    return mv;}
    

    ModelMap:

    public String testModelMap(ModelMap mm) {
    mm.put("属性名", 属性值或对象);
    return "jsp";}
    

    Map:

    public String testMap(Map<String,Object> m) {
    m.put("属性名", 属性值或对象);
    return "jsp";}
    

    Model:

    public String testMode(Model model) {
    model.addAttribute("属性名", 属性值或对象);
    return "jsp";}
    

    @SessionAttributes(value\type):实现将数据放在request中的同时也将数据放在session中
    @ModelAttribute:通过@ModelAttribute修饰的方法,会在每次请求前先执行;并且该方法的参数map.put()可以将对象放入即将查询的参数中,可以进行属性值的修改
    视图的顶级接口:Views
    视图解析器的顶级接口:ViewsResolver
    国际化:针对不同地区、不同国家进行不同的显示 (创建资源文件、配置springmvc.xml并加载资源文件、通过jstl使用国际化)

    <!--加载国际化资源文件-->
    	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    	<property name="basename" value="i18n"></property>
    	</bean>
    

    请求方法改变:return “redirect:/views/success.jsp”;
    直接跳转(不通过控制器):< mvc:view-controller path=“handle/a” view-name=“success” /> < mvc:annotation-driven></ mvc:annotation-driven>
    静态资源访问:(MVC交给Servlet处理)加入配置:

    <!--该注解会让springmvc:接收一个请求,并且该请求没有对应@requestmapping时,将该请求交给服务器默认的servlet去处理(直接访问)-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>
    <mvc:annotation-driven></mvc:annotation-driven>
    

    类型转换:Spring自带一些常见的类型转换器、自定义类型转换器MyConverter(编写自定义类型转换类并引入接口Converter、配置将MyConverter加入到springmvc中、测试转换器)@RequestParam(“name”)是触发转换器的桥梁
    格式化:springMVC配置数据格式化注解所依赖的bean+通过注解使用@DateTimeFormat(pattern=“yyyy-MM-dd”)@NumberFormat(parttern="###,#") 、错误消息处理(参数BindingResult result返回前面参数的错误信息,可以放在map里面传递到前端打印)、数据检验:JSR303和Hibernate Validator(jar包+配置(自动加载一个LocalValidatorFactoryBean类)+注解(给校验的对象前增加@Valid))
    JSON:@ResponseBod修饰的方法,会将该方法的返回值以一个json数组的形式返回给前台
    文件上传:配置MultipartResolver将其加入springIOC容器(可以设置属性)、处理方法中(参数@RequestParam(“file”) MultipartFile file获取前端传过来的文件数据)根据IO方法写输入输出流
    拦截器:原理和过滤器原理相同(编写拦截器实现HandlerInterceptor接口、处理请求响应和渲染过程+将自己写的拦截器配置到springmvc中(< mvc:interceptor>))
    异常处理:如果一个方法用于处理异常,并且只处理当前类中的异常:@ExceptionHandler({Exception.class});如果一个方法用于处理异常,并且处理所有类中的异常: 类前加@ControllerAdvice、 处理异常的方法前加@ExceptionHandler;自定义异常显示页面@ResponseStatus;
    通过配置实现的异常处理:SimpleMappingExceptionResolver

    <bean  class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    		<!-- 如果发生异常,异常对象会被保存在  exceptionAttribute的value值中;并且会放入request域中;异常变量的默认值是exception-->
    		<!--<property name="exceptionAttribute" value="exception"></property>-->
    			<property name="exceptionMappings">
    					<props>
    						<!-- 相当于catch(ArithmeticException exception){ 跳转:error页面 } -->
    						<prop key="java.lang.ArithmeticException">error</prop>
    						<prop key="java.lang.NullPointerException">error</prop>
    					</props>
    			</property>
    </bean>
    

    Mybatis

    Mybatis可以简化JDBC操作和实现数据的持久化
    ORM:Object Relational Mapping开发人员 像操作对象一样操作数据库表
    SqlSessionFactory -> SqlSession ->StudentMapper ->CRUD
    配置与使用
    引入mybatis-jar包和数据库jar包
    配置mybatis
    conf.xml:配置数据库信息和需要加载的映射文件(运行环境默认是development、可以通过build的第二参数指定数据库环境)

    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <!--如果使用的事务方式为jdbc,则需要手工commit提交,即session.commit()-->
    <dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
    </configuration>
    

    表 - 类
    映射文件xxMapper.xml :增删改查标签< select>

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog" parameterType="int>
    <!--parameterType:输入参数的类型,resultType:返回类型、输入参数parameterType 和输出参数resultType,在形式上都只能有一个、如果输入参数:是简单类型(8个基本类型+String)是可以使用任何占位符,#{xxxx}、如果是对象类型,则必须是对象的属性#{属性名}-->
    select * from Blog where id = #{id}
    </select>
    </mapper>
    

    测试类
    session.selectOne(“需要查询的SQL的namespace.id”,“SQL的参数值”)

    public static void main(String[] args) throws IOException {
    		//加载MyBatis配置文件(为了访问数据库)
    		Reader reader = Resources.getResourceAsReader("conf.xml") ;
    		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader) ;
    		//session - connection
    		SqlSession session = sessionFactory.openSession() ;
    		String statement = "org.lanqiao.entity.personMapper.queryPersonById" ;
    		Student person = session.selectOne( statement,1 ) ;
    		System.out.println(person);
    		session.close(); 
    

    mapper动态代理方式CRUD:省略掉statement,即根据约定直接可以定位出SQL语句
    配置环境:mybatis.jar/ojdbc.jar、conf.xml、mapper.xml
    新建类和方法实现接口必须遵循以下约定:
    1.方法名和mapper.xml文件中标签的id值相同
    2.方法的输入参数和mapper.xml文件中标签的parameterType类型一致 (如果mapper.xml的标签中没有parameterType,则说明方法没有输入参数)
    3.方法的返回值和mapper.xml文件中标签的resultType类型一致(无论查询结果是一个还是多个(student、List< Student>),在mapper.xml标签中的resultType中只写 一个(Student);如果没有resultType,则说明方法的返回值为void)
    4.namespace的值就是接口的全类名( 接口 - mapper.xml 一 一对应)
    5.测试中使用

    T t = session.getMapper(接口.class) ;
    返回类 返回值 = t.接口中的方法(参数) ;//接口中的方法->SQL语句
    StudentMapper studentMapper = session.getMapper(StudentMapper.class) ;
    studentMapper.方法();
    

    6.优化:将配置信息单独放入db.properties文件中然后再动态引入、MyBatis全局参数在conf.xml中设置、设置别名
    类型转换器(自定义):实现java类型和jdbc类型的相互转化
    1.创建转换器:实现TypeHandler接口或者继承BaseTypeHandler
    2.配置conf.xml< typeHandlers>和mapper.xml与类对应(resultMap可以实现类型转换和属性-字段的映射关系)
    输入参数:parameterType(简单类型:8个基本类型+String、.对象类型)、取值方法(#{}、${}都可以获取对象值和嵌套类型对象)(不同之处:简单类型的标识符、#{}会给String类型加上单引号、#{}可以防止sql注入)、参数类型为HashMap(用map中key的值匹配占位符#{stuAge},如果匹配成功就用map的value替换占位符)、参数为多个数据(可以使用[arg3, arg2, arg1, arg0, param3, param4, param1, param2]、在接口中通过@Param(“sNo”) 指定sql中参数的名字)
    输出参数:resultType、resultMap(实体类的属性、数据表的字段: 类型、名字不同时、除此之外:实体类属性和数据字段不匹配时可以使用resultType+HashMap)、(输出类型为简单类型、对象类型、集合类型(resultType依然写集合的元素类型)、hashmap类型(本身是一个集合,可以存放多个元素))
    输出参数为Hashmap

    //根据@MapKey("stuNo")知道 Map的key是stuNo
        @MapKey("STUNO")  //oracle的元数据(字段名、表名 )都是大写
        HashMap<Integer,Student> queryStudentsByHashMap();
    //程序根据select的返回值 知道map的value就是 Student ,
        <select id="queryStudentsByHashMap" resultType="HashMap">
             select stuNo ,stuName ,stuAge  from student
        </select>
    

    储存过程:数据库新建储存过程、Mapper中使用CALL调用存储过程(通过statementType="CALLABLE"设置SQL的执行方式是存储过程、存储过程的输入参数需要通过HashMap来指定)、使用过程(通过hashmap的put方法传入输入参数的值、通过hashmap的Get方法获取输出参数的值。)
    动态sql语句:where-if(只能处理第一个有效的and)

    <select id="queryStuByNOrAWishSQLTag" 	 parameterType="student"	resultType="student" >
    		select stuno,stuname,stuage from student
    		<where>
    			<!-- <if test="student有stuname属性 且不为null"> -->
    			<if test="stuName !=null and stuName!=''  "> 
    				and stuname = #{stuName}
    			</if>
    			<if test="stuAge !=null and stuAge!=0  "> 
    				 and stuage = #{stuAge}
    			</if>
    		</where>
    	</select>
    

    foreach:< foreach>迭代的类型:数组、对象数组、集合、属性(包括属性类)、(简单类型的数组:在mapper.xml中必须用array代替该数组、集合类型的数组:必须用list代替该集合、对象数组:必须用object代替该集合、使用时用对象.属性)

    	<select id="queryStudentsWithNosInGrade"  parameterType="grade" resultType="student">
    	  	select * from student 
    	  	<where>
    	  		 <if test="stuNos!=null and stuNos.size>0">
    	  		 	<foreach collection="stuNos" open=" and  stuno in (" close=")" 
    	  		 		item="stuNo" separator=",">   
    	  		 		#{stuNo}
    	  		 		<!--此时stuNo为对象的属性,本身是一个集合-->
    	  		 	</foreach>
    	  		 </if>
    	  	</where>
    	</select>
    

    refid:(提取并引用sql语句)
    trim:增删改中有效使用

    //添加where并处理拼接sql中开头或结尾的第一个和最后一个and
    <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
    

    关联查询:一对一(业务扩展类核心:用resultType指定类的属性包含多表查询的所有字段、resultMap:通过属性成员建立起对应关系,对象成员使用 association映射、javaType指定该属性的类型(一对一:association,一对多:collection))一对多:resultMap:通过属性成员建立起对应关系,对象成员使用 collection映射、ofType指定该属性中元素的类型
    日志:通过日志信息,详细的阅读mybatis执行情况
    log4j.jar包、开启日志conf.xml、编写配置日志输出文件log4j.properties

    <settings>
    <!-- 开启日志,并指定使用的具体日志 -->
    <setting name="logImpl" value="LOG4J"/>
    </settings>
    

    延迟加载:实现部分查找和懒加载
    开启延迟加载conf.xml、配置mapper.xml(查询对象的sql是通过select属性指定的关联mapper,并且通过column指定外键名)、如果增加了mapper.xml,要修改conf.xml配置文件(将新增的mapper.xml加载进去)

    <settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 关闭立即加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    

    查询缓存
    一级缓存:MyBatis默认开启一级缓存,如果用同样SqlSession对象查询相同的数据,则只会在第一次查询时向数据库发送SQL语句,并将查询的结果 放入到SQLSESSION中(作为缓存在);后续再次查询该同样的对象时,则直接从缓存中查询该对象即可(即省略了数据库的访问)、commit语句会清理数据库缓存
    二级缓存:只要产生的Mapper对象来自于同一namespace,则这些对象共享二级缓存
    conf.xml中配置二级缓存< setting name=“cacheEnabled” value=“true”/>
    mapper.xml中声明开启 < cache/>
    Mapper的对应类、对应类的父类和级联属性都要需要实现序列化接口Serializable
    触发将对象写入二级缓存的时机为SqlSession对象的close()方法
    禁用二级缓存:select标签中 useCache=“false”
    清理二级缓存:select标签中 flushCache="true"或者 其他操作的commit(),不能用查询的commit();
    ehcache二级缓存整合:jar包、编写ehcache配置文件 Ehcache.xml、Mapper.xml中开启

    <cache  type="org.mybatis.caches.ehcache.EhcacheCache">
    <property name="maxElementsInMemory" value="2000"/>
    <property name="maxElementsOnDisk" value="3000"/>
    </cache>
    

    逆向工程:表、类、接口、mapper.xml四者密切相关,根据表生成其他三个
    jar包:mybatis-generator-core.jar、mybatis.jar、ojdbc.jar
    逆向工程的配置文件generator.xml
    执行

    public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
            //配置文件
    		File file = new File("src/generator.xml") ;
    		List<String> warnings = new ArrayList<>();
    		ConfigurationParser cp = new ConfigurationParser(warnings);
    		Configuration config = cp.parseConfiguration(file);
    		DefaultShellCallback callBack = new DefaultShellCallback(true);
    		//逆向工程的核心类
    		MyBatisGenerator generator = new MyBatisGenerator(config, callBack,warnings  );
    		generator.generate(null);
    	}
    

    增加conf.xml和db.properties文件
    使用query by CriteriaQBC

    Mybatis进阶

    数据库版本切换
    conf.xml切换environment并配置Provider别名

    <!-- 配置数据库支持类-->
    <databaseIdProvider type="DB_VENDOR">
            <property name="MySQL" value="mysql" />
            <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    

    mapper.xml写不同数据SQL语句,配置databaseId=“Provider别名”

    <select id="queryStudentByNoWithONGL" parameterType="student" resultType="student" databaseId="oracle">
    

    注解方式
    将sql语句写在接口的方法上@Select("")
    将接口的全类名写入< mapper>,或者批量写入

    <mappers>
    	<!--以下可以将com.yanqun.mapper 包中的注解接口和xml全部一次性引入 -->
        <package name="com.yanqun.mapper" />
    </mappers>
    

    增删改返回值可以是void、Integer、Long、Boolean,都不需要在mapper.xml中设置返回值类型
    事务提交

    //手动提交:
      sessionFactory.openSession();
      session.commit();
    //自动提交:每个dml语句 自动提交
      sessionFactory.openSession(true);
    

    自增语句

    //mysql设置自增数据为Integer类型的null,设置返回的自增数据,实现回写
    useGeneratedKeys="true" keyProperty="number"
    //oracle通过序列模拟实现(before和after方式)
    

    鉴别器 : 对查询结果进行分支处理: 如果是a年级则真名,如果b年级显示昵称,由于字段不一致需要在resultMap中写

    <discriminator javaType="string"  column="gname">
        <case value="a" resultType="com.yanqun.entity.Student" >
             <result  column="sname" property="stuName"/>
        </case>
        <case value="b" resultType="student">
             <result  column="nickname" property="stuName"/>
        </case>
    </discriminator>
    

    架构分析
    接口层:广义接口(增删改查)
    数据处理层:参数处理,sql解析与执行,处理结果
    框架支撑层:事务管理,缓存机制,连接池管理
    引导层:XML配置,注解方式
    源码分析
    获取SqlSessionFactory对象
    (通过parseConfiguration()在configuration标签设置了properties、settings、environments等属性标签)(所有的配置信息放在Configuration对象中)(解析所有的Mapper.xml文件(分析其中的增删改查标签)成MappedStatement对象并放在Configuration中)(SqlSessionFactory对象 ->DefaultSqlSessionFactory ->Configuration ->包含了一切配置信息)
    获取SqlSession对象
    (根据不同的类型execType,产生不同的 Executorconfiguration.newExecutor(tx, execType) ->SimpleExecutor、通过装饰模式将刚才产生的executor包装成一个更加强大的executor:executor = (Executor) interceptorChain.pluginAll(executor)、返回DefaultSqlSession(configuration,executor,事务))
    (SqlSession ->openSession()->openSessionFromDataSource()->DefaultSqlSession对象->执行SQL)
    获取XxxMapper对象
    (代理接口中的方法、mapper.xml中的< select>等标签)(SqlSession(configuration,executor,事务)、代理接口的对象(MapperInterface)、methodCache(存放查询缓存,底层是CurrentHashMap))
    执行< select>等标签中定义的SQL语句
    (MyBatis底层在执行CRUD时 可能会涉及到四个处理器:处理对象ParameterHandler的处理器:StatementHandler、处理参数的处理器:ParameterHandler、类转化器处理器:TypeHandler、处理结果集的处理器:ResultSetHandler)(动态代理:MapperProxy对象、执行增删改查->MapperProxy/invoke()–>InvocationHandler :JDK动态代理接口)(实际调用增删改查的方法:mapperMethod.execute(sqlSession,args) )(处理增删改查方法的参数:method.convertArgsToSqlCommandParam(args))(boundSql :将我们写的SQL和参数值进行了拼接后的对象,即最终能被真正执行的SQL)(通过Executor执行sql语句、底层执行方法:PreparedStatement的execute())
    自定义插件
    四大核心对象:都涉及到了拦截器用于增强并且包含了该增强操作
    StatementHandler、ParameterHandler、ResultSetHandler、Executor
    步骤
    编写拦截器

    //目標方法target
            Object target = invocation.getTarget();
    //目标对象target的包装后的产物MetaObject
            MetaObject metaObject = SystemMetaObject.forObject(target);
    //metaObject.getValue获取参数
            Object value = metaObject.getValue("parameterHandler.parameterObject");
    //metaObject.setValue设置参数
            metaObject.setValue("parameterHandler.parameterObject",2);
    //放行
            Object proceed = invocation.proceed();
            return proceed;
    

    编写签名注解

    @Intercepts({
    //@Signature(type = StatementHandler.class , method ="query",args = {Statement.class, ResultHandler.class})
    @Signature(type = StatementHandler.class , method ="parameterize",args = {Statement.class})
    })
    

    conf.xml配置

    <plugins>
            <plugin interceptor="com.yanqun.my.interceptors.MyInterceptor">
                <property name="name" value="zs"/>
                <property name="age" value="23"/>
            </plugin>
    </plugins>
    

    批量操作:预编译一次sessionFactory.openSession(ExecutorType.BATCH)、不推荐拼接SQL方式
    分页插件:PageHelper
    引入jar(jsqlparser-0.9.5.jar、pagehelper-5.1.8.jar)
    配置conf.xml插件
    执行PageHelper.startPage(当前页, 每页数据)
    使用页面信息(PageInfo、Page对象)
    插件:Mybatis-Plus、通用Mapper

    Maven

    Maven是一个基于Java平台的自动化构建工具–将java、js、jsp等各个文件进行筛选、组装,变成一个可以直接发布的项目
    http://www.mvnrepository.com
    清理:删除编译的结果,为重新编译做准备
    编译:java->class
    测试:针对于项目中的关键点进行测试,亦可用项目中的测试代码去测试开发代码;
    报告:将测试的结果进行显示
    打包:将项目中包含的多个文件 压缩成一个文件, 用于安装或部署:(java项目-jar、web项目-war)
    安装:将打成的包放到本地仓库,供其他项目使用
    部署:将打成的包放到服务器上准备运行
    下载配置maven
    使用maven
    mvn compile 只编译main目录中的java文件
    mvn test 测试
    mvn package 打成jar/war
    mvn install 将开发的模块放入本地仓库供其他模块使用(放入的位置是通过gav决定)
    mvn clean 删除target目录(删除编译文件的目录)
    运行mvn命令必须在pom.xml文件所在目录
    目录结构

    -src				
    	--main		          程序功能代码
    		--java		      java代码  
    		--resources       资源代码、配置代码
    	--test			      测试代码
    		--java			
    		--resources	
    pom.xml                   项目对象模型
    

    POM(项目对象模型):Maven工程的基本工作单元XML文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖

    	<groupId>域名翻转.项目名</groupId>
    	<groupId>org.lanqiao.maven</groupId>
    
    	<artifactId>子模块名</artifactId>
    	<artifactId>HelloWorld</artifactId>
    
    	<version>版本号</version>
    	<version>0.0.1-SNAPSHOT</version>
    

    依赖
    在maven项目中如果要使用一个当时不存在的Jar或模块,则可以通过依赖实现(例如依赖测试类)

    <dependency>
    	<groupId>junit</groupId>
    	<artifactId>junit</artifactId>
    	<version>4.0</version>
    	<scope>test</scope>
    </dependency>
    

    依赖排除:

    <exclusions>
    <exclusion>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    </exclusion>
    </exclusions>
    

    依赖关联:
    A.jar->B.jar->C.jar
    要使 A.jar ->C.jar:当且仅当 B.jar依赖于C.jar的范围是compile
    依赖原则:路径最短原则(防止冲突)
    路径相同距离:同一个pom文件后覆盖前、不同pom文件前覆盖后
    统一编号和版本:

    <properties>  
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <xx>版本号</xx>
    </properties>  
     //使用动态版本
     <version>${xx} </version>
    

    生命周期
    clean lifecycle :清理 pre-clean、clean、post-clearn
    default lifecycle :默认(常用)
    site lifecycle:站点 pre-site、site、post-site site-deploy
    继承
    父->子工程,可以通过父工程 统一管理依赖的版本
    父工程pom方式打包
    在父工程pom中编写依赖

    <dependencyManagement>
      	<dependencies>
      		<dependency>
    

    给当前工程继承父工程

    <parent>
    <!-- 1加入父工程坐标gav -->
    <groupId>org.lanqiao.maven</groupId>
    <artifactId>B</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!-- 2当前工程的Pom.xml到父工程的Pom.xml之间的相对路径 --> 
    <relativePath>../B/pom.xml</relativePath>
    </parent>
    

    在子类中需要声明使用哪些父类的依赖

    <dependency>
    <!-- 声明:需要使用到父类的junit(只需要ga) -->
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    </dependency>
    

    聚和
    聚合可以将拆分的多个子工程合起来共同操作(前置关联工程的install操作)
    新建总工程中配置:只能配置在打包为pom方式的maven中

    <modules>
    <!--项目的根路径  -->
    <module>../Maven1</module>
    <module>../Maven2</module>
    </modules>
    

    配置完聚合之后,操作总工程会自动操作改聚合中配置过的工程
    部署Web
    新建Maven项目并打war包
    pom中配置servlet-api

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
        <scope>provided</scope>
    </dependency>
    

    补全路径名:webapp->WEB-INF->web.xml
    实际开发中开发人员将自己的项目开发完毕后打成war包(package) 交给实施人员去部署

    SpringBoot

    springboot可以快速开发微服务模块,springboot将各个应用/三方框架设置成了一个个“场景”starthttps://spring.io可以自动生成SpringBoot的文件项目
    准备JAVA和MAVEN的环境、实现springboot开发工具、目录结构resources(static:静态资源(js css 图片 音频 视频)
    、templates:模板文件(模版引擎freemarker ,thymeleaf;默认不支持jsp)、application.properties: 配置文件对端口号等服务端信息进行配置)
    注解
    @SpringBootApplication:springboot的主配置类
    @Configuration:表示“配置类”:该类是一个配置类 、自动纳入Spring容器(@Component)
    @EnableAutoConfiguration:使spring boot可以自动配置:spring boot在启动时,就会将该包及所有的子包全部纳入spring容器,会根据META-INF/spring.factories找到相应的三方依赖,并将这些依赖引入本项目、Xxx_AutoConfiguration会实现自动装配并给予默认值
    @SpringBootConfiguration:自己代码的自动配置
    @ConditionalOnXxx:当这些条件都满足时此配置自动装配生效(手工修改改 自动装配:XxxProperties文件中的prefix.属性名=value
    配置文件
    application.properties : k=v,或行内写法(k: v,[Set/List/数组] {map,对象类型的属性},并且 []可省,{}不能省)
    application.yml:k:空格v、通过垂直对齐指定层次关系、默认可以不写引号 (双引号会将其中的转义符进行转义)
    @Component:将此类型加入Javabean
    @ConfigurationProperties(prefix=“student”):根据前缀名实现该类型自动装配,同yml文件相匹配
    @Autowired:自动装配从bean中取值
    @Value(""):实现单个值注入,可以和全局配置文件实现互补
    @Validated:开启jsr303数据校验的注解
    @Test:此类型为测试类
    @ImportResource(locations={“classpath:spring.xml”}):识别spring.xml配置文件
    @bean:通过实现注解配置将此类型加入到bean中

    @ConfigurationProperties@Value
    注值批量注入单个
    松散语法支持不支持
    SpEL不支持支持
    JSR303数据校验支持不支持
    注入复杂类型支持不支持

    多环境切换:配置环境切换和动态环境切换
    配置文件的位置:properties的优先级要大于yml
    外部文件和外部参数:Run configuration ,argumenets方式和命令控制行
    日志:spring boot默认选用slf4j和logback、日志级别、日志信息格式、日志输出路径
    Web静态资源:静态资源的存放路径通过WebMvcAutoConfiguration类-addResourceHandlers()指定:/webjars/成静态资源存放目录: {“classpath:/META-INF/resources/”,“classpath:/resources/”,“classpath:/static/”,“classpath:/public/”}以上目录存放资源文件后,访问时不需要加前缀、欢迎页(任意一个静态资源目录中的Index.html)、Logo(任意一个静态资源目录中的favicon.ico)、自定义静态资源存放路径(spring.resources.static-locations=…)
    Web动态资源:默认不支持jsp、html等,但是推荐使用模板引擎thymeleaf(网页=模板+数据)
    引入依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    </dependency>
    <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    </dependency>
    

    使用thymeleaf只需要将文件放入目录:“classpath:/templates/”; 文件的后缀: “.html”;
    thymeleaf的th指定方式和符号取值方式(usingthymeleaf文档)

    Dubbo

    RPC(Remote Procedure Call):远程过程调用
    SOA(Service-Oriented Architecture):面向服务架构
    Dubbo中控制器和服务端分离
    步骤:提供方服务器运行提供方提供的服务、注册中心发布服务、消费方订阅服务、注册中心推送服务、消费方向提供方调用服务
    开发dubbo程序:准备环境(linux环境中的jdk及其环境变量和zookeeper)、服务方(引入依赖:pom.xml、接口及实现类@Service、补全WEB-INF/web.xml、配置spring:applicationContext.xml)、消费方(引入依赖:pom.xml改端口号、补全WEB-INF/web.xml、配置springmvc、编写控制器代码@Reference:用于访问服务方提供的服务代码)、监听器(下载incubator-dubbo-ops、打包war、在linux中的tomcat中运行)

    Quartz

    quartz:定时执行、异步任务
    任务:Job、触发器:定义时间、调度器:将任务和触发器 一一对应
    步骤
    引入jar包
    新建类制定具体任务
    新建类任务调用的实现继承Job接口并执行具体任务

    public class PlanJob implements Job {
        MeetingService meetingService = new MeetingService();
        //jobExecutionContext可以获取设置的各种参数值
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            TriggerKey triggerkey =
                    jobExecutionContext.getTrigger().getKey();
            JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
            System.out.println("----");
            System.out.println(triggerkey+"\n"+jobKey);
            JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
            List<String> infos = (List<String>)jobDataMap.get("infos");
            System.out.println(infos);
            //存放 计划执行的任务...
            meetingService.calClassMeeting();
        }
    }
    

    新建测试方法类(产生)

    public class TestQuartz {
    //XxxBuilder ->withIdentity()-->Xxx
    public static void main(String[] args) throws SchedulerException, InterruptedException, ParseException {
    //        PlanJob
            JobBuilder jobBuilder = JobBuilder.newJob(PlanJob.class);//PlanJob PlanJob PlanJob
            //产生实际使用的Job
            JobDetail jobDetail = jobBuilder.withIdentity("meeting Job", "group1").build();
    
            //向Job的execute()中传入一些参数。。。
    //        JobDatMap
            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            List<String> names = Arrays.asList(new String[]{"zs","ls","ww"});
            jobDataMap.put("infos",names);
    
            // 触发器(Trigger):时间规则  ,依赖2个对象(TriggerBuilder ,Scheduel)
            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
            triggerBuilder = triggerBuilder.withIdentity("meeting trigger", "group1");
            triggerBuilder.startNow();//当满足条件时  立刻执行
            // 2019-03-13 09:46:30   --  // 2019-03-13 09:46:45
    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date start = sdf.parse("2019-03-13 09:46:30");
            Date end = sdf.parse("2019-03-13 09:46:45");
    
    //        triggerBuilder.startAt(start);
    //        triggerBuilder.endAt(end);
    
            //scheduelBuilder:定执行的周期(时机)
    //        SimpleScheduleBuilder scheduelBuilder = SimpleScheduleBuilder.simpleSchedule();
    //        scheduelBuilder.withIntervalInSeconds(1) ;//每隔1秒执行一次
    //        scheduelBuilder.withRepeatCount(3) ;//重复执行3次
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("5,10,15,30,45 * * * * ? *");
            //产生触发器
            // SimpleTrigger trigger = triggerBuilder.withSchedule(ScheduleBuilder).build();
            CronTrigger trigger = triggerBuilder.withSchedule(cronScheduleBuilder).build();
    //        调度器(工厂产生调度器)
             SchedulerFactory secheduleFacotry = new StdSchedulerFactory();
            //产生调度器
            Scheduler scheduler = secheduleFacotry.getScheduler();
            //通过调度器 将 任务 和 触发器一一对应
            scheduler.scheduleJob(jobDetail,trigger) ;
            scheduler.start();
    //        scheduler.shutdown(true);
        }
    }
    

    框架整合

    Spring-MyBatis整合

    引入jar包
    配置类-表对应
    配置Spring配置文件(applicationContext.xml)

    <!-- 加载db.properties文件 -->
    <bean  id="config" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
    <property name="locations">
    <array><value>classpath:db.properties</value></array>
    </property>
    </bean>
    
    <!-- 引入service层的bean -->
    <bean id="studentService" class="org.lanqiao.service.impl.StudentServiceImpl">
    <property name="studentMapper" ref="studentMapper"></property>
    </bean>
    
    <!-- 配置配置数据库信息(替代mybatis的配置文件conf.xml) -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${driver}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
    </bean>
    
    <!-- 在SpringIoc容器中 创建MyBatis的核心类 SqlSesionFactory -->
    <bean id="sqlSessionFacotry" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <!-- 加载mapper.xml路径 -->
    <property name="mapperLocations" value="org/lanqiao/mapper/*.xml"></property>
    </bean>
    

    配置mapper中的sql语句
    使用Spring-MyBatis整合产物开发程序

    1. DAO层实现类继承SqlSessionDaoSupport类
    2. 直接MyBatis提供的 Mapper实现类:org.mybatis.spring.mapper.MapperFactoryBean
    <bean id="studentMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="org.lanqiao.mapper.StudentMapper"></property>
    <property name="sqlSessionFactory" ref="sqlSessionFacotry"></property>
    </bean>
    
    1. 批量生成多个mapper
    <!--Mapper对在SpringIOC中的id默认就是首字母小写接口名,service在ref对象要首字母小写-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFacotry"></property>
    <!--指定批量产生 哪个包中的mapper对象-->
    <property name="basePackage" value="org.lanqiao.mapper"></property>
    </bean>
    

    使用:

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    context.getBean("bean") ;
    

    Spring-SpringMVC-MyBatis整合

    引入jar包
    配置类-表对应
    配置Spring配置文件(applicationContext.xml):使用Spring整合MyBatis
    编写springmvc配置文件:视图解析器、基础配置
    配置mapper中的sql语句
    配置Web配置文件

    <!-- Web项目中,引入Spring -->
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 整合SPringMVC -->
    <servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext-controller.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>springDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    

    Springboot-JSP整合

    新建boot项目war包
    建立基本的web项目所需要的目录结构
    webapps/WEB-INF(需要)
    webapps/WEB-INF/web.xml (不需要)
    webapps/index.jsp
    创建tomcat实例并部署项目:如果是一个war包的spring boot项目,在启动服务器tomcat时,会自动调用ServletInitializer类中的configure方法,configure方法会调用spring boot的主配置类从而启动spring boot

    Dubbo-SSM整合

    创建父工程(引入公共依赖、实体类POJO依赖、module子模块依赖、解决Maven内置tomcat打包不引入xml问题的代码)
    创建实体类(实体类需要implements Serializable接口)
    创建公共模块实现接口共用(引入实体类POJO依赖、提供公共接口)
    创建dao层(映射文件mapper、动态代理接口mapper、spring文件application(加载db文件、配置数据库信息、创建核心类sqlsessionfactory)、将核心类交给spring处理)
    创建service层(补全WEB-INF/web.xml并拦截交给spring处理、pom依赖父工程+dao层+公共接口+tomcat代码、编写spring的application文件并顺带启动dao层spring的application、service的具体实现类)
    创建显示web层(补全WEB-INF/web.xml并拦截交给springMVC处理、pom依赖父工程+公共接口+tomcat代码、编写控制器、配置sppringMVC)
    其他
    在maven中引入一个 mvn中央仓库中不存在的Jar:将jar自己安装到本地mvn仓库:cmd命令(ojdbc7.jar为例):mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc7 -Dversion=10.2.0.5.0 -Dpackaging=jar -Dfile=f:\ojdbc7.jar
    解决Maven内置tomcat打包不引入xml问题:

    # 在父工程pom.xml的<project>元素中加入
      <build>
        	<resources> 
    	  	<resource> 
    	  	<directory>src/main/java</directory> 
    	  	<includes> 
    	  	<include>**/*.xml</include> 
    	  	</includes> 
    	  	<filtering>false</filtering>
    		</resource> 
    		<resource>
    		<directory>src/main/resources</directory>
    		</resource>
    	</resources>
     </build>
    

    Spring-Redis整合

    建立java文件并引入jar包
    指定连接 Redis 服务器的相关信息redis.properties(实现分配连接池和线程)
    在spring的applicationContext配置文件中配置连接池、连接工厂和操作模板,并把RedisUtil加入bean中
    使用工具类和测试类进行Redis的访问和检测

    Spring-Quartz整合

    引入jar包
    创建实体类封装Job信息
    创建实体类调用的具体方法
    创建测试方法初始化容器和开始执行
    创建配置文件applicationContext.xml

     <!--配置任务信息-->
        <bean id="scheduleJobEntity" class="com.yanqun.entity.ScheduleJob"  >
                <property name="jobId" value="j001"></property>
                <property name="jobName" value="任务1"></property>
                <property name="jobGroup" value="任务组1"></property>
                <property name="jobStatus" value="1"></property>
                <property name="cronExpression" value="5,10,30,50 * * * * ? *"></property>
                <property name="desc" value="描述..."></property>
        </bean>
        
    <!--配置job类信息-->
        <bean  id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean" >
                <property name="jobClass" value="com.yanqun.job.PlanJob"></property>
                <property name="jobDataAsMap">
                        <map>
                            <entry key="scheduleJob">
                                <ref bean="scheduleJobEntity"></ref>
                            </entry>
                        </map>
                </property>
        </bean>
    
    <!-- cronTrigger触发器:定义时间规则
        <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    
                <property name="jobDetail" ref="jobDetail" >
                </property>
                <property name="cronExpression" value="#{scheduleJobEntity.cronExpression}">
                </property>
        </bean>-->
        
    <!--SimpleTrigger触发器-->
        <bean  id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"  >
                <property name="repeatInterval" value="2000"></property>
                <property name="repeatCount" value="10"></property>
                <property name="startDelay" value="3"></property>
                <property name="jobDetail" ref="jobDetail" ></property>
        </bean>
    <!--配置调度器-->
          <bean id="schedulerFactoryBean"  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
                <property name="triggers">
                    <list>
                            <ref bean="simpleTrigger" />
                    </list>
                </property>
          </bean>
    
    展开全文
  • Android面试题整理

    千次阅读 2018-02-27 21:56:47
    当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有Activity,Service持有Context,那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider并不是Context的子类,...

    前言

    本文整理了简书 goeasyway 面试相关文章,并在文章中加以自己的理解以及看见的部分精彩评论,所有文章并非自己原创,如对Android面试有兴趣,可前往作者文章专栏传送门或者关注对方的微信公众号:Android面试启示录
    这里写图片描述


    startService只是启动Service,启动它的组件(如Activity)和Service并没有关联,只有当Service调用stopSelf或者其他组件调用stopService服务才会终止。
    bindService方法启动Service,其他组件可以通过回调获取Service的代理对象和Service交互,而这两方也进行了绑定,当启动方销毁时,Service也会自动进行unBind操作,当发现所有绑定都进行了unBind时才会销毁Service。
    Service生命周期函数
    IntentService——可以看做是Service和HandlerThread的结合体,在完成了使命之后会自动停止,适合需要在工作线程处理UI无关任务的场景。

    • IntentService 是继承自 Service 并处理异步请求的一个类,在 IntentService内有一个工作线程来处理耗时操作。
    • 当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。
    • 如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,使用串行的方式,执行完自动结束。

    Normal broadcasts无序广播,会异步的发送给所有的Receiver,接收到广播的顺序是不确定的,有可能是同时。
    Ordered broadcasts有序广播,广播会先发送给优先级高(android:priority)的Receiver,而且这个Receiver有权决定是继续发送到下一个Receiver或者是直接终止广播。
    sendStickyBroadcast发送Sticky类型的广播。Sticky简单说就是,在发送广播时Reciever还没有被注册,但它注册后还是可以收到在它之前发送的那条广播。
    LocalBroadcastManager发送广播时限定有权限(receiverPermission)的接收者才能收到。但是我们知道APK太容易被反编译,注册广播的权限也只是一个字符串,并不安全。然后可能使用Handler,没错,往主线程的消息池(Message Queue)发送消息,只有主线程的Handler可以分发处理它,广播发送的内容是一个Intent对象,我们可以直接用Message封装一下,留一个和sendBroadcast一样的接口。在handleMessage时把Intent对象传递给已注册的Receiver。
    如果不是频繁地刷新,使用广播来做也是可以的。但对于较频繁地刷新动作,建议还是不要使用这种方式。广播的发送和接收是有一定的代价的,它的传输是通过Binder进程间通信机制来实现的(细心人会发现Intent是实现了Parcelable接口的),那么系统定会为了广播能顺利传递做一些进程间通信的准备。

    如果一个Activity在用户可见时才处理某个广播,不可见时注销掉,那么应该在哪两个生命周期的回调方法去注册和注销BroadcastReceiver呢?
    如果有一些数据在Activity跳转时(或者离开时)要保存到数据库,那么你认为是在onPause好还是在onStop执行这个操作好呢?
    简单说一下Activity A启动Activity B时,两个Activity生命周期的变化。

    来源于Android源码中的Call应用,AsyncTask中的onPostExecute片段

    @Override
        protected void onPostExecute(Void result) {
            final Activity activity = progressDialog.getOwnerActivity();
    
            if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
                return;
            }
    
            if (progressDialog != null && progressDialog.isShowing()) {
                progressDialog.dismiss();
            }
        }

    参考TextView的源代码,BaseSavedState是View的一个内部静态类,从代码上我们也很容易看出是把控件的属性(如selStart)打包到Parcel容器,Activity的onSaveInstanceState、onRestoreInstanceState最终也会调用到控件的这两个同名方法。

    /**
         * User interface state that is stored by TextView for implementing
         * {@link View#onSaveInstanceState}.
         */
        public static class SavedState extends BaseSavedState {
            int selStart;
            int selEnd;
            CharSequence text;
            boolean frozenWithFocus;
            CharSequence error;
    
            SavedState(Parcelable superState) {
                super(superState);
            }
    
            @Override
            public void writeToParcel(Parcel out, int flags) {
                super.writeToParcel(out, flags);
                out.writeInt(selStart);
                out.writeInt(selEnd);
                out.writeInt(frozenWithFocus ? 1 : 0);
            ......

    View有唯一的ID;
    View的初始化时要调用setSaveEnabled(true) ;
    这里写图片描述

    “在Java里面参数传递都是按值传递”
    这句话的意思是:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。
    简单的说,基本类型是按值传递的,方法的实参是一个原值的复本。类对象是按对象的引用地址(内存地址)传递地址的值,那么在方法内对这个对象进行修改是会直接反应在原对象上的(或者说这两个引用指向同一内存地址)。不过要注意String这个类型,如下代码:

    public static void main(String[] args) {
        String x = new String("goeasyway");
        change(x);
        System.out.println(x);
    }
    
    public static void change(String x) {
        x = "even";
    }
    • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
    • MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
    • Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
    • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。
    • Serializalbe会使用反射,序列化和反序列化过程需要大量I/O操作,Parcelable自已实现封送和解封(marshalled
      &unmarshalled)操作不需要用反射,数据也存放在Native内存中,效率要快很多。

    • 两个Activity之前传递对象一定要注意对象的大小,Intent中的Bundle是在使用Binder机制进行数据传递的,能使用的Binder的缓冲区是有大小限制的(有些手机是2M),而一个进程默认有16个binder线程,所以一个线程能占用的缓冲区就更小了(以前做过测试,大约一个线程可以占用128KB)。所以当你看到“The Binder transaction failed because it was too large.”这类TransactionTooLargeException异常时,你应该知道怎么解决了。

    Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。ContextThemeWrapper类,如其名所言,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实现了Context中的所以函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。一句话总结:Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。
    一个应用程序有几个Context?
    从上面的关系图我们已经可以得出答案了,在应用程序中Context的具体实现子类就是:Activity,Service,Application。那么Context数量=Activity数量+Service数量+1。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有Activity,Service持有Context,那Broadcast
    Receiver,Content Provider呢?Broadcast Receiver,Content
    Provider并不是Context的子类,他们所持有的Context都是其他地方传过去的,所以并不计入Context总数。传送门

    在一次显示ListView的界面时,getView会被执行几次?
    每次getView执行时间应该控制在多少毫秒之内?
    getView中设置listener要注意什么?
    - 重用ConvertView;
    - 使用View Holder模式;
    - 使用异步线程加载图片(一般都是直接使用图片库加载,如Glide, Picasso);
    - 在adapter的getView方法中尽可能的减少逻辑判断,特别是耗时的判断;
    - 避免GC(可以从LOGCAT查看有无GC的LOG);
    - 在快速滑动时不要加载图片;
    - 尽可能减少List Item的Layout层次(如可以使用RelativeLayout替换LinearLayout,或使用自定的View代替组合嵌套使用的Layout);
    - 将ListView的scrollingCache和animateCache这两个属性设置为false(默认是true);

    这里写图片描述

    实现的方式也很简单,直接调用Android开放的接口Resources.updateConfiguration:

    public static void changeSystemLanguage(Context context, String language) {
            if (context == null || TextUtils.isEmpty(language)) {
                return;
            }
    
            Resources resources = context.getResources();
            Configuration config = resources.getConfiguration();
            if (Locale.SIMPLIFIED_CHINESE.getLanguage().equals(language)) {
                config.locale = Locale.SIMPLIFIED_CHINESE;
            } else {
                config.locale = new Locale(language);
            }
            resources.updateConfiguration(config, null);
        }

    线程池可以同时执行多少个TASK?
    Android 3.0之前(1.6之前的版本不再关注)规定线程池的核心线程数为5个(corePoolSize),线程池总大小为128(maximumPoolSize),还有一个缓冲队列(sWorkQueue,缓冲队列可以放10个任务),当我们尝试去添加第139个任务时,程序就会崩溃。当线程池中的数量大于corePoolSize,缓冲队列已满,并且线程池中的数量小于maximumPoolSize,将会创建新的线程来处理被添加的任务。如下图会出现第16个Task比第6-15个Task先执行的情况。
    多个AsyncTask任务是串行还是并行?

    从Android 1.6到2.3(Gingerbread)
    AsyncTask是并行的,即上面我们提到的有5个核心线程的线程池(ThreadPoolExecutor)负责调度任务。从Android
    3.0开始,Android团队又把AsyncTask改成了串行,默认的Executor被指定为SERIAL_EXECUTOR。
    AsyncTask容易引发的Activity内存泄露
    如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

    commit这种方式很常用,在比较早的SDK版本中就有了,这种提交修改的方式是同步的,会阻塞调用它的线程,并且这个方法会返回boolean值告知保存是否成功(如果不成功,可以做一些补救措施)。
    而apply是异步的提交方式,目前Android Studio也会提示大家使用这种方式。
    多进程操作和读取SharedPreferences的问题
    在SDK 3.0及以上版本,可以通过Context.MODE_MULTI_PROCESS属性来实现SharedPreferences多进程共享。本来以为通过MODE_MULTI_PROCESS属性使用SharedPreferences就可以实现不同时程间共享数据,但是在真正使用中确发现有会有一定概率出现这个取值出错(变为初始值)问题。

    最后发现在官网上Google也在SDK 6.0的版本将这个MODE_MULTI_PROCESS标识为deprecated(不赞成使用)。

    封装。对数据进行封装,提供统一的接口,使用者完全不必关心这些数据是在DB,XML、Preferences或者网络请求来的。当项目需求要改变数据来源时,使用我们的地方完全不需要修改。
    提供一种跨进程数据共享的方式。
    ContentProvider接口方法运行在哪个线程中呢?
    1. ContentProvider和调用者在同一个进程,ContentProvider的方法(query/insert/update/delete等)和调用者在同一线程中;
    2. ContentProvider和调用者在不同的进程,ContentProvider的方法会运行在它自身所在进程的一个Binder线程中。

    Object的wait和notify/notifyAll如何实现线程同步?

    在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

    wait和yield(或sleep)的区别?

    1. wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权。
    2. wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
    try
        {
            sqLiteDatabase.beginTransaction();
            SQLiteStatement stat = sqLiteDatabase.compileStatement(insertSQL);
    
            // 插入10000次
            for (int i = 0; i < 10000; i++)
            {
                stat.bindLong(1, 123456);
                stat.bindString(2, "test");
                stat.executeInsert();
            }
            sqLiteDatabase.setTransactionSuccessful();
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        finally
        {
            // 结束
            sqLiteDatabase.endTransaction();
            sqLiteDatabase.close();
        }

    线程问题

    我们常常在多线程中只使用一个SQLiteDatabase引用,在用SQLiteDataBase.close()的时需要注意调是否还有别的线程在使用这个实例。如果一个线程操作完成后就直接close了,别一个正在使用这个数据库的线程就会异常。所以有些人会直接把SQLiteDatabase的实例放在Application中,让它们的生命周期一致。也有的做法是写一个计数器,当计数器为0时才真正关闭数据库。

    为难原由:Activity的启动模式(launchMode)有哪些,有什么区别?
    很多人在使用startActivityForResult启动一个Activity时,会发现还没有开始界面跳转本身的onActivityResult马上就被执行了,这是为什么呢?

    如下面表格,左边第1列代表MainActivity的启动模式,第一行代表SecondActivity(即要startActivityForResult启动的Activity)的启动模式,打叉代表在这种组合下onActivityResult会被马上调用。

    standsingleTopsingleTasksingleInstance
    standxx
    singleTopxx
    singleTaskxx
    singleInstancexxxx

    Android在5.0及以后的版本修改了这个限制。也就是说上面x的地方全部变成了√。但是如在Intent中设置了FLAG_ACTIVITY_NEW_TASK再startActivityForResult,即使是标准的启动模式仍然会有这个问题。

    最终我们发现只要是不和原来的Activity在同一个Task就会产生这种立即执行onActivityResult的情况

    当前应用有两个Activity A和B,B的android:launchMode设置了singleTask模式,A是默认的standard,那么A startActivity启动B,B会新启一个Task吗?如果不会,那么startActivity的Intent加上FLAG_ACTIVITY_NEW_TASK这个参数会不会呢?

    设置了”singleTask”启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。

    当Intent对象包含FLAG_ACTIVITY_NEW_TASK标记时,系统在查代时仍然按Activity的taskAffinity属性进行匹配,如果找到一个Task的taskAffinity与之相同,就将目标Activity压入此Task栈中,如果找不到则创建一个新的Task。

    注意:设置了”singleTask”启动模式的Activity在已有的任务中已经存在相应的Activity实例,再启动它时会把这个Activity实例上面的Activity全部结束掉。

    1. 设置为singleTask的启动模式,当Activity的实例已经存在时,再启动它,那么它的回调函数是否会被执行?我们可以在哪个回调中处理新的Intent协带的参数?(通过startActivity(Intent)启动)
    2. 或者问设置为singleTop的启动模式,当Activity的实例已经存在于Task的栈顶,我们可以在哪个回调中处理新的Intent协带的参数?(在当前Activity中从通知栏点击再跳转到此Activity就是这种在栈顶的情况)

    当您请求要为其提供备用资源的资源时,Android 会根据当前的设备配置选择要在运行时使用的备用资源。

    为演示 Android 如何选择备用资源,假设以下可绘制对象目录分别包含相同图像的不同版本:

    drawable/

    drawable-en/

    drawable-fr-rCA/

    drawable-en-port/

    drawable-en-notouch-12key/

    drawable-port-ldpi/

    drawable-port-notouch-12key/

    同时,假设设备配置如下:

    语言区域 = en-GB
    屏幕方向 = port
    屏幕像素密度 = hdpi
    触摸屏类型 = notouch
    主要文本输入法 = 12key

    通过将设备配置与可用的备用资源进行比较,Android 从 drawable-en-port 中选择可绘制对象。

    系统使用以下逻辑决定要使用的资源:

    淘汰与设备配置冲突的资源文件。 drawable-fr-rCA/ 目录与 en-GB 语言区域冲突,因而被淘汰。

    drawable/

    drawable-en/

    drawable-fr-rCA/

    drawable-en-port/

    drawable-en-notouch-12key/

    drawable-port-ldpi/

    drawable-port-notouch-12key/
    例外:屏幕像素密度是唯一一个未因冲突而被淘汰的限定符。 尽管设备的屏幕密度为 hdpi,但是 drawable-port-ldpi/
    未被淘汰,因为此时每个屏幕密度均视为匹配。如需了解详细信息,请参阅支持多种屏幕文档。

    选择列表(表 2)中(下一个)优先级最高的限定符。(先从 MCC 开始,然后下移。) 是否有资源目录包括此限定符?

    若无,请返回到第 2 步,看看下一个限定符。(在该示例中,除非达到语言限定符,否则答案始终为“否”。)

    若有,请继续执行第 4 步。 淘汰不含此限定符的资源目录。在该示例中,系统会淘汰所有不含语言限定符的目录。

    drawable/
    drawable-en/
    drawable-en-port/
    drawable-en-notouch-12key/
    drawable-port-ldpi/
    drawable-port-notouch-12key/

    例外:如果涉及的限定符是屏幕像素密度,则 Android 会选择最接近设备屏幕密度的选项。通常,Android 倾向于缩小大型原始图像,而不是放大小型原始图像。

    返回并重复第 2 步、第 3 步和第 4步,直到只剩下一个目录为止。在此示例中,屏幕方向是下一个判断是否匹配的限定符。因此,未指定屏幕方向的资源被淘汰:

    drawable-en/
    drawable-en-port/
    drawable-en-notouch-12key/
    剩下的目录是 drawable-en-port。

    尽管对所请求的每个资源均执行此程序,但是系统仍会对某些方面做进一步优化。 例如,系统一旦知道设备配置,即会淘汰可能永远无法匹配的备用资源。
    比如说,如果配置语言是英语(“en”),则系统绝不会将语言限定符设置为非英语的任何资源目录包含在选中的资源池中(不过,仍会将不带语言限定符的资源目录包含在该池中)。

    根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,则系统将使用专为小于当前屏幕的屏幕而设计的资源(例如,如有必要,大尺寸屏幕将使用标准尺寸的屏幕资源)。
    但是,如果唯一可用的资源大于当前屏幕,则系统不会使用这些资源,并且如果没有其他资源与设备配置匹配,应用将会崩溃(例如,如果所有布局资源均用
    xlarge 限定符标记,但设备是标准尺寸的屏幕)。

    注:限定符的优先顺序(表 2 中)比与设备完全匹配的限定符数量更加重要。例如,在上面的第 4 步中,列表剩下的最后选项包括三个与设备完全匹配的限定符(方向、触摸屏类型和输入法),而 drawable-en 只有一个匹配参数(语言)。但是,语言的优先顺序高于其他两个限定符,因此 drawable-port-notouch-12key 被淘汰。
    查找最佳匹配资源的流程图
    传送门

    密度建议尺寸
    mipmap-mdpi48 * 48
    mipmap-hdpi72 * 72
    mipmap-xhdpi96 * 96
    mipmap-xxhdpi144 * 144
    mipmap-xxxhdpi196 * 96

    Android drawable微技巧,你所不知道的drawable的那些细节

    • px:pixel,像素,电子屏幕上组成一幅图画或照片的最基本单元
    • pt: point,点,印刷行业常用单位,等于1/72英寸
    • ppi:pixel per inch,每英寸像素数,该值越高,则屏幕越细腻
    • dpi: dot per inch,每英寸多少点,该值越高,则图片越细腻
    • dp: dip,Density-independent pixel,是安卓开发用的长度单位,1dp表示在屏幕像素点密度为160ppi时1px长度
    • sp: scale-independent pixel,安卓开发用的字体大小单位。

    px、pt、ppi、dpi、dp、sp之间的关系

    其实图片最终要显示在屏幕上,都会对应一个屏幕上的点,即对应一个颜色值。不同格式的图片,只是不同压缩编码和解压算法。也就是说,我们看到的.jpg、.png图片的文件大小只有几十KB,担把它们加载到内存中时,每张图片最终都按长X宽展开,计算其占用内存大小的就变成了(ARGB_8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte,假设是ARGB_8888):
    内存占用=长 X 宽 X 4bytes

    这种算法其这还忽略屏幕的Density,放在不同的drawable目录中的图片显示时会根据Denisty有一定的缩放。所以有时候图片占用的内存会比我们上面公式计算出来的还要大很多。

    Bitmap和Drawable

    Bitmap是Android系统中的图像处理的最重要类。可以简单地说,Bitmap代表的是图片资源在内存中的数据结构,如它的像素数据,长宽等属性都存放在Bitmap对象中。
    Drawable官文文档说明为可绘制物件的一般抽象。也就是Drawable是一种抽像,最终实现的方式可以是绘制Bitmap的数据或者图形、Color数据等。理解了这些,你很容易明白为什么我们有时候需要进行两者之间的转换。

    由于Fragment之间是没有任何依赖关系的,因此如果要进行Fragment之间的通信,建议通过Activity作为中介,不要Fragment之间直接通信。当然,也可以选择EventBus等框架实现通信。
    关于Fragment的更多知识点可查阅Android 基础:Fragment,看这篇就够了 (上)Android 基础:Fragment,看这篇就够了 (下)

    常有些开发不知道为什么自己的Application.onCreate中的代码执行了两次,如果你遇到这样的情况可以检查一下AndroidManifest.xml是否给某个组件配置了android:process属性。每个进程创建后,都会启动一个主线程(Looper接收消息),每个组件启动前都会先创建Application实例(一个进程只创建一个)。

    优先级

    • 前台进程
    • 可见进程
    • 服务进程
    • 后台进程
    • 空进程

    一般提高进程优先级的方法

    1. 进程要运行一些组件,不要成为空进程。
    2. 远行一个Service,并设置为前台运行方式(startForeground)。
    3. AndroidManifest.xml中配置persistent属性(persistent的App会被优先照顾,进程优先级设置为PERSISTENT_PROC_ADJ=-12)

    关于第2点,摘抄一段代码给大家看:

    private void keepAlive() {
            try {
                Notification notification = new Notification();
                notification.flags |= Notification.FLAG_NO_CLEAR;
                notification.flags |= Notification.FLAG_ONGOING_EVENT;
                startForeground(0, notification); // 设置为前台服务避免kill,Android4.3及以上需要设置id为0时通知栏才不显示该通知;
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

    在Service的onCreate方法调用keepAlive()即可,其实就是是欺骗系统把自己当成一个一直在通知栏的Notification。不过这种方式,并不保证在所有的机型上都有效。

    • Android面试一天一题(Day 26:ScrollView嵌套ListView的事件冲突)——如何解决ScrollView嵌套中一个ListView的滑动冲突?

      1. ScrollView布局中嵌套Listview显示是不正常的,确切地说是只会显示ListView的第一个项。
      2. ScrollView和ListView都是上下滑动的,嵌套在一起后ScrollView中的ListView就没法上下滑动了,事件被ScrollView响应了。

    问题1:为什么会只显示ListView的第一个Item,简单的说就是ListView在计算(比较正式的说法是:测量)自己的高度时对MeasureSpec.UNSPECIFIED这个模式在测量时只会返回一个List
    Item的高度(当然还有一些padding这些的值我们可以先忽略),而ScrollView的重写了measureChildWithMargins方法导致它的子View的高度被强制设置成了MeasureSpec.UNSPECIFIED模式。

    UNSPECIFIED

    不限定,父View不限制子View的具体的大小,所以子View可以按自己需求设置宽高(前面说的ScrollView就给子View设置了这个模式,ListView就会自己确认自己高度)。

    EXACTLY

    父View决定子View的确切大小,子View被限定在给定的边界里,忽略本身想要的大小。

    AT_MOST

    最多的,子View最大可以达到的指定大小(当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少。

    知道了这些我们解决这个问题,就不算难了,我们也可以重写ListView的onMeasure让它按我们的要求测量高度。

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 固定高度(实际中这个值应该是根据手机屏幕计算出来的)
            int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(480, 
                    MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);
        }

    问题2:ScrollView和ListView的事件冲突问题,从ScrollView的源码可以看到它对Touch事件(ACTION_MOVE)进行了拦截,所以滑动的事件传递不到ListView。

    引用块内容
    所以我们解决这个问题,需要让在ListView区域的滑动事件ScrollView不要拦截。这样在ListView区域外的还是由ScrollView去处理事件,ListView外滑动的就是ScrollView。这里用到一个系统自带的API来实现这种方案:requestDisallowInterceptTouchEvent(我觉得可以从名字直接读出它的用途,不再解释),代码也不复杂:

    public class MyListView extends ListView {
    
        public MyListView(Context context) {
            super(context);
        }
    
        public MyListView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(480, // 固定高度(实际中这个值应该是根据手机屏幕计算出来的)
                    MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE:
                    getParent().requestDisallowInterceptTouchEvent(true);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    getParent().requestDisallowInterceptTouchEvent(false);
                    break;
            }
            return super.onInterceptTouchEvent(ev);
        }
    }

    Dalvik虚拟机就是针对Android应用(.dex)的一个JVM,虽然他们实现和原理有很多不同,但我们可以简单这样理解。每个应用的进程中都会有一个Dalvik虚拟机实例,它们是相互独立的,一个应用的Dalvik虚拟机Crash了并不影响其他的。APK应用在运行时,就需要Dalvik虚拟机去加载类并转化成机器码执行,这个过程就是JIT(Just-In-Time),虽然dex经过优化会生成一个odex文件,但这个过程仍然是运行时让编译器去解释字节码,相当于多了一道转换工序,对性能的影响不小。
    ART(Android runtime)和Dalvik不一样的地方,就是想法去掉了中间解释字节码的过程,Dalvik是JIT的机制,与JIT相对的是AOT(Ahead-Of-Time),它发生在程序运行之前。如我们用静态语言(例如C/C++)来开发应用程序的时候,编译器直接就把它们翻译成目标机器码。这种静态语言的编译方式也是AOT的一种。

    问题1:使用MAT等内存分析工具监测,在使用工具时,您应积极地对自己的应用代码进行测试并尝试强制内存泄漏。在应用中引发内存泄漏的一种方式是,先让其运行一段时间,然后再检查堆。泄漏在堆中将逐渐汇聚到分配顶部。不过,泄漏越小,您越需要运行更长时间的应用才能看到泄漏。

    您还可以通过以下方式之一触发内存泄漏:

    1. 将设备从纵向旋转为横向,然后在不同的活动状态下反复操作多次。旋转设备经常会使应用泄漏 Activity、Context 或 View
      对象,因为系统会重新创建 Activity,而如果您的应用在其他地方保持对这些对象其中一个的引用,系统将无法对其进行垃圾回收。

    2. 处于不同的活动状态时,在您的应用与另一个应用之间切换(导航到主屏幕,然后返回到您的应用)。

    内存分析工具参数说明

    问题2:优化建议

    1. 注意单例模式和静态变量是否会持有对Context的引用;
    2. 注意监听器的注销;(在Android程序里面存在很多需要register与unregister的监听
    3. 不要在Thread或AsyncTask中的引用Activity;

    标准单例模式如下

    public class Singleton{
        private volatile static Singleton instance;
        private Singleton() {};
        public static Singleton getInstance() {
            if (instance==null) {
                synchronized(Singleton.class) {
                    if (instance==null)
                        instance=new Singleton();
                }
            }
            return instance;
        }
    }
    1. invalidate和postInvalidate方法的区别?
    2. 自定义View的绘制流程?
    3. View的Touch事件分发流程?(Day 26:ScrollView嵌套ListView的事件冲突已经详细说明了这个问题)

    答案:

    问题1:
    invalidate()是用来刷新View的,必须是在UI线程中进行工作。比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。

    对于屏幕刷新有以下几种情况可以考虑:

    • 不使用多线程和双缓冲

    这种情况最简单了,一般只是希望在View发生改变时对UI进行重绘。你只需在Activity中显式地调用View对象中的invalidate()方法即可。系统会自动调用
    View的onDraw()方法。

    • 使用多线程和不使用双缓冲

    这种情况需要开启新的线程,新开的线程就不好访问View对象了。强行访问的话会报:

    android.view.ViewRoot$CalledFromWrongThreadException:Only the original
    thread that created a view hierarchy can touch its views.

    这时候你需要创建一个继承了android.os.Handler的子类,并重写handleMessage(Message
    msg)方法。android.os.Handler是能发送和处理消息的,你需要在Activity中发出更新UI的消息,然后再你的Handler(可以使用匿名内部类)中处理消息(因为匿名内部类可以访问父类变量,
    你可以直接调用View对象中的invalidate()方法
    )。也就是说:在新线程创建并发送一个Message,然后再主线程中捕获、处理该消息。

    • 使用多线程和双缓冲

    Android中SurfaceView是View的子类,她同时也实现了双缓冲。你可以定义一个她的子类并实现SurfaceHolder.Callback接口。由于实现SurfaceHolder.Callback接口,新线程就不需要android.os.Handler帮忙了。SurfaceHolder中lockCanvas()方法可以锁定画布,绘制玩新的图像后调用unlockCanvasAndPost(canvas)解锁(显示),还是比较方便得。

    另一个是postInvalidate(),invalidate()是在UI线程自身中使用,而postInvalidate()在非UI线程中使用。

    View 类中postInvalidate()方法源码如下,可见它也是用到了handler的:

    public void postInvalidate() {
            postInvalidateDelayed(0);
    }
    public void postInvalidateDelayed(long delayMilliseconds) {
            // We try only with the AttachInfo because there's no point in invalidating
            // if we are not attached to our window
            if (mAttachInfo != null) {
                Message msg = Message.obtain();
                msg.what = AttachInfo.INVALIDATE_MSG;
                msg.obj = this;
                mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
            }
        }

    问题2:自定义View的绘制流程

    1. 自定义View的属性
    2. 在View的构造方法中获得我们自定义View的步骤
    3. 重写onMeasure(不必须)
    4. 重写onDraw
    5. 重写onTouchEvent事件(不必须)

    除了太基础的题目(原则)之外,面试者答错题并不算是严重的问题,严重的是面试者思路让别人难以理解,或者说面试者没有很好的表达能力让别人更容易理解他的思路。

    回答思路:

    1. 问题背景(让面试官听得懂你的问题产生自什么情景,以助于了解问题Why)
    2. 问题描述(问题的详细描述,让面试官听懂问题是怎么回事?Where、When、What)
    3. 解决思路(有哪些解决方案,其中有哪些坑?how)
    4. 最终效果(最后选择了什么方案,并怎么解决的坑?how mush)

    可不可以在使用第三方开源库时,就是没有问题呢?当然可以,不过你可以聊一聊你为什么要选择这个库,其实就是从侧面来说明:你对这个库的优缺点的看法,以及你是否了解它的实现原理,因为你要对自己的项目负责(开源作者可不需要)。

    引用块内容
    进一步学习可研究浅谈Android中的MVP与动态代理的结合

    一人个在成长中是会经历各个不同的阶段的,这些阶段从他常浏览的风站也能窥视一二。所以,问对方常浏览的网站这样的面试题,我一般会这样问:“你以前常上(喜欢)什么网站,现在常上什么网站,为什么你会有这个转变?”根据面试者的回答,你可以从侧面了解他目前处于一个什么样的阶段。如果一个开发没有常浏览的网站,其实你也很容易定义他为“不爱学习,对Android的热情、兴趣不大”,意外的情况应该很少。

    • 为什么要使用Binder?
    • Binder运行机制
    • Binder的线程管理
    • Binder对应用开发者的用处

    一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。理解这一点的话,你做进程间通信时处理并发问题就会有一个底,比如使用ContentProvider时(又一个使用Binder机制的组件),你就很清楚它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程在跑。

    会使用AIDL进行进程间通信;
    会手写AIDL的编码,加深对Binder机制的理解。

    需求分析

    • 可以浏览用户已经上传到服务器的图片;
    • 可以查看某张图片的详情;
    • 选择和编辑要上传的图片;(从本地或者摄像头获取图片)
    • 工作线程上传一张图片到服务器;

    架构设计

    • 框架的设计:UI和逻辑的分层设计,错误处理机制 (这点其实很重要)
    • 性能要求:列表要滑动顺畅,不会出现OOM
    • 应对需求变代的可扩展性:是否需要加入用户登录和注册?每张图片是否可以点赞或者添加评论?是否要加上保存和收藏图片功能?编辑图片时添加滤镜等图片处理效果;搜索、定位、个人偏好的算法等等。

    相关的模块和涉及的技术点:

    • Retrofit做网络请求和解析(Gson);
    • Http文件上传的协义;
    • 图片浏览的性能优化或者遇到的问题:如图片库的加载和ListView的展示配合,RecyclerView瀑布流展示页面跳动等问题;
    • 框架的设计:UI和逻辑的分离问题;
    • 无网或网络异常时的处理;

    吹牛回答方向

    • 和对方交流清楚,确定理解面试官关注和考察的重点;
    • 尽可能在谈设计思路时考虑到一些实际问题(边界、异常等),这方面的经历其实正是面试官想看到的;
    • 从用户的角度去展开和设计这个应用;

    讨论结束

    也许你不相信,接下来最重要的不是你的技术水平,而是我们称为编码之外的能力。而这些能力往往又有些主观,难于用一两道诸如算法题来测试你的能力值。而考察你的输出能力就是一个很好的验证你编码外能力的办法。
    当你学会怎么理解别人和怎么让别人更好的理解你,这个编码之外的能力给你带来的效果可能会远远好于你的技术水平。

    如果我们直接这样组合就认为是一个应用框架的话,那我认为你还没有真正认识框架,或者没有遇到稍大一点复杂一点的项目,所以你毫不费力就有了自己“高大上”的框架。

    但是在你整合这些库时,你更应该学习一下他们是怎么能无缝地对接上的,这一点也是我认为可以问面试者的一个重要的点。如Retrofit的解耦方式:

    • 通过注解来配置请求参数;
    • 通过工厂来生成CallAdapter,Converter。

    Kotlin的一个主要优点是它的简洁。你用很少的代码就可以实现原来Java写的功能,而你写的代码越少,你犯错误的概率就越小。光这个原因我就比较推荐大家尝试一下Kotlin来开发应用。

    常用招式

    • merge标签
    • ViewStub标签
    • include标签
    • 用RelativeLayout代替LinearLayout
    • 使用ConstraintLayout减少布局嵌套

    新招式
    FlexBoxLayout

    面向对象设计的SOLID原则:

    • S 单一功能原则:对象应该仅具有一种单一功能。
    • O 开闭原则:软件体应该是对于扩展开放的,但是对于修改封闭的。
    • L 里氏替换原则:程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的。
    • I 接口隔离原则:多个特定客户端接口要好于一个宽泛用途的接口。
    • D 依赖反转原则:依赖于抽象而不是一个实例,依赖注入是该原则的一种实现方式。

    标准 参考答案
    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。观察者模式完美的将观察者和被观察的对象分离开,一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
    回调函数其实也算是一种观察者模式的实现方式,回调函数实现的观察者和被观察者往往是一对一的依赖关系。
    所以最明显的区别是观察者模式是一种设计思路,而回调函数式一种具体的实现方式;另一明显区别是一对多还是多对多的依赖关系方面。

    Java内存模型

    Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。程序中的变量存储在主内存中,每个线程拥有自己的工作内存并存放变量的拷贝,线程读写自己的工作内存,通过主内存进行变量的交互。JMM就是规定了工作内存和主内存之间变量访问的细节,通过保障原子性、有序性、可见性来实现线程的有效协同和数据的安全。

    JVM如何判断一个对象实例是否应该被回收?

    垃圾回收器会建立有向图的方式进行内存管理,通过GC Roots来往下遍历,当发现有对象处于不可达状态的时候,就会对其标记为不可达,以便于后续的GC回收。

    说说JVM的垃圾回收策略。
    JVM采用分代垃圾回收。在JVM的内存空间中把堆空间分为年老代和年轻代。将大量创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象。

    一个content provider可以接受来自另外一个进程的数据请求。尽管ContentResolver与ContentProvider类隐藏了实现细节,但是ContentProvider所提供的query(),insert(),delete(),update()都是在ContentProvider进程的线程池中被调用执行的,而不是进程的主线程中。这个线程池是有Binder创建和维护的,其实使用的就是每个应用进程中的Binder线程池。


    后记

    整理这篇面试题花了我不少精力,把题刷一遍以后再将面试题抄一遍的做法看上去显得有一些笨拙。而为了看上去不是这么笨拙,我对作者提到的问题专门去思考了自己平时看见的问题以及查阅相关的资料,并将这些资料放在了相对应的面试题下面。整理本文的初衷是为了应付面试官,但是越看越觉得自己欠缺的越多,因此不管是作者分享的面试经验还是作者分享的技术细节对自己都是如此弥足珍贵!这个过程就好像当初我们上学一样,一开始为了应付老师和家长的要求,但是随着自己逐渐长大才发现这个过程自己的收获远远大于应付老师和家长的要求。
    最后,努力将面试题中提到的短板补足,也希望各位看到这里的看官都能找到一份满意的工作或者在文章中找到了自己想要的东西!


    展开全文
  • Spring Boot四大神器之CLI

    万次阅读 2019-03-06 22:19:58
    Spring Boot CLI 为Spring Cloud 提供了Spring Boot 命令行功能。您可以编写groovy脚本来运行Spring Cloud 组件应用程序(例如@enableurekaserver)。您还可以轻松地执行加密和解密等操作,以支持具有机密配置值的 ...

    1.简介

    Spring Boot CLI 为Spring Cloud 提供了Spring Boot 命令行功能。您可以编写groovy脚本来运行Spring Cloud 组件应用程序(例如@enableurekaserver)。您还可以轻松地执行加密和解密等操作,以支持具有机密配置值的 SpringCloud 配置客户端。使用启动器cli,您可以从命令行方便地同时启动诸如eureka、zipkin、config server等服务(这在开发时非常有用)。

    这篇简短的文章快速介绍了如何配置Spring Boot CLI并执行简单的终端命令来运行预配置的微服务。

    我们将在本文中使用Spring Boot CLI 2.0.0.RELEASE。可以在Maven Central找到最新版本的Spring Boot CLI 。

    2.设置Spring Boot CLI

    设置 Spring Boot CLI 的最简单方法之一是使用SDKMAN。可以在此处找到SDKMAN的安装和安装说明。

    安装SDKMAN后,运行以下命令自动安装和配置Spring Boot CLI:

    $ sdk installspringboot
    

    要验证安装,请运行以下命令:

    $ spring --version
    

    我们还可以通过源代码编译来安装Spring Boot CLI,Mac用户可以使用Homebrew或MacPorts的预构建软件包。有关所有安装选项,请参阅官方文档

    3.通用终端命令

    Spring Boot CLI 提供了一些开箱即用的有用命令和功能。其中一个最有用的功能是Spring Shell,它使用必要的 spring 前缀包装命令。
    要启动嵌入式shell,我们运行:

    spring shell
    

    从这里,我们可以直接输入所需的命令,而无需预先挂起 spring 关键字(因为我们现在在spring shell中)。

    例如,我们可以通过键入以下内容来显示正在运行的CLI 的当前版本:

    version
    

    另外中一个最重要的命令是告诉Spring Boot CLI 运行Groovy脚本:

    run [SCRIPT_NAME].groovy
    

    Spring Boot CLI 将自动推断依赖关系,或者在给定正确提供的注释的情况下执行此操作。在此之后,它将启动一个嵌入式Web容器和应用程序。

    让我们仔细看看如何在 Spring Boot CLI 中使用Groovy脚本!

    4.基本的Groovy脚本

    Groovy和Spring与Spring Boot CLI结合在一起,可以在单个Groovy文件部署中快速编写功能强大,高性能的微服务。

    对多脚本应用程序的支持通常需要额外的构建工具,如Maven或Gradle。

    下面我们将介绍 Spring Boot CLI 的一些最常见的用例。

    有关所有Spring支持的Groovy注释的列表,请查看官方文档

    4.1 @Grab

    @Grab注释和Groovy的Java式的进口条款允许依赖管理和注射。

    实际上,大多数注释都抽象、简化并自动包含必要的import语句。这使我们可以花更多的时间来考虑架构以及我们想要部署的服务的基础逻辑。

    我们来看看如何使用@Grab注释:

    package org.test
     
    @Grab("spring-boot-starter-actuator")
     
    @RestController
    class ExampleRestController{
      //...
    }
    

    正如我们所看到的,spring-boot-starter-actuator是预先配置的,允许简洁的脚本部署,无需定制的应用程序或环境属性,XML或其他编程配置,但必要时可以指定这些内容。

    @Grab参数的完整列表- 每个都指定要下载和导入的库 - 可在此处获得。

    4.2 @ Controller,@ RestController和@EnableWebMvc

    为了进一步加快部署,我们可以使用Spring Boot CLI提供的“抓取提示”来自动推断要导入的正确依赖项。

    我们将介绍下面一些最常见的用例。

    例如,我们可以使用熟悉的@Controller和@Service注释来快速构建标准MVC控制器和服务:

    @RestController
    class Example {
      
        @Autowired
        private MyService myService;
     
        @GetMapping("/")
        public String helloWorld() {
            return myService.sayWorld();
        }
    }
     
    @Service
    class MyService {
        public String sayWorld() {
            return "World!";
        }
    }
    

    Spring Boot CLI支持Spring Boot的所有默认配置。因此,我们的Groovy应用程序可以自动从其通常的默认位置访问静态资源。

    4.3 @ EnableWebSecurity

    要将 Spring Boot Security 选项添加到我们的应用程序,我们可以使用@EnableWebSecurity注解,然后由 Spring Boot CLI 自动下载。

    下面,我们将使用spring-boot-starter-security依赖项来抽象此过程的一部分,该依赖项利用了引擎下的@EnableWebSecurity注释:

    package org.test
     
    @Grab("spring-boot-starter-security")
     
    @RestController
    class SampleController {
     
        @RequestMapping("/")
        public def example() {
            [message: "Hello World!"]
        }
    }
    

    有关如何保护资源和处理安全性的更多详细信息,请查看官方文档

    4.4 @Test

    要设置一个简单的JUnit测试,我们可以添加@Grab(‘junit’)或@Test注解:

    package org.test
     
    @Grab('junit')
    class Test {
        //...
    }
    

    这将允许我们轻松地执行JUnit测试。

    4.5 DataSource和JdbcTemplate

    可以指定持久数据选项,包括DataSource或JdbcTemplate,而无需显式使用@Grab注释:

    package org.test
     
    @Grab('h2')
    @Configuration
    @EnableWebMvc
    @ComponentScan('org.test')
    class DataConfig {
     
        @Bean
        DataSource dataSource() {
            return new EmbeddedDatabaseBuilder()
              .setType(EmbeddedDatabaseType.H2).build();
        }
     
    }
    

    通过简单地使用熟悉的Spring bean配置约定,我们获取了H2嵌入式数据库并将其设置为DataSource。

    5.自定义配置

    使用 Spring Boot CLI 配置 Spring Boot 微服务有两种主要方法:

    1. 我们可以在终端命令中添加参数
    2. 我们可以使用自定义的YAML文件来提供应用程序配置

    Spring Boot会自动在/config目录中搜索application.yml或application.properties

    ├── app
        ├── app.groovy
        ├── config
            ├── application.yml
        ...
    

    我们还这样可以设置:

    ├── app
        ├── example.groovy
        ├── example.yml
        ...
    

    应用程序属性的完整列表,可以在这里看到。

    6.结论

    以上是 Spring Boot CLI 的快速演练!有关更多详细信息,请查看官方文档

    展开全文
  • Socket编程

    千次阅读 多人点赞 2019-04-19 15:12:04
      针对网络通信的不同层次,Java提供了不同的API,其提供的网络功能四大类: InetAddress:用于标识网络上的硬件资源,主要是IP地址 URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据 ...

    Socket编程

      在网络编程中,使用最多的就是Socket。像大家熟悉的QQ、MSN都使用了Socket相关的技术。

    一、计算机网络基础知识

      关于计算机网络基础知识可以参考博客:
      https://blog.csdn.net/u014644574/article/details/89301205

    1、两台计算机间进行通讯需要以下三个条件:

      IP地址、协议、端口号

    2、TCP/IP协议:

      TCP/IP是目前世界上应用最为广泛的协议,是以TCP和IP为基础的不同层次上多个协议的集合,也称TCP/IP协议族、或TCP/IP协议栈

    • TCP:Transmission Control Protocol 传输控制协议
    • IP:Internet Protocol 互联网协议

    3、TCP/IP四层模型

    • 应用层:HTTP、FTP、POP3、SMTP、Telnet、SSH、DNS等
    • 传输层:TCP/UDP
    • 网络层:IP
    • 网络接口层: 包括数据链路层(负责网络寻址)和物理层(通过物理介质:双绞线、光纤等传输二进制数据)

    4、IP地址

      为实现网络中不同计算机之间的通信,每台计算机都必须有一个唯一的标识—IP地址。使用32位二进制表示。

    5、端口

      区分一台主机的多个不同应用程序,端口号范围为0-65535,其中0-1023位为系统保留。
      如:HTTP:80 FTP:21 Telnet:23
      IP地址+端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础

    6、Socket套接字:

      网络上具有唯一标识的IP地址和端口组合在一起才能构成唯一能识别的标识符套接字。

      Socket原理机制:

    • 通信的两端都有Socket
    • 网络通信其实就是Socket间的通信
    • 数据在两个Socket间通过IO传输

    7、Java中的网络支持

      针对网络通信的不同层次,Java提供了不同的API,其提供的网络功能有四大类:

    • InetAddress:用于标识网络上的硬件资源,主要是IP地址
    • URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据
    • Sockets:使用TCP协议实现的网络通信Socket相关的类
    • Datagram:使用UDP协议,将数据保存在用户数据报中,通过网络进行通信。

    二、TCP编程

    1、TCP协议

      是面向连接的、可靠的、有序的、以字节流的方式发送数据,通过三次握手方式建立连接,形成传输数据的通道,在连接中进行大量数据的传输,效率会稍低

    2、Java中基于TCP协议实现网络通信的类

      客户端的Socket类
      服务器端的ServerSocket类

    3、Socket通信的步骤

    • ① 创建ServerSocket和Socket
    • ② 打开连接到Socket的输入/输出流
    • ③ 按照协议对Socket进行读/写操作
    • ④ 关闭输入输出流、关闭Socket

    4、服务器端:

    • ① 创建ServerSocket对象,绑定监听端口
    • ② 通过accept()方法监听客户端请求
    • ③ 连接建立后,通过输入流读取客户端发送的请求信息
    • ④ 通过输出流向客户端发送相应信息
    • ⑤ 关闭相关资源
    // 服务端
    // 1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
    ServerSocket serverSocket = new ServerSocket(10086);// 1024-65535的某个端口
    // 2、调用accept()方法开始监听,等待客户端的连接
    Socket socket = serverSocket.accept();
    // 3、获取输入流,并读取客户端信息
    InputStream is = socket.getInputStream();
    InputStreamReader isr = new InputStreamReader(is, "utf-8");
    BufferedReader br = new BufferedReader(isr);
    String info = null;
    while ((info = br.readLine()) != null) {
    	System.out.println("我是服务器,客户端说:" + info);
    }
    socket.shutdownInput();// 关闭输入流
    // 4、获取输出流,响应客户端的请求
    OutputStream os = socket.getOutputStream();
    PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "utf-8"));
    pw.write("欢迎您!");
    pw.flush();
    // 5、关闭资源
    pw.close();
    os.close();
    br.close();
    isr.close();
    is.close();
    socket.close();
    serverSocket.close();
    

    5、客户端:

    • ① 创建Socket对象,指明需要连接的服务器的地址和端口号
    • ② 连接建立后,通过输出流想服务器端发送请求信息
    • ③ 通过输入流获取服务器响应的信息
    • ④ 关闭响应资源
    // 客户端
    // 1、创建客户端Socket,指定服务器地址和端口
    Socket socket = new Socket("192.168.9.128", 10086);
    // 2、获取输出流,向服务器端发送信息
    OutputStream os = socket.getOutputStream();// 字节输出流
    PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "utf-8"));// 将输出流包装成打印流
    pw.write("用户名:admin;密码:123");
    pw.flush();
    socket.shutdownOutput();
    // 3、获取输入流,并读取服务器端的响应信息
    InputStream is = socket.getInputStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(is, "utf-8"));
    String info = null;
    while ((info = br.readLine()) != null) {
    	System.out.println("我是客户端,服务器说:" + info);
    }
    // 4、关闭资源
    br.close();
    is.close();
    pw.close();
    os.close();
    socket.close();  
    

    6、应用多线程实现服务器与多客户端之间的通信

    • ① 服务器端创建ServerSocket,循环调用accept()等待客户端连接
    • ② 客户端创建一个socket并请求和服务器端连接
    • ③ 服务器端接受苦读段请求,创建socket与该客户建立专线连接
    • ④ 建立连接的两个socket在一个单独的线程上对话
    • ⑤ 服务器端继续等待新的连接
    package socket;
    
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * 多线程服务端
     */
    public class ServerThread extends Thread {
    
    	private Socket socket = null;
    
    	public ServerThread(Socket socket) {
    		this.socket = socket;
    	}
    
    	@Override
    	public void run() {
    		try {
    			// 3、获取输入流,并读取客户端信息
    			InputStream is = socket.getInputStream();
    			InputStreamReader isr = new InputStreamReader(is, "utf-8");
    			BufferedReader br = new BufferedReader(isr);
    			String info = null;
    			while ((info = br.readLine()) != null) {
    				System.out.println("我是服务器,客户端说:" + info);
    			}
    			socket.shutdownInput();// 关闭输入流
    			// 4、获取输出流,响应客户端的请求
    			OutputStream os = socket.getOutputStream();
    			PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "utf-8"));
    			pw.write("欢迎您!");
    			pw.flush();
    			// 5、关闭资源
    			pw.close();
    			os.close();
    			br.close();
    			isr.close();
    			is.close();
    			socket.close();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    	}
    
    	public static void main(String[] args) {
    		try {
    
    			// 服务器代码
    			ServerSocket serverSocket = new ServerSocket(10086);
    			Socket socket = null;
    			int count = 0;// 记录客户端的数量
    			while (true) {
    				socket = serverSocket.accept();
    				ServerThread thread = new ServerThread(socket);
    				thread.start();
    				count++;
    				System.out.println("客户端连接的数量:" + count);
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    	}
    }
      
    

    三、UDP编程

      UDP协议(用户数据报协议)是无连接的、不可靠的、无序的,速度快,进行数据传输时,首先将要传输的数据定义成数据报(Datagram),大小限制在64k,在数据报中指明数据索要达到的Socket(主机地址和端口号),然后再将数据报发送出去
      DatagramPacket类:表示数据报包
      DatagramSocket类:进行端到端通信的类

    1、服务器端实现步骤

    • ① 创建DatagramSocket,指定端口号
    • ② 创建DatagramPacket
    • ③ 接受客户端发送的数据信息
    • ④ 读取数据
    	// 服务器端,实现基于UDP的用户登录
    	// 1、创建服务器端DatagramSocket,指定端口
    	DatagramSocket socket = new DatagramSocket(10010);
    	// 2、创建数据报,用于接受客户端发送的数据
    	byte[] data = new byte[1024];
    	DatagramPacket packet = new DatagramPacket(data, data.length);
    	// 3、接受客户端发送的数据
    	socket.receive(packet);// 此方法在接受数据报之前会一致阻塞
    	// 4、读取数据
    	String info = new String(data, 0, packet.getLength(), "utf-8");
    	System.out.println("我是服务器,客户端告诉我:" + info);
    	
    	// =========================================================
    	// 向客户端响应数据
    	// 1、定义客户端的地址、端口号、数据
    	InetAddress address = packet.getAddress();
    	int port = packet.getPort();
    	byte[] data2 = "欢迎您!".getBytes("utf-8");
    	// 2、创建数据报,包含响应的数据信息
    	DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
    	// 3、响应客户端
    	socket.send(packet2);
    	// 4、关闭资源
    	socket.close();
    

    2、客户端实现步骤

    • ① 定义发送信息
    • ② 创建DatagramPacket,包含将要发送的信息
    • ③ 创建DatagramSocket
    • ④ 发送数据
    	// 客户端
    	// 1、定义服务器的地址、端口号、数据
    	InetAddress address = InetAddress.getByName("192.168.9.128");
    	int port = 10010;
    	byte[] data = "用户名:admin;密码:123".getBytes("utf-8");
    	// 2、创建数据报,包含发送的数据信息
    	DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
    	// 3、创建DatagramSocket对象
    	DatagramSocket socket = new DatagramSocket();
    	// 4、向服务器发送数据
    	socket.send(packet);
    
    	// 接受服务器端响应数据
    	// ======================================
    	// 1、创建数据报,用于接受服务器端响应数据
    	byte[] data2 = new byte[1024];
    	DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
    	// 2、接受服务器响应的数据
    	socket.receive(packet2);
    	String reply = new String(data2, 0, packet2.getLength(), "utf-8");
    	System.out.println("我是客户端,服务器说:" + reply);
    	// 4、关闭资源
    	socket.close();
    

    四、注意问题:

    1、多线程的优先级问题:
      根据实际的经验,适当的降低优先级,否侧可能会有程序运行效率低的情况
    2、是否关闭输出流和输入流:
      对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可
    3、使用TCP通信传输对象,IO中序列化部分
    4、socket编程传递文件,IO流部分

    展开全文
  • 注:本笔记是根据尚硅谷的MyBatis视频记录的 ...在四大对象创建的时候: 1、每个创建出来的对象不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler); 2、获取到所有的Intercep...
  • 四大组件的生命周期 目录四大组件的生命周期Activity生命周期:这个结合它的各个方法来看方法间的区别Activity的切换Activity的四种启动模式当按下Home键时,引起的变化内存资源不足,导致低优先级Activity被杀死...
  • 基本数据类型分为四大类型:布尔型、整数型、浮点型、字符型。 布尔型:boolean 整数型:byte,short,int,long 浮点型:float,double 字符型:char 引用类型: 数据 类 接口 对比 byte占1个字节,8位 short...
  • 5.mContentResolver( ApplicationContentResolver):继承于 CR,主要功能是通过 Uri 来获取文件、数据库、Asset、Res 等数据,还有就是通过 ContentProvider 来获取其他应用和本机数据。 6.mResourcesManager...
  • Java 基础数据按类型可以分为四大类:布尔型、整数型、浮点型、字符型,这四大类包含 8 种基础数据类型。 布尔型:boolean 整数型:byte、short、int、long 浮点型:float、double 字符型:char 八种基础类型取值...
  • “付款”功能满足的是我的支付需求,“余额宝”功能满足的是我的理财需求,而“蚂蚁森林”功能则满足我的社交与趣味需求,所以我会留在支付宝,成为它的活跃使用用户。 我们的服务号,现在也有几个功能在支撑着...
  • 中国食品包装机械行业发展状况分析及未来前瞻报告2021-2027年 +++HS++++HS+++HS+++HS++++HS++++HS++++HS++++HS+++HS+++HS++++ 【撰写单位】:鸿晟信合研究院 第一章 包装机械行业分析 第一节 包装机械行业的基本概述...
  • java的(三)四大特性

    千次阅读 2018-10-01 20:55:04
    为什么说息息相关,就用刚才举的例子,我有一个水果类,我让一些类都继承了这个水果类,然后继承了这些水果类既有了水果的特征属性也可以有自己独特的属性,所以呢一般子类功能都比父类强大。这就是继承的概念。 ...
  • 可增加装饰器@name.setter装饰,也就是在@property装饰之后,加上setter函数再包装已被包装为property实例的name,如: class Book(object): def __init__(self,name,sale): self.__name = name #私有 self.__...
  • java 包装类详细解析

    2020-05-17 16:20:03
    Java 基本数据按类型可以分为四大类:布尔型、整数型、浮点型、字符型,这四大类包含 8 种基本数据类型。 布尔型:boolean 整数型:byte、short、int、long 浮点型:float、double 字符型:char float 单浮点型 0.0f...
  • 近日,香港人工智能与机器人学会(HKSAIR)创会理事长、微众银行首席AI官、香港科技大学讲席教授杨强老师,领衔HKSAIR《AI金融》系列线上讲座第一课,主讲联邦学习及其四大应用场景。 以下为杨强教授演讲全文,雷...
  • 四大组件原理在探

    2020-07-03 13:27:25
    四大组件是什么 Android四大组件有Activity,Service服务,Content Provider内容提供,BroadcastReceiver。 四大组件的生命周期和简单用法 activity:onCreate() -> onStart() -> onResume() -> onPause()...
  • ),主要功能是通过Uri来获取文件、数据库、asset、res等数据,还有就是通过ContentProvider来获取其他应用和本机数据。 6.mResourcesManager(ResourcesManager):单例,因为一个apk不同机型的适配资源,所以用来...
  • 随着人们生活水平的提高,平面设计在我们的日常生活中显得越来越重要,无论是淘宝店铺装修,还是企业产品包装、形象宣传,甚至是我们日常穿的衣服、住的房子都离不开平面设计的参与,近几年来平面设计从业人员在逐年...
  • | 最后总结beautifulsoup的四大对象种类 tag:标签 NavigableString:包装在tag中的字符串 BeautifulSoup:这个文档的全部内容 comment:特殊类型 这就是我这位爬虫小白对beautifulsoup库的一些非常肤浅的理解,还望...
  • Android中的四大组件详解 我们都知道Android系统应用层框架中,为开发者提供了四大组件来便于应用的开发,它们是Activity、Service、BroadcastReceiver、ContentProvider。 它们用于在应用开发过程中,不同场景的...
  • 面向对象的四大特征

    2019-09-27 09:24:46
    面向对象的四大特征:封装,继承,多态,抽象。 1,封装(Encapsulation):封装,一种将抽象性函式接口的实作细节部份包装、隐藏起来的方法。 也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只 让...
  • Java六大线程池和四大拒绝策略 学习常见的 6 种线程池,并详细讲解 Java 8 新增的 ForkJoinPool 线程池,6 种常见的线程池如下。 FixedThreadPool CachedThreadPool ScheduledThreadPool ...
  • 第一章 四大组件

    千次阅读 多人点赞 2019-06-03 14:14:59
    1、介绍一下四大组件 Activity:是一个应用程序组件,提供一个界面,可以与用户交互来实现功能。 Service(服务):提供需在后台长期运行的服务,无用户界面 Content Provider(内容提供者):实现不同应用间进行数据...
  • Java 基础数据按类型可以分为四大类:布尔型、整数型、浮点型、字符型,这四大类包含 8 种基础数据类型。 布尔型:boolean 整数型:byte、short、int、long 浮点型:float、double 字符型:char 八种基础类型...
  • Java 的 IO 四大基类详解(上)

    千次阅读 2017-10-30 20:54:31
    使用处理流的好处是:只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码访问不同的数据源,随着处理流包装节点流的变化,程序实际访问的数据源也在变化。Java用处理流包装节点流是一个典型的装饰器模式...
  • 接下来的分析先从MMS中四大组件(Activity ,BroadCastReceiver,Service,ContentProvider),也是MMS中最核心的部分入手: 一. Activity  1、ConversationList 对话列表界面,这是进入应用程序的主界面。它有两个...
  • Android 四大组件 之 Service

    千次阅读 2020-02-16 12:00:12
    Android 四大组件之一,特点是无需界面,用于在后台处理耗时的操作或长期任务。甚至在程序退出的情况下,我们也可以让 Service 在后台继续保持运行状态。 二、 生命周期 先来一张经典的图: 从图上分析: Service...
  • ”谋定研究中国智库网:对话功能性农业农业大健康大会(国际)农民丰收节贸易会,全国工商联执委、中国经济和信息化研究中心主任、国家政策研究室中国国情研究中心主任万祥军研读表明:在中国农业大变革的当下,其...
  • 盘点机器人四大家族——KUKA机器人 特盖德智能装备特盖德机器人租赁今天 德国KUKA机器人(库卡)是Johann Josef Keller和Jakob Knappich于1898年在德国巴伐利亚州的奥格斯堡(Augsburg)正式创立,是世界领先的工业...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,454
精华内容 2,981
关键字:

包装的四大功能