一:Spring概述、Spring体系介绍
Spring的核心是控制反转(IoC)和面向切面(AOP)
spring是开源的、轻量级的、一站式的框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核
二:Spring安装配置
在工程目录下的pom.xml中配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
可在网上查找,粘贴过来
三:IOC
IOC容器初始化
ApplicationContext实现类
ApplicationContext是一个接口,有以下几个不同的实现类:
ClassPathXmlApplicationContext
AnnotationConfigApplicationContext
FileSystemXmlApplicationContext
XmlWebApplicationContext
ClassPathXmlApplicationContext
使用方法:
-
创建实体类
package com.lanou3g.bean; import lombok.Getter; import lombok.Setter; @Getter @Setter public class Man { private Food name; private String hobby; private int age; private String sex; private String food; public void eat(){ System.out.println("我叫" + name + ",我今年" + age + "岁了,我喜欢吃:" + food); } public void play(){ System.out.println("我叫" + name + ",我今年" + age + "岁了,我喜欢玩:" + hobby); } }
-
配置Spring xml文件
<?xml version="1.0" encoding="UTF-8"?> <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="man" class="com.lanou3g.bean.Man"> <property name="age" value="18"></property> <property name="food" value="橘子"></property> <property name="hobby" value="吃鸡"></property> <!-- <property name="name" value="赵云"></property>--> <property name="name" ref="apple"></property> <property name="sex" value="女"></property> </bean> <bean id="apple" class="com.lanou3g.bean.Food"> <property name="name" value="张飞"></property> </bean> </beans>
-
使用
public class App { public static void main( String[] args ) { useSpringIOC(); } public static void useSpringIOC(){ //加载Spring上下文配置文件 ApplicationContext cxt = new ClassPathXmlApplicationContext("applicationContext.xml"); //获取bean Man man = (Man) cxt.getBean("man"); Man man1 = cxt.getBean("man",Man.class); Man man2 = cxt.getBean(Man.class); man.eat(); man.play(); System.out.println("man:" + man); System.out.println("man1:" + man1); System.out.println("man2:" + man2); } }
如果是两个xml文件以上,可以使用字符串数组
ApplicationContext cxt = new ClassPathXmlApplicationContext(new String[]{“applicationContext.xml”,“applicationContext1.xml”});
或者使用通配符
ApplicationContext cxt = new ClassPathXmlApplicationContext(“classpath:/*.xml”);
AnnotationConfigApplicationContext
使用方法:
-
创建实体类
package com.lanou3g.bean; import lombok.Getter; import lombok.Setter; @Getter @Setter public class Student { private String name; private int age; }
-
创建@Configuration配置类
package com.lanou3g; import com.lanou3g.bean.Student; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration //表明是bean的配置类 public class AppConfig { @Bean public Student student1(){ //方法名相当于id Student student = new Student(); student.setName("张三"); student.setAge(18); return student; } @Bean public Student student2(){ Student student = new Student(); student.setName("赵云"); student.setAge(20); return student; } }
@configuration可理解为用Spring的时候xml里面的标签
@Bean可理解为用Spring的时候xml里面的标签
-
使用
public class App { public static void main( String[] args ) { // useSpringIOC(); useSpringIOC2(); } public static void useSpringIOC2(){ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("AppConfig.class"); Student student1 = (Student) ctx.getBean("student1"); System.out.println(student1.getName()); System.out.println(student1.getAge()); Student student2 = (Student) ctx.getBean("student2"); System.out.println(student2.getName()); System.out.println(student2.getAge()); } }
FileSystemXmlApplicationContext
XmlWebApplicationContext
这两种跟上面的类似
Application初始化路径
路径前缀
// 前缀classpath:表示的是项目的classpath下相对路径
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
路径中的通配符
// 使用通配符加载所有符合要求的文件
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml");
通过Xml方式配置管理bean
schema约束地址不能用https,否则每次都要从Spring加载,无网不能运行(新版本已解决这个问题)
使用步骤:
- 创建实体类
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Man {
private String hobby;
private int age;
private String sex;
private String food;
public void eat(){
System.out.println("我叫" + name + ",我今年" + age + "岁了,我喜欢吃:" + food);
}
public void play(){
System.out.println("我叫" + name + ",我今年" + age + "岁了,我喜欢玩:" + hobby);
}
}
- 配置xml
<?xml version="1.0" encoding="UTF-8"?>
<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="man" class="com.lanou3g.bean.Man">
<property name="age" value="18"></property>
<property name="food" value="橘子"></property>
<property name="hobby" value="吃鸡"></property>
<property name="name" ref="apple"></property>
<property name="sex" value="女"></property>
</bean>
</beans>
通过注解方式管理bean
两种方式:
第一种:
- 创建实体类
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
import org.springframework.stereotype.Component;
@Component //把普通实体类实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
@Getter
@Setter
public class Student {
private String name;
private int age;
}
- 创建配置类
package com.lanou3g;
import com.lanou3g.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration //表明该类是Spring的一个配置类,该类中会包含应用上下文创建bean的具体细节
@ComponentScan(basePackages = "com.lanou3g")//开启注解扫描支持,同时指定扫描包根路径
public class MyConfiguration {
}
- 使用
package com.lanou3g;
import com.lanou3g.bean.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main( String[] args ) {
//加载Spring上下文配置文件
ApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfiguration.class);
testIOCBean(cxt);
}
public static void testIOCBean(ApplicationContext cxt){
//获取bean
Student stu = cxt.getBean(Student.class);
System.out.println(stu.getName());
System.out.println(stu.getAge());
}
}
第二种:
- 创建实体类
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Student {
private String name;
private int age;
}
- 创建配置类
package com.lanou3g;
import com.lanou3g.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//加上@Configuration注解后,这个类就相当于变成了一个Spring上下文配置文件
@Configuration
public class AppConfig {
@Bean
public Student student1(){//方法名相当于id
Student student = new Student();
student.setName("张三");
student.setAge(18);
return student;
}
@Bean
public Student student2(){
Student student = new Student();
student.setName("赵云");
student.setAge(20);
return student;
}
}
@configuration可理解为用Spring的时候xml里面的标签
@Bean可理解为用Spring的时候xml里面的标签
- 使用
public class App {
public static void main( String[] args ) {
// useSpringIOC();
useSpringIOC2();
}
public static void useSpringIOC2(){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("AppConfig.class");
Student student1 = (Student) ctx.getBean("student1");
System.out.println(student1.getName());
System.out.println(student1.getAge());
Student student2 = (Student) ctx.getBean("student2");
System.out.println(student2.getName());
System.out.println(student2.getAge());
}
}
注意:
package com.lanou3g.service;
import com.lanou3g.bean.Student;
import com.lanou3g.dao.StudentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service //自动扫描(注入dao)
public class StudentService {
@Autowired //自动装配
private StudentDao studentDao;
public Student getStudentById(Integer id) {
return studentDao.findStuById(id);
}
}
在一个配置中导入另一个配置
xml中导入其他xml配置
这是application xml的配置
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 导入其他spring配置的.xml文件 -->
<import resource="classpath:other.xml" />
</beans>
other.xml的配置
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
注解方式导入
导入其他注解配置
-
创建Dog和Cat类
package com.example.demo; @Configuration public class Dog { } package com.example.demo; @Configuration public class Cat { }
-
在启动类中需要获取Dog和Cat对应的bean,需要用注解@Import注解把Dog和Cat的bean注入到当前容器中。
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; @ComponentScan /*把用到的资源导入到当前容器中*/ @Import({Dog.class, Cat.class}) public class App { public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println(context.getBean(Dog.class)); System.out.println(context.getBean(Cat.class)); context.close(); } }
导入xml配置
还是上面的Dog和Cat类,现在在一个配置类中进行配置bean,然后在需要的时候,只需要导入这个配置就可以了,最后输出结果相同。
-
MyConfig 配置类:
package com.example.demo; import org.springframework.context.annotation.Bean; public class MyConfig { @Bean public Dog getDog(){ return new Dog(); } @Bean public Cat getCat(){ return new Cat(); } }
-
若在启动类中要获取Dog和Cat的bean,如下使用:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; @ComponentScan /*导入配置类就可以了*/ @Import(MyConfig.class) public class App { public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println(context.getBean(Dog.class)); System.out.println(context.getBean(Cat.class)); context.close(); } }
深入了解IOC
管理bean的作用域
scope:配置bean的作用域
<bean id="man" class="com.lanou.spring.bean.Man" scope="prototype">
</bean>
singleton(默认)
单例。整个IOC容器中只有一个bean的实例,而且该bean的实例会在IOC容器实例化的时候创建。
<bean id="man" class="com.lanou.spring.bean.Man" scope="singleton">
<property name="age" value="26" />
<property name="hobby" value="吃饭,睡觉" />
<property name="gender" value="女" />
<property name="name" value="孙尚香" />
<!--引用-->
<property name="food" ref="apple" />
</bean>
prototype
原形。整个IOC容器中有多个bean的实例,在容器初始化的时候不会创建,只有通过getBean方法获取bean的实例时,IOC容器都会创建一个新的对象返回。
<bean id="man" class="com.lanou.spring.bean.Man" scope="prototype">
<property name="age" value="26" />
<property name="hobby" value="吃饭,睡觉" />
<property name="gender" value="女" />
<property name="name" value="孙尚香" />
<!--引用-->
<property name="food" ref="apple" />
</bean>
管理bean的生命周期
-
Singleton Bean的生命周期
<bean id="zhangFei" class="com.lanou.spring.bean.Man" init-method="init" destroy-method="destroy"> <property name="name" value="张飞" /> </bean>
scope:singleton单例模式的生命周期,默认IOC容器初始化的时候就会调用init
-
Prototype Bean的生命周期
- 初始化时机: 在实际使用该bean的时候,比如:getBean、获取依赖此bean的其他bean需要使用
- 销毁时机: 在IOC容器销毁时。(但是通过destroy-method指定的声明周期方法不会被调用,也就是说Spring不提供prototypebean完整的生命周期管理)
-
如何指定生命周期的回调方法
- xml中的init-method、destory-method
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
-
注解方式@PostConstrutor、@PreDetory
@PostConstruct注解Bean中的某些方法,可以用在服务器启动时的做一些初始化工作。
import javax.annotation.PostConstruct;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
@Service("bean1")
public class Bean1 {
private static final Logger log = Logger.getLogger(Bean1.class);
@PostConstruct
public void test() {
}
@PreDetory
public void test1() {
}
}
- 指定默认的声明周期回调方法
- 在xml中,通过在beans标签上添加default-init-method、default-destory-method来指定
- 在注解配置中,没有对应的方法可以设置所有bean默认的生命周期回调
id和name命名的bean
- 在XML中配置中可以通过标签上的id、name属性值给一个bean命名,以便在其他地方引用。
- id属性: bean的唯一名称,只允许出现一个值。且同一个IOC容器中不允许出现两个id值一样的bean。
- name属性: 和id类似也是给bean命名。但是name属性的值可以有多个,多个值之间使用英文逗号或者英文分号或者空格符隔开
实例化bean的方式
-
构造方法
-
静态工厂方法
- 创建实体类
package com.lanou3g; import lombok.Getter; @Getter public class Women { private String name; public Women(String name) { this.name = name; } }
- 创建工厂
package com.lanou3g.factory; import com.lanou3g.Women; public class WomenFactory { public Women newInstance(String name) { return new Women(name); } }
- 配置xml
<bean id="women" class="com.lanou3g.factory.WomenFactory" factory-method="newInstance"> <constructor-arg name="name" value="张三" /> </bean>
-
实例工厂方法
- 创建实体类
package com.lanou3g; import lombok.Getter; @Getter public class Women { private String name; public Women(String name) { this.name = name; } }
- 创建工厂
package com.lanou3g.factory; import com.lanou3g.Women; public class WomenFactory { public static Women newInstance(String name) { return new Women(name); } }
- 配置xml
<!-- 使用实例工厂方法创建bean对象 --> <!-- Spring中无法使用对象的方式去调用静态工厂方法,比如下面这么写就不行 --> <!-- <bean id="womenFactory" class="com.lanou3g.factory.WomenStaticFactory" />--> <bean id="womenFactory" class="com.lanou3g.factory.WomenFactory" /> <bean id="xiaoFang" factory-bean="womenFactory" factory-method="newInstance"> <constructor-arg name="name" value="小芳" /> </bean>
优雅的关闭IOC容器
添加一个shutdown hook。所有派生自ConfigurableApplicationContext接口的实现类都支持此方法
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("xxx.xml");
ctx.registerShutdownHook(); //注册停止回调
懒加载
懒加载配置主要是针对单例的bean,因为它默认是在容器初始化时就被实例化了。
- lazy-init属性(默认是false)
spring-di-xml配置方式
构造方法注入
创建java类
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Setter
@Getter
public class Man {
private String name;
private Man gf;
private List<String> hobbies;
private Map<String, String> gameTitle;
public Man() {}
public Man(String name) {
this.name = name;
}
}
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class User {
private String username;
private String password;
private String email;
}
创建UserDao
package com.lanou3g.dao;
import com.lanou3g.bean.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
import java.util.List;
@Component
public class UserDao {
@Autowired
private QueryRunner queryRunner;
public List<User> selectAll() {
String sql = "select username, password, email from user;";
try {
List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class));
return userList;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
有参
<!-- 带参数的构造方法创建bean -->
<!-- 使用constructor-arg注入构造参数 -->
<!--<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource" />
</bean>-->
<bean id="lisi" class="com.lanou3g.spring.bean.Man">
<constructor-arg name="name" value="李四" />
</bean>
测试代码
import com.lanou3g.bean.Man;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class testInjection extends BaseTest{
@Autowired
private ApplicationContext ctx;
/**
*构造方法注入
*/
@Test
public void testConstructorInjection(){
Man zhangsan = ctx.getBean("zhangsan", Man.class);
Man lisi = ctx.getBean("lisi", Man.class);
System.out.println(ReflectionToStringBuilder.toString(zhangsan));
System.out.println(ReflectionToStringBuilder.toString(lisi));
}
}
无参
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"/>
测试代码
package com.lanou3g;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import javax.sql.DataSource;
public class TestBaseTest extends BaseTest{
@Autowired
private DataSource dataSource;
@Test
public void testConnectionPool(){
System.out.println(dataSouce);
}
}
通过c命名空间注入参数
原来的头部
<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
</beans>
c命名空间的头部
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
<!--添加的-->
xmlns:c="http://www.springframework.org/schema/c"
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
<!-- 添加的--> http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--自动扫描文件-->
<context:component-scan base-package="com.lanou3g"/>
c命名空间的使用
<!-- 使用c命名空间来注入构造参数(请注意文件头的schema) -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" c:ds-ref="dataSource" />
<bean id="zhangsan" class="com.lanou3g.spring.bean.Man" c:name="张三" />
setter注入
支持注入的类型
- 普通字面量
- String
- Integer(int)
- Long(long)
- Byte(byte)
- …
setter方法注入参数
外层用包裹,内部属性用或者
<!-- setter方法注入参数 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
<property name="asyncInit" value="true" />
</bean>
使用实例
import com.lanou3g.bean.Man;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class testInjection extends BaseTest{
@Autowired
private ApplicationContext ctx;
@Test
public void testSetterInjection(){
Man xiaohong = ctx.getBean("xiaohong", Man.class);
System.out.println(ReflectionToStringBuilder.toString(xiaohong));
}
}
注入匿名内部bean
外层用包裹,内部属性用或者
,在或者内部再包裹
<!-- 使用内部bean注入 -->
<bean id="xxx" class="com.lanou3g.spring.bean.Man">
<property name="name" value="张三" />
<property name="gf">
<!-- 内部bean只能在这里使用,别的地方无法引用,即使有id属性也不行 -->
<bean id="yyy" class="com.lanou3g.spring.bean.Man" p:name="李四" />
</property>
</bean>
使用示例
import com.lanou3g.bean.Man;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class testInjection extends BaseTest{
@Autowired
private ApplicationContext ctx;
/**
* 测试内部bean
*/
@Test
public void testInnerBeanInjection(){
Man kangJian = ctx.getBean("xxx", Man.class);
System.out.println(kangJian.getName() + "的GF是" + kangJian.getGf().getName());
}
}
通过p命名空间注入属性
原来的头部
<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
</beans>
p命名空间的头部
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
<!--添加的-->
xmlns:p="http://www.springframework.org/schema/p"
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
<!-- 添加的--> http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--自动扫描文件-->
<context:component-scan base-package="com.lanou3g"/>
使用
<!-- 使用p命名空间注入属性 -->
<bean id="xiaohong" class="com.lanou3g.bean.Man" p:name="xaiohong"/>
注入集合类型属性
实体类类中有一个属性为数组类型或list(map)等,如:
package com.lanou3g.bean;import lombok.Getter;import lombok.Setter;import java.util.List;import java.util.Map;@Setter@Getterpublic class Man { private String name; private Man gf; private List<String> hobbies; private Map<String, String> gameTitle; public Man() { } public Man(String name) { this.name = name; } }
<!--注入集合类型属性-->
<bean id="xiaoMing" class="com.lanou3g.bean.Man">
<!--List-->
<property name="hobbies">
<list>
<value>简单类型值</value>
<bean>内部bean</bean>
<ref bean="xxx" />
</list>
</property>
<!--Map-->
<property name="gameTitle">
<map>
<entry key="王者荣耀" value="倔强青铜"/>
<entry key="吃鸡" value="超级王牌"/>
</map>
</property>
<!--Set-->
<property name="hobbies">
<!-- set用法和List类似, 里面可以注入普通字面量值、也可以是一个bean引用,或者内部bean、或者是一个set、list、Properties -->
<set>
<value>简单类型值</value>
<bean>内部bean</bean>
<ref bean="xxx" />
</set>
</property>
<!--java.util.Properties-->
<!-- props标签是用来注入java.util.Properties类型的属性,用法和map类似,但是属性值是在标签中间写 -->
<property name="gameNick">
<props>
<prop key="王者荣耀">最擅长1V5</prop>
<prop key="吃鸡">一枪爆头</prop>
</props>
</property>
</bean>
使用
package com.lanou3g;
import com.lanou3g.bean.Man;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class testInjection extends BaseTest{
@Autowired
private ApplicationContext ctx;
@Test
public void testCollectionInjection(){
/**
* 使用list注入集合属性
*/
Man xiaoMing = ctx.getBean("xiaoMing", Man.class);
System.out.println(xiaoMing.getName() + "的爱好是" + xiaoMing.getHobbies());
/**
* 使用Map注入集合属性
*/
System.out.println(xiaoMing.getName() + "的游戏段位是:" + xiaoMing.getGameTitle());
}
}
注入null、空字符串类型属性值
<bean id="xiaoMing" class="com.lanou3g.bean.Man">
<!--注入空值和null-->
<property name="marrayStatus" value=""/>
<property name="gender">
<null/>
</property>
</bean>
使用
package com.lanou3g;
import com.lanou3g.bean.Man;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class testInjection extends BaseTest{
@Autowired
private ApplicationContext ctx;
/**
* 注入null、空字符串类型属性值
*/
@Test
public void testNullOrEmptyInjection(){
Man man = ctx.getBean("xiaoMing",Man.class);
System.out.println(man.getName()+"婚姻状况:" + man.getMarrayStatus()+", 字段值长度:" + man.getMarrayStatus().length());
System.out.println(man.getName()+"性别:" + man.getGender());
}
}
注入复合属性
<bean id="xiaoMing" class="com.lanou3g.bean.Man">
<!-- 注入复合属性 -->
<property name="username">
<bean class="com.lanou3g.bean.User" />
</property>
<property name="username.username" value="三国演义"/>
</bean>
使用
package com.lanou3g;
import com.lanou3g.bean.Man;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
public class testInjection extends BaseTest{
@Autowired
private ApplicationContext ctx;
/**
* 注入复合属性
*/
@Test
public void testComboPropertyInjection() {
Man man = ctx.getBean("xiaoMing", Man.class);
System.out.println(man.getName() + "最喜欢看的一本书:" + man.getUsername().getUsername());
}
}
注入外部properties文件中的属性值
<bean id="xiaoMing" class="com.lanou3g.bean.Man">
<property name="gameNick">
<props>
<prop key="王者荣耀">最擅长1V5</prop>
<prop key="吃鸡">一枪爆头</prop>
</props>
</property>
</bean>
自动装配
自动装配支持的策略
使用autowire属性可以开启自动注入(不需要显式使用setter、构造方法注入属性值)可选的自动注入策略有四个:
no: 不自动注入(默认值)
byType: 自动根据属性类型去IOC中寻找,如果找到的匹配类型多于一个会 报错
byName: 根据setter方法的名称去IOC中找bean的名称对应的,如果找 到,就自动注入;否则不注入
constructor: 类似于byType,但是注入的地方是构造方法的参数。 查找流程:
首先按照所需参数类型去IOC容器中查找,如果只找到一个就自动装配;如果有多个匹配的类型,就按照参数的名称进一步筛查;如果没有找到一个匹配的类型,就报错。
<!--构建bean-->
<bean class="com.lanou3g.dao.UserDao"autowire="constructor">
</bean>
<bean id="xh" class="com.lanou3g.bean.Man" p:name="小红" autowire="constructor" />
将某个类从自动装配候选中排除
当按照类型自动装配时,如果有多个匹配类型的bean满足条件,我们可以给候选的bean添加autowire-candidate属性,让其退出竞争。
开启自动装配
<!--
通过给当前的bean添加autowire属性开启自动注入
可选的值:参见自动装配章节
-->
<bean id="xx" class="" autowire="" />
提高自动装配时的权重
<!-- 当其他的bean中需要注入一个Test类型的属性,而满足条件的bean有多个时,会优先注入primary="true"的bean -->
<bean id="xx" class="com.Test" primary="true" />
注解方式提高自动装配时的权重
@Primary
@Component
public class Test {
}
public class Main {
@Primary
@Bean
public void test() {
return new Test();
}
}
按类型自动装配时,不参与候选
!-- 当其他的bean中需要注入一个Test类型的属性,而满足条件的bean有多个时,autowire-candidate="false"的bean会自动退出候选序列 -->
<bean id="xx" class="com.Test" autowire-candidate="false" />
spring-di-注解配置方式
创建实体类
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class User {
private String username;
private String password;
private String email;
}
package com.lanou3g.bean;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Setter
@Getter
public class Man {
public Man(User username) {
this.username = username;
}
private String name;
private Man gf;
private List<String> hobbies;
private Map<String, String> gameTitle;
private String marrayStatus;
private String gender;
private User username;
public Man() {}
public Man(String name) {
this.name = name;
}
}
创建dao
package com.lanou3g.dao;
import com.lanou3g.bean.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
//@Component
//把普通Java类实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
@Repository
public class UserDao {
@Autowired //自动注入
private QueryRunner queryRunner;
public List<User> selectAll() {
String sql = "select username, password, email from user;";
try {
List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class));
return userList;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
@Configuration
标识一个类作为Spring的一个bean配置文件
@componentScan
指定要扫描哪些包下的注解,相当于xml中的
<context:component-scope base-packag=“org.example” />
@Autowired
开启自动注入功能
默认是byType注入。如果候选的bean多于一个,则会报错。 可以配合@Qualifier注解实现byName注入。
常用属性:
- required: 注入的属性是否是必要的,默认是true。必要注入属性如果没有找到可以注入的目标bean,会直接报错。
@Autowired(required = false) //required默认是true
private QueryRunner queryRunner;
@PropertySource
将外部的properties引入IOC容器,配合@value属性自动将properties中的属性注入到类中使用@value注解的属性上
@PropertySource(“classpath:/jdbc.properties”)
注意后面的格式
@Configuration //标识一个类作为spring的一个bean文件
@ComponentScan(basePackages = "com.lanou3g") //指定要扫描哪些包下的注解
//将外部的properties引入IOC容器,配合@value属性自动将properties中的属性注入到类中使用@value注解的属性上
@PropertySource("classpath:/jdbc.properties")
@Qualifier
有两个作用:
- 配合@Autowired注解使用, 将自动注入方式由byType改成byName
- 配合@Bean使用,给bean命名
@Autowired
@Qualifier("miYue")//此注解配合上面的自动注入注解(代表按照名称 注入bean byName)
private Girl girl;
@Bean
public Girl miYue() {
return new Girl("芈月");
}
@Resource
- 这是JSR-250规范里的原生注解,Spring也支持用此注解实现自动注入
- 此注解的工作原理:先按照byName模式去查找bean,如果没找到自动切成成byType模式
- 相当于 @Autowired和@Qualifier(“miYue”)合用
@Autowired
@Resource
private Girl girl;
@Bean
public Girl girl() {
return new Girl("芈月");
}
@Bean
@Primary
public Girl meiNiang() {
return new Girl("媚娘");
}
输出结果芈月,因为芈月的方法名字是girl
@Component
这个注解会将该类的对象交由IOC容器来管理
相当于在xml中配置了一个bean
在不同层有不同的专用注解
@Service 业务层
@Repository 持久层
@Controller 控制层
@Scope
@Scope(scopeName = “prototype”) //指定作用域(相当于xml中bean的scope属性)
@Bean
将一个bean交由ioc管理, 和@Component的区别是:
- 使用的地方不一样: @Component是在类上面用;@Bean是在方法上使用
- 使用场景不一样:@Bean可以将无法修改源代码的类(第三方jar包中的类)交由IOC容器管理,而@Component注解只能用来标注我们自己写的类。
- @Component注解使用起来更简单,@Bean需要自己去实现初始化过程;
使用@Bean定义的bean,名称默认就是方法名,可以通过@Qualifier注解改变默认名称
- @Bean明确地指示了产生一个bean的方法,并且交给Spring容器管理
- 它很明确地告诉被注释的方法,你给我产生一个Bean,然后交给Spring容器
- 记住,@Bean就放在方法上
@Value
给类中的属性设置值。
// 设置常量值
@Value(“张三”)
private String sname;// 读取外部properties文件中的jdbc.url key对应的值
@Value("${jdbc.url}")
private String url;
@Required
已过时, 可以使用@Autowired的required属性代替
自定义注解
package com.lanou.spring.myannotaion;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target(ElementType.TYPE) //注解可以在什么场合下使用 TYPE代表只能在类上使用,TYPE变成METHOD代表只能在方法上使用
@Retention(RetentionPolicy.RUNTIME)//注解保留到什么时候
@Documented //是否生成文档
@Component //将bean交由IOC管理
public @interface MyComponent {
}
使用
package com.lanou.spring.bean;
import com.lanou.spring.myannotaion.MyComponent;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 皇上选佳丽
*/
@Scope(scopeName = "prototype") //指定作用域(相当于xml中bean的scope属性)
@Getter
@Setter
@MyComponent //使用派生自@Component注解的自定义注解实现bean定义功能
public class King {
@Value("李世民")
private String name;
@Autowired //默认是按照类型(byType)
// @Qualifier("girl") //此注解配合上面的自动注入注解(代表按照名称注入bean byName)
@Resource //这是JSR-250规范里的原生注解,Spring也支持用此注解实现自动注入
// 此注解的工作原理:先按照byName模式去查找bean,如果没找到自动切成成byType模式
private Girl girl;
}
Spring-AOP
AOP的概念、思想
面向切面编程:在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想
SpringAOP和Aspectj的区别
Aspectj是一个专门主打面向切面编程的框架,它使用一种特殊的语言(拓展自Java语言)来编写切面代码,后缀是.aj格式,并且需要专门的编译器将其编译成jvm可以运行的.class文件。
SpringAOP底层也是使用了Aspectj的方案,但在上层做了很多封装层面的工作,可以让开发人员直接使用Java语言来编写切面。并且由于是使用的时标准的Java语言,所以并不需要再额外安装一个专门的编译器。但是由于开发人员直接接触的时SpringAOP,那么凡是Spring中没有实现的那些AOP功能,我们就用不了了。这种情况下只能跟产品经理沟通或者去学习原生的Aspectj。
AOP的术语
切面
切面由切点和通知组成,它既包括了横切逻辑的定义,也包括了连接点的定义
pointcut切点
切点就是定义了通知被应用的位置
- 要横切的点(在Spring中就是Pointcut,通常是一个表达式)
- 切面与目标对象间的连接点有很多, 切点就是从众多的连接点中选择我们感兴趣的点将切面织入到目标代码中
所有的切点都来源于连接点,并不是所有的连接点都是切点
advice通知(要织入的代码)
前置通知(before)
- 在目标方法被调用之前调用通知功能
- 无法阻止目标方法执行
后置通知(after-returning)
- 在目标方法正常执行之后调用通知,此时不会关心方法的输出是什么
- 出现异常就无法通知
异常通知(after-throwing)
在目标方法抛出异常后调用通知
环绕通知(around)
- 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
- 可以阻止目标方法执行
- 方法内部可以使用连接点对象获取被代理方法的所有信息:调用、屏蔽被代理方法
最终通知(after)
目标方法执行结束后通知,无论是否出现异常都可以执行,跟Finally相似
连接点
- 连接点是一个比较空泛的概念,就是定义了哪一些地方是可以切入的,也就是所有允许你通知的地方。
- 连接点是一个应用执行过程中能够插入一个切面的点。
- 连接点可以是调用方法时、抛出异常时、甚至修改字段时
- 切面代码可以利用这些点插入到应用的正规流程中
- 程序执行过程中能够应用通知的所有点。
织入
- 织入是将通知添加到目标类具体连接点上的过程。
- 织入是将切面应用到目标对象来创建代理对象的过程。
- 切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入
Spring AOP的使用步骤
添加依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
XML方式
加入aop schema
<beans xmlns="http://www.springframework.org/schema/beans"
<!--添加的-->
xmlns:aop="http://www.springframework.org/schema/aop"
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
<!--添加的-->
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
定义切面类,并配置到applicationContext中
定义切面类
package com.lanou3g.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 该切面用来插入起床的逻辑
*/
public class MyAspect {
public void wakeup() {
System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?");
}
public void goToBed() {
System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了");
}
}
配置到applicationContext中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/beans/spring-aop.xsd">
<!--将bean注入到spring容器中-->
<bean id="Student" class="com.lanou3g.service.Student"/>
<bean id="nr" class="com.lanou3g.service.NetRed"/>
<bean id="coder" class="com.lanou3g.service.Coder"/>
<!--将定义的切面类配置进来-->
<bean id="wakeUpAspect"class="com.lanou3g.aop.MyAspect"/>
</beans>
定义aop:config
引用切面bean
定义切入点表达式
定义通知
- 通知类型:引用切入点表达式
- 织入切面bean中的方法
上面三部曲的整合:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="Student" class="com.lanou3g.service.Student"/>
<bean id="nr" class="com.lanou3g.service.NetRed"/>
<bean id="coder" class="com.lanou3g.service.Coder"/>
<!--将定义的切面类配置进来-->
<bean id="wakeUpAspect"class="com.lanou3g.aop.MyAspect"/>
<aop:config>
<!--相当于注解方式中定义了切入点-->
<aop:pointcut id="beforeOneDay" expression="execution(* com.lanou3g..NetRed.oneDay(..))"/>
<!--相当于注解方式中定义来了切面类-->
<aop:aspect ref="wakeUpAspect">
<!--前置通知-->
<aop:before method="wakeup" pointcut-ref="beforeOneDay"/>
<!--后置返回通知-->
<aop:after-returning method="afterRetuing" pointcut-ref="beforeOneDay" returning="message"/>
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="beforeOneDay" />
<!--后置异常通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="beforeOneDay" throwing="ex"/>
<!--后置通知-->
<aop:after method="goToBed" pointcut-ref="beforeOneDay"/>
</aop:aspect>
</aop:config>
</beans>
注解方式
创建实体类,并注入到Spring中
package com.lanou3g.service;
import org.springframework.stereotype.Component;
@Component
public class Student{
/**
* AOP方式织入起床、睡觉
*/
public String oneDay() {
// System.out.println("1. 起床");
System.out.println("2. 吃早饭");
System.out.println("3. 进班学习");
System.out.println("4. 吃中饭");
System.out.println("5. 午休");
System.out.println("6. 进班学习");
System.out.println("7. 吃晚饭");
System.out.println("8. 进班自习");
// System.out.println("9. 睡觉");
return "放学了!";
}
}
package com.lanou3g.service;
import org.springframework.stereotype.Component;
/**
* 程序员的一天
*/
@Component
public class Coder {
/**
* 用AOP思想解决:
* 1. 需要有一个代理类来代理Coder
* 2. 调用coder的oneDayByAOP方法时,实际上时通过代理来间接调用
* 3. 在代理类调用真正的Coder中方法的过程中,可以动态插入一些其他的逻辑
*/
public String oneDay() {
System.out.println("2. 洗漱");
System.out.println("3. 吃早饭");
System.out.println("4. 挤地铁");
System.out.println("5. 上班");
System.out.println("6. 敲代码");
System.out.println("7. 解Bug");
System.out.println("8. 被领导骂");
System.out.println("9. 加班");
System.out.println("10. 又被骂");
System.out.println("11. 删库跑路");
return "下班了!";
}
}
package com.lanou3g.service;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* 网红主播的一天
*/
@Component
public class NetRed {
/**
* 用AOP思想解决:
* 1. 需要有一个代理类来代理Coder
* 2. 调用coder的oneDayByAOP方法时,实际上时通过代理来间接调用
* 3. 在代理类调用真正的Coder中方法的过程中,可以动态插入一些其他的逻辑
*/
public String oneDay() {
System.out.println("2. 开播");
System.out.println("3. 开车");
System.out.println("4. 表演节目");
System.out.println("5. 开竞猜");
/*String name = null;
System.out.println(name.length());*/
System.out.println("6. 主播PK");
System.out.println("7. 被超管警告");
System.out.println("8. 下播");
return "网红的一天";
}
}
开启自动织入支持
package com.lanou3g;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 开启自动织入支持
*/
@Configuration //标识一个类作为Spring的一个bean配置文件
@ComponentScan(basePackages = "com.lanou3g")
@EnableAspectJAutoProxy // 开启注解支持,同时强制指定代理机制为cglib
public class MyConfigauration {
}
定义切面类,添加@Aspect、@Component注解
-
定义切入点(方法加注解)
package com.lanou3g; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component /** * 定义切入点 */ public class MyPointCut { @Pointcut("execution(* oneDay(..))") public void allOneDayMethod(){ } }
或者把切入点定义到切面类中,如下
-
定义切面类
package com.lanou3g.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * 定义切面类 */ @Aspect /** *由于@Aspect注解没有让Spring作为组件bean扫描的能力,所以我们*需要额外添加@Component注解 */ @Component public class MyAspect { /** *定义切入点 */ @Pointcut("execution(* oneDay(..))") public void allOneDayMethod(){ } /** *前置通知 */ @Before("allOneDayMethod()") public void wakeup() { System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?"); } /** *后置通知 */ @After("com.lanou3g.MyPointCut.allOneDayMethod()") public void goToBed() { System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了"); } /** *后置返回通知 */ @AfterReturning(value = "com.lanou3g.MyPointCut.allOneDayMethod()",returning = "message") public void afterRetuing(Object message) { System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message); } /** *后置异常通知 */ @AfterThrowing(value = "com.lanou3g.MyPointCut.allOneDayMethod()",throwing = "ex") public void afterThrowing(Throwable ex) { System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage()); } /** * 环绕通知 */ @Around("com.lanou3g.MyPointCut.allOneDayMethod()") public void aroundAdvice(ProceedingJoinPoint joinPoint) { // 连接点参数可以获取到被切入方法的所有信息 // 这里演示了如何获取被切入方法的名称 String targetMethodName = joinPoint.getSignature().getName(); System.out.println("[环绕通知]被切入的方法名:" + targetMethodName); // System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!"); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!"); } }
-
定义通知(方法加注解)
@AfterReturning(pointcut = "com.lanou3g.spring.GlobalPointcut.say_all_method()", returning = "ret") public Object afterRM(Object ret) { log.debug("[afterRM] 返回值: " + ret); return ret; }
Spring AOP的代理机制
JDK动态代理
- 创建接口
package com.lanou3g.service;
public interface Man {
String oneDay();
}
- 让实体类继承接口并实现其方法
package com.lanou3g.service;
/**
* 网红主播的一天
*/
public class NetRed implements Man{
/**
* 使用JDK动态代理,动态在方法执行前和执行后添加一些操作
*/
public String oneDay() {
System.out.println("2. 开播");
System.out.println("3. 开车");
System.out.println("4. 表演节目");
System.out.println("5. 开竞猜");
System.out.println("6. 主播PK");
System.out.println("7. 被超管警告");
System.out.println("8. 下播");
return "网红的一天";
}
}
- 定义一个事件管理器类实现invocationHandle接口,并重写invoke(代理类,被代理的方法,方法的参数列表)方法。
package com.lanou3g.jdkproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 此代理类负责在被代理类的方法运行前和后分别做一些操作
*/
/**
* JDK代理方式仅适于用实现了接口的类
*
* 因为JDK创建代理类的原理是, 先创建一个Object,然后让这个代理类实现指定的接口,进而拥有被代理类所有从接口中实现的方法
*/
public class MethodBeforeAfterProxy implements InvocationHandler {
Object target;
public MethodBeforeAfterProxy(Object target){
this.target = target;
}
/**
* 参数:
*methodProxy 被代理的对象
*method 被代理对象的方法
*args 方法的参数
*target 方法返回值
*/
@Override
public Object invoke(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
//模拟在方法运行前执行的操作
System.out.println(methodName + "方法开始执行了");
Object invoke = method.invoke(target, args);
//模拟方法在运行后执行的操作
System.out.println(methodName + "方法执行结束了" + invoke);
return invoke;
}
}
- 调用Proxy.newProxyInstance(类加载器,类实现的接口,事务处理器对象)生成一个代理实例。通过该代理实例调用方法
public static void testJDkDynamicProxy() {
Man netRed = new NetRed();
MethodBeforeAfterProxy mba = new MethodBeforeAfterProxy(netRed);
Man netRedProxy = (Man) Proxy.newProxyInstance(MethodBeforeAfterProxy.class.getClassLoader(), new Class[]{Man.class}, mba);
netRedProxy.oneDay();
}
动态代理不需要我们去编写代理类,是在程序中动态生成的。
CGLIB动态代理
Cglib可以代理实现接口的类、也可以代理未实现接口的类
代理实现接口的类
- 定义接口
package com.lanou3g.service2;
public interface Animal {
/**
* 动物吼叫
*/
void bray();
}
- 实现接口的类
package com.lanou3g.service2;
public class Cat implements Animal {
public void bray() {
System.out.println("喵喵~");
}
}
- 在pom文件中添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
- 生成代理类,实现MethodInterceptor接口,并重写intercept方法
package com.lanou3g.cglibproxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyCglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
enhancer.setUseCache(false);
return enhancer.create();
}
/**
* 拦截所有目标类方法的调用
* 参数:
* target 目标实例对象
* method 目标方法的反射对象
* args 方法的参数
* methodProxy 代理类的实例
*/
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
System.out.println(methodName + "开始执行了");
//代理类对象实例调用父类方法
Object retVal = methodProxy.invokeSuper(target, args);
System.out.println(methodName + "执行结束了");
return retVal;
}
}
- 调用cglibProxy.getProxy生成一个实例,使用该实例
/**
* cglib动态代理
* Cglib可以代理实现接口的类、也可以代理未实现接口的类
*/
public static void testCglibProxy() {
MyCglibProxy cglibProxy = new MyCglibProxy();
Cat cat = (Cat) cglibProxy.getProxy(Cat.class);
cat.bray();
}
代理未实现接口的类
- 定义一个类
package com.lanou3g.proxy.service2;
public class Cat {
public void bray() {
System.out.println("喵喵~");
}
}
- 在pom文件中添加依赖
- 生成代理类,实现MethodInterceptor接口,并重写intercept方法
- 调用cglibProxy.getProxy生成一个实例,使用该实例
步骤同上