轻量级的DI/IoC和AOP容器的框架,支持事务的处理,对框架整合的支持!
(1)管理创建和组装对象之间的依赖关系
Controller -> Service -> Dao
UserControoler
private UserService userService = new UserService();
(2)面向切面编程 - AOP
作用:解耦核心业务和边缘业务的关系
场景:用户调用下单购买视频接口,需要判断登录,拦截器是AOP思想的一种体现。
使用前:代码写逻辑,每次下单都能调用方法判断,多个方法需要判断登录则都需要登陆方法判断。
(3)包含了大型项目里面常见解决方案,包括Web层、业务层、数据访问层等。
(4)极其便利的整合其他主流技术栈,比如redis、mq、mybaties、jpa等。
(1)创建Maven项目
(2)添加依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.2.5.RELEASEversion>
dependency>
(3)添加bean配置文件applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="video" class="com.dx.ds.domain.Video"
scope="prototype">
<property name="name" value="tom"/>
<property name="id" value="23"/>
bean>
beans>
(4)创建模块的Video类
package com.dx.ds.domain;
public class Video {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(5)创建APP类获取容器中的Bean
package com.dx.ds;
import com.dx.ds.domain.Video;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、获取Bean
Video video = (Video) applicationContext.getBean("video");
// 3、输出Bean的属性
System.out.println("Bean的ID:"+video.getId()+"Bean的name:"+video.getName());
}
}
(1)业务需求一:获取用户的配置信息
public interface UserDao {
void getuser();
}
public class UserDaoImpl implements UserDao{
@Override
public void getuser() {
System.out.println("获取用户的配置信息!");
}
}
public interface UserService {
void getUser()
}
public class UserServiceImpl {
private UserDao userDao = new UserDaoImpl();
public void getUser(){
userDao.getuser();
}
}
public class MyTest {
public static void main(String[] args) {
// 用户实际上调用的是业务层,DAO层不需要接触。
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
(2)业务需求二:在原有基础上添加业务需求之获取Oracle/mysql中的用户配置信息
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getuser() {
System.out.println("获取Mysql用户配置信息");
}
}
public class UserDaoOraclImpl implements UserDao{
@Override
public void getuser() {
System.out.println("获取Oracle的用户配置信息!");
}
}
private UserDao userDao = new UserDaoOraclImpl();
private UserDao userDao = new UserDaoMysqlImpl();
(4)缺点
用户的需求更改可能会影响我们的源代码,我们需要根据用户需求更改源代码!如果程序代码量十分大,修改一次的成本非常昂贵。
public class UserServiceImpl {
private UserDao userDao;
// 利用Set进行动态实现值的注入
public void setUserDao(){
this.userDao = userDao;
}
public void getUser(){
userDao.getuser();
}
}
public class MyTest {
public static void main(String[] args) {
// 用户实际上调用的是业务层,DAO层不需要接触。
UserService userService = new UserServiceImpl();
((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
这种思想从本质上解决了问题,程序员不在去管理对象的创建,系统的耦合性大大降低,可以更加专注的在业务的实现上,这就是IOC的原型。
(1)早期:业务层创建对象,创建对象的主动权在程序员手里,程序员创建什么才会调用什么。
(2)控制反转:主动权在用户手里,用户选择调用什么程序才会被动的创建对象。
(3)代码实战
①创建hello/helloSpring类
public class HelloSpring {
private String str;
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
public class Hello {
private String str;
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
②配置XML文件 - Spring2.x版本方式
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="HelloSpring" class="com.kuang.pojo.HelloSpring">
<property name="str" value="Spring"/>
bean>
<bean id="Hello" class="com.kuang.pojo.Hello">
<property name="str" value="Spring2"/>
bean>
beans>
③创建test类测试代码
public class test {
public static void main(String[] args) {
// 获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// 我们的对象现在都在Spring中管理了,我们要使用直接在里面取出来就可以
HelloSpring helloSpring = (HelloSpring) context.getBean("HelloSpring");
System.out.println(helloSpring.toString());
Hello hello = (Hello) context.getBean("Hello");
System.out.println(hello.toString());
}
}
(4)IOC控制反转 - 程序从主动的创建对象变成被动的接收。
对象创建:对象由Spring创建
对象属性设置:由Spring容器来设置
控制:谁来控制对象的创建?
反转:程序本身不创建对象,而是被动接收对象
依赖注入:利用set方法来进行注入。
(5)IOC创建对象方式
配置文件加载的时候,容器中管理的对象就已经初始化了!
<bean id="user" class="com.peach.pojo.User">
<constructor-arg index="0" value="peach">constructor-arg>
bean>
<bean id="user" class="com.peach.pojo.User">
<constructor-arg type="java.lang.String" value="peach"/>
bean>
<bean id="user" class="com.peach.pojo.User">
<constructor-arg name="name" value="peach">constructor-arg>
bean>
(1)bean元素:使用该元素描述spring容器管理的对象。
(2)id元素:Bean的唯一标识,也就是对象名。
(2)name属性:给被管理的对象起个名字,获得对象是根据名称获得对象,也就是别名。
(3)class属性:被管理对象的完整类名,也就是包名+类名。
(4)import属性:用于团队开发,可以将多个配置文件导入合并为1个。
<import resource="bean1.xml"/>
(5)scope属性:规定Bean的作用域,常见的有以下五种。
依赖注入:在创建对象的同时或之后,如何给对象的属性赋值,即在运行期,由Spring根据配置文件,将其他对象的引用通过组件的提供的setter方法进行设定。
依赖:Bean对象创建依赖于容器。
注入:Bean对象中的所有属性,由容器来注入。
Spring中bean有三种装配机制,分别是:
(1)Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
1、配置扫描哪些包下的注解
<!--指定注解扫描包-->
<context:component-scan base-package="com.kuang.pojo"/>
2、在指定包下编写类,增加注解
@Component("user")
// 相当于配置文件中
public class User {
public String name = "秦疆";
}
3、测试
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
(2)属性注入:使用注解注入属性
①可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相当于配置文件中
public class User {
@Value("秦疆")
// 相当于配置文件中
public String name;
}
②如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")
public class User {
public String name;
@Value("秦疆")
public void setName(String name) {
this.name = name;
}
}
(3)衍生注解
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
写上这些注解,就相当于将这个类交给Spring管理装配了!
(4)自动装配注解
见以上模式。
(5)作用域 @scope
@Controller("user")
@Scope("prototype")
public class User {
@Value("秦疆")
public String name;
}
(6)小结
XML与注解比较
xml与注解整合开发 :推荐最佳实践
<context:annotation-config/>
作用:
①编写一个实体类,Dog
@Component //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
public String name = "dog";
}
②新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();
}
}
③测试
@Test
public void test2(){
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);
}
④成功输出结果
作用域名 | 描述 |
---|---|
singleton | Bean以单例方式存在,在Spring IoC容器仅存在一个Bean实例 |
prototype | Bean以单例方式存在,每次从容器中调用Bean时,都会返回一个新的实例,频繁创建/销毁对象会造成很大开销 |
(1)单例模式
<bean name="video" class="com.dx.ds.domain.Video" scope="singleton">
<property name="name" value="tom"/>
<property name="id" value="23"/>
bean>
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testScope(applicationContext);
}
public static void testScope(ApplicationContext applicationContext) {
// 2、获取Bean
Video video1 = (Video) applicationContext.getBean("video");
Video video2 = (Video) applicationContext.getBean("video");
// 3、验证内存地址是否先相等
System.out.println(video1==video2);
}
}
(2)多例模式
<bean name="video" class="com.dx.ds.domain.Video" scope="prototype">
<property name="name" value="tom"/>
<property name="id" value="23"/>
bean>
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testScope(applicationContext);
}
public static void testScope(ApplicationContext applicationContext) {
// 2、获取Bean
Video video1 = (Video) applicationContext.getBean("video");
Video video2 = (Video) applicationContext.getBean("video");
// 3、验证内存地址是否先相等
System.out.println(video1==video2);
}
}
(1)使用属性set方法注入
public class Video {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
System.out.println("set注入测试id");
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("set注入测试name");
this.name = name;
}
}
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testScope(applicationContext);
}
public static void testScope(ApplicationContext applicationContext) {
// 2、获取Bean
Video video1 = (Video) applicationContext.getBean("video");
// 3、验证set注入
System.out.println(video1.getId());
}
}
(2)使用带参构造函数注入
<bean name="video" class="com.dx.ds.domain.Video" scope="singleton">
<constructor-arg name="name" value="⾯试专题课程第⼀季">constructor-arg>
bean>
package com.dx.ds.domain;
public class Video {
private int id;
private String name;
public Video(String name) {
this.name = name;
System.out.println("带参构造函数被调用!!!");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testScope(applicationContext);
}
public static void testScope(ApplicationContext applicationContext) {
// 2、获取Bean
Video video1 = (Video) applicationContext.getBean("video");
// 3、验证set注入
System.out.println(video1.getId());
}
}
(3)属性注入
①配置文件
<context:annotation-config />
<context:component-scan base-package="pojo"/>
②类上@Component注解,使用@Value注解为各属性赋初值
@Component
public class Video2 {
@Value("111")
private int id;
@Value("哔哩哔哩")
private String name;
private String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
(4) List & Map类型注⼊
<bean name="video" class="com.dx.ds.domain.Video" scope="singleton">
<property name="chapterList">
<list>
<value>第⼀章SpringBootvalue>
<value>第⼆章Mybatisvalue>
<value>第三章Springvalue>
list>
property>
<property name="videoMap">
<map>
<entry key="1" value="SpringCloud课程">entry>
<entry key="2" value="⾯试课程">entry>
<entry key="3" value="javaweb课程">entry>
map>
property>
bean>
public class Video {
private int id;
private String title;
private List<String> chapterList;
private Map<Integer,String> videoMap;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<String> getChapterList() {
return chapterList;
}
public void setChapterList(List<String> chapterList) {
this.chapterList = chapterList;
}
public Map<Integer, String> getVideoMap() {
return videoMap;
}
public void setVideoMap(Map<Integer, String> videoMap) {
this.videoMap = videoMap;
}
}
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testScope(applicationContext);
}
public static void testScope(ApplicationContext applicationContext) {
// 2、获取Bean
Video video1 = (Video) applicationContext.getBean("video");
// 3、验证set注入
System.out.println(video1.getChapterList());
System.out.println(video1.getVideoMap());
}
}
(1)继承
①配置文件
<bean name="video" class="com.dx.ds.domain.Video" scope="singleton">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
bean>
<bean name="video2" class="com.dx.ds.domain.Video2" scope="singleton" parent="video">
<property name="name" value="课程" >property>
bean>
②Video类文件
public class Video {
private int id;
private String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
③Video2类文件
public class Video2 {
private int id;
private String name;
private String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
④APP类
public class APP {
public static void main(String[] args) {
// 1、配置Beans的xml文件路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testScope(applicationContext);
}
public static void testScope(ApplicationContext applicationContext) {
// 2、获取Bean
Video2 video2 = (Video2) applicationContext.getBean("video2");
// 3、继承
System.out.println(video2.getName());
System.out.println(video2.getTitle());
}
}
(2)依赖
①配置文件
<bean name="video" class="com.dx.ds.domain.Video" scope="singleton">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
bean>
<bean name="video2" class="com.dx.ds.domain.Video2" scope="singleton" depends-on="video">
<property name="name" value="课程" >property>
<property name="title" value="SpringBoot" />
bean>
②Video、Video2、APP的类同上
(1)Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
(2)Bean实例化后对将Bean的引入和值注入到Bean的属性中
(3)如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
(4)如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
(5)如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
(6)如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
(7)如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
(8)如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
(9)此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
(10)如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
①Spring IOC容器化实例Bean;
②调⽤BeanPostProcessor的postProcessBeforeInitialization⽅法 ;
③调⽤bean实例的初始化⽅法 ;
④调⽤BeanPostProcessor的postProcessAfterInitialization⽅法。
(1)自动装配概述
①注解方式:本质就是对类中的变量进行自动赋值操作
②使用方式:基于@Autowired的自动装配,默认是根据类型注入,可以用于构造器、字段、方法注入。
③Spring的自动装配需要从两个角度来实现,或者说是两个操作:
④autowire设置值
(2)在XML文件中配置ByName
注解逻辑:
①将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
②去spring容器中寻找是否有此字符串名称id的对象。
③如果有,就取出注入;如果没有,就报空指针异常。
<bean id="user" class="com.kuang.pojo.User" autowire="byName">
<property name="str" value="qinjiang"/>
bean>
(3)实战案例
①将User类中的set方法去掉,使用@Autowired注解
②配置文件内容
(1)POP概述
(2)AOP概述
在不改变原有逻辑上增加额外的功能,比如解决系统层面的问题,或者添加新功能。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。
(3)应用场景
(4)AOP思想关注点
(5)好处
(1)横切关注点
对哪些⽅法进⾏拦截,拦截后怎么处理,这些就叫横切关注点 ⽐如 权限认证、⽇志、事物
(2)通知 Advice
在特定的切⼊点上执⾏的增强处理,有5种通知,后⾯讲解 做啥? ⽐如你需要记录⽇志,控制事务 ,提前编写好通⽤的模块,需要的地⽅直接调⽤ 。
(3)连接点 JointPoint
要⽤通知的地⽅,业务流程在运⾏过程中需要插⼊切⾯的具体位置, ⼀般是⽅法的调⽤前后,全部⽅法都可以是连接点 只是概念,没啥特殊
(4)切⼊点 Pointcut
不能全部⽅法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那⼏个你想要 的⽅法 在程序中主要体现为书写切⼊点表达式(通过通配、正则表达式)过滤出特定的⼀组 JointPoint连接点 过滤出相应的 Advice 将要发⽣的joinpoint地⽅
(5)切⾯ Aspect
通常是⼀个类,⾥⾯定义 切⼊点+通知 , 定义在什么地⽅; 什么时间点、做什么事情 通知 advice指明了时间和做的事情(前置、后置等)
(6)切⼊点 pointcut
指定在什么地⽅⼲这个事情 web接⼝设计中,web层->⽹关层->服务层->数据层,每⼀层之间也是⼀个切⾯,对象和对 象,⽅法和⽅法之间都是⼀个个切⾯
(7)⽬标 target
⽬标类,真正的业务逻辑,可以在⽬标类不知情的条件下,增加新的功能到⽬标类的链路上
(8)织⼊ Weaving
把切⾯(某个类)应⽤到⽬标函数的过程称为织⼊
(9)AOP代理
AOP框架创建的对象,代理就是⽬标对象的加强 Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理
(1)静态代理
由程序创建或特定工具自动生成源代码,在程序运行前代理类的.class文件就已经存在
(2)实现方式
将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的。
(3)实战案例
public interface PayService {
String callback(String outTradeNo);
int save(int userId,int productId);
}
public class PayServiceimpl implements PayService{
public String callback(String outTradeNo) {
System.out.println("回调方法callback");
return outTradeNo;
}
public int save(int userId, int productId) {
System.out.println("下单方法Save");
return productId;
}
}
public class StaticPayServiceimpl implements PayService {
// 1、构建PayService类对象
private PayService payService;
// 2、构造函数注入
public StaticPayServiceimpl(PayService payService) {
this.payService = payService;
}
@Override
public String callback(String outTradeNo) {
// 3、注入日志功能
System.out.println("调用payService对象的callback方法前");
String result = payService.callback(outTradeNo);
System.out.println("调用payService对象的callback方法后");
return result;
}
@Override
public int save(int userId, int productId) {
System.out.println("调用payService对象的save方法前");
int userid = payService.save(userId,productId);
System.out.println("调用payService对象的save方法后");
return userid;
}
}
public class ProxyTest {
public static void main(String[] args) {
// 将目标类与代理类实现同一个接口,让代理类持有真实类对象
PayService payService = new StaticPayServiceimpl(new PayServiceimpl());
payService.save(199,203);
}
}
(4)优缺点
(1)JDK动态代理
①核心对象 - Proxy代理类
// 创建目标类的代理
/**
* 1、类加载器
* 2、目标类实现接口
* 3、方法拦截时执行的InvocationHandler的方法
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
②核心对象 - InvocationHandler代理类
// 重写invoke方法
/**
* (1)Object proxy:被代理的对象
* (2)Method method:要被调用的方法
* (3)Obeject[] args:方法被调用时所需参数
**/
public interface InvocationHandler{
public Object invoke(Object proxy,Method method,Object[] args) thows Thowable;
}
(2)代码实战
// 1、创建一个实现接口InvocationHandler的类,必须实现invoke方法
public class JdkProxy implements InvocationHandler {
// 2、创建被代理类的对象
private Object targetObject;
// 3、代理类与目标类绑定关系,使代理类能调用目标类的方法
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 特殊说明必须实现invoke方法
Object result = null;
try{
// AOP逻辑
System.out.println("运行前日志");
method.invoke(targetObject,args);
// AOP逻辑
System.out.println("运行后日志");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
public class ProxyTest {
public static void main(String[] args) {
// 1、创建代理类对象
JdkProxy jdkProxy = new JdkProxy();
// 2、获取代理类对象,传入目标类
PayService payService = (PayService)jdkProxy.newProxyInstance(new PayServiceimpl());
// 3、调用目标方法
payService.callback("唯品会");
}
}
(2)CGLIB动态代理
①业务主类
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
②重写方法拦截在方法前和方法后加入业务逻辑
/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor {
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
③main主类
public class ProxyTest {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
(1)如果⽬标对象实现了接口,则默认采⽤JDK动态代理
(2)如果⽬标对象没有实现接口,则采⽤CgLib进⾏动态代理
(3)如果⽬标对象实现了接口,程序⾥⾯依旧可以指定使⽤CGlib动态代理
在开发过程中,我们经常会需要对方法进行一些简单的监控,例如监控某个方法的执行时间,必要的时候打印入参和返回值,对抛出的异常进行记录。这样的一些监控点虽然很小,但是这些重复的代码散落在各处而且侵入到业务逻辑当中让业务代码显得非常杂乱。
三个核心包:
项目实战
①定义接口
public interface Video {
int save(Video video);
Video findIdBy(int id);
}
②定义目标类
public class VideoServiceImpl implements Video{
@Override
public int save(Video video) {
System.out.println("保存Video");
return 1;
}
@Override
public Video findIdBy(int id) {
System.out.println("根据ID找视频");
return (Video) new Video2();
}
}
// Video类
public class Video2 {
@Value("111")
private int id;
@Value("哔哩哔哩")
private String name;
private String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
③横切关注点
// 横切关注点
public class TimeHander {
public void printBefore(){
System.out.println("pring log_time_start:"+ LocalTime.now().toString());
System.out.println("pring log_time_end:"+ LocalTime.now().toString());
}
}
④AOP织入文件配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"
xmlns:aop="http://www.springframework.org/schema/aop">
<bean name="video" class="com.dx.ds.domain.Video" scope="singleton" autowire="byName">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
bean>
<bean name="video2" class="com.dx.ds.domain.Video2" scope="singleton" autowire="byName">
<property name="name" value="课程" >property>
<property name="title" value="SpringBoot" />
bean>
<bean id="TimeHander" class="com.dx.ds.proxy.TimeHander"/>
<bean id="videoService" class="com.dx.ds.proxy.VideoServiceImpl"/>
<aop:config>
<aop:aspect id="timeAspect" ref="TimeHander">
<aop:pointcut id="allMethodLogPointCut" expression="execution(* com.dx.ds.proxy.VideoService.*(..))"/>
<aop:before method="printBefore" pointcut-ref="allMethodLogPointCut"/>
<aop:after method="printAfter" pointcut-ref="allMethodLogPointCut"/>
aop:aspect>
aop:config>
beans>
⑤main类测试
public class APP {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
testAOP(applicationContext);
}
public static void testAOP(ApplicationContext context){
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.save(new Video());
}
}
注解作用 | 注解名称 | 注解含义 |
---|---|---|
bean定义 | @Controller | 用于web层 |
@Service | 用于service层 | |
@Repository | 用于dao层 | |
bean取名 | @Component(“name”) | 用于Bean命名 |
bean注入 | @Autowired | 自动装配 |
bean生命周期 | @PostConstruct | Bean初始化 |
@PreDestroy | Bean销毁 | |
bean作用范围 | @scope | Bean作用域 |
(1)@Component注解类
①main类
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 1、扫描指定包及子包,将其
context.scan("net.xdclass");
// 2、通知Spring进行上下文,完成初始化,即刷新生成Bean
context.refresh();
// 4、获取Bean
VideoService videoService = (VideoService) context.getBean("videoService");
// 5、调用Bean的findid方法
videoService.findById(2);
}
}
②VideoService接口
public interface VideoService {
int save(Video video);
Video findById(int id);
}
③VideomServiceImpl实现类
@Component("videoService")
// 3、Spring扫描包时,遇到Compenent时就会把类实力化为Bean.
public class VideoServiceImpl implements VideoService {
public int save(Video video) {
System.out.println("保存video");
return 1;
}
public Video findById(int id) {
System.out.println("根据id找视频");
return new Video();
}
}
(2)@Configuration和@Bean注解定义第三方Bean
①AppConfig类
@Configuration
public class AppConfig {
@Bean
public VideoOrder videoOrder(){
return new VideoOrder();
}
}
②APP类
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 1、扫描指定包及子包,将其
context.scan("net.xdclass");
// 2、通知Spring进行上下文,完成初始化,即刷新生成Bean
context.refresh();
// 4、获取Bean
VideoOrder videoOrder = (VideoOrder) context.getBean("videoOrder");
}
}
③Video类
public class Video {
private int id;
@Autowired
private String title;
public void init(){
System.out.println("video类 init 方法被调用");
}
public void destroy(){
System.out.println("video类 destroy 方法被调用");
}
public Video(){
//System.out.println("video 空构造函数被调用");
}
public Video(String title){
//System.out.println("video 带参数构造函数被调用");
this.title = title;
}
public int getId() {
return id;
}
public void setId(int id) {
//System.out.println("Video setId方法被调用");
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
//System.out.println("Video setTitle方法被调用");
this.title = title;
}
}
(3)PropertySource和@Value加载配置文件信息
①创建config.properties配置文件
server.host=192.168.6.102
server.port=1000
②创建配置文件对应的Config.class类,将配置信息文件注入
@Configuration
@PropertySource(value = {"classpath:config.properties"})
public class Config {
@Value("${server.host}")
private String host;
@Value("${server.port}")
private int port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
③生成Bean并自动装配
@Component("videoService")
// 3、Spring扫描包时,遇到Compenent时就会把类实力化为Bean.
public class VideoServiceImpl implements VideoService {
@Autowired
private Config config;
public int save(Video video) {
System.out.println("保存video");
return 1;
}
public Video findById(int id) {
System.out.println("根据id找视频");
System.out.println("打印配置文件信息"+config.getHost()+":"+config.getPort());
return new Video();
}
}
④APP类获取Bean
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 1、扫描指定包及子包,将其
context.scan("net.xdclass");
// 2、通知Spring进行上下文,完成初始化,即刷新生成Bean
context.refresh();
// 4、获取Bean
VideoService videoService = (VideoService) context.getBean("videoService");
// 5、调用方法
videoService.findById(1);
}
}
(1)AOP注解入门概述
注解名称 | 注解详述 | 备注 |
---|---|---|
@Aspect | 告诉Spring这是一个切面类,里面可以定义切入点和通知 | |
@Pointcut | 切入点,配置切入点相关路径 | |
@Before | 前置通知 | |
@After | 后置通知 |
①创建切面类
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
public class LogAdvice {
//切入点表达式,也可以直接在通知上编写切入点表达式
@Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
//@Before("aspect()")
@Before("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void beforeLog(JoinPoint joinPoint){
System.out.println("LogAdvice beforeLog");
}
//后置通知
@After("aspect()")
public void afterLog(JoinPoint joinPoint){
System.out.println("LogAdvice afterLog");
}
}
②开启AOP注解配置和扫描
注解名称 | 注解详述 | 备注 |
---|---|---|
@EnableAspectJAutoProxy | 开启spring对Aspect的支持 | |
@Pointcut | 切入点,配置切入点相关路径 |
@Configuration
@ComponentScan("net.xdclass")
@EnableAspectJAutoProxy //开启了spring对aspect的支持
public class AnnotationAppConfig {
}
③APP类
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
}
}